diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go
index 949fe889..220982ef 100644
--- a/backend/app/cmd/server.go
+++ b/backend/app/cmd/server.go
@@ -487,6 +487,11 @@ func (a *serverApp) run(ctx context.Context) error {
go a.devAuth.Run(context.Background()) // dev oauth2 server on :8084
}
+ // staging images resubmit after restart of the app
+ if e := a.dataService.ResubmitStagingImages(a.Sites); e != nil {
+ log.Printf("[WARN] failed to resubmit comments with staging images, %s", e)
+ }
+
go a.imageService.Cleanup(ctx) // pictures cleanup for staging images
a.restSrv.Run(a.Port)
diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go
index 4a429d6a..a3d65fd9 100644
--- a/backend/app/store/service/service.go
+++ b/backend/app/store/service/service.go
@@ -200,6 +200,29 @@ func (s *DataStore) DeleteUserDetail(siteID string, userID string, detail engine
})
}
+// ResubmitStagingImages retrieves timestamp of the oldest image in staging and
+// calls s.submitImages on all comments newer than it
+func (s *DataStore) ResubmitStagingImages(sites []string) error {
+ info, err := s.ImageService.Info()
+ if err != nil {
+ return err
+ }
+ ts := info.FirstStagingImageTS
+ if ts.IsZero() {
+ return nil
+ }
+ result := new(multierror.Error)
+ for _, site := range sites {
+ locator := store.Locator{SiteID: site}
+ comments, err := s.FindSince(locator, "time", store.User{}, ts)
+ result = multierror.Append(result, errors.Wrapf(err, "problem finding comments for site %s", site))
+ for _, c := range comments {
+ s.submitImages(c.Locator, c.ID)
+ }
+ }
+ return result.ErrorOrNil()
+}
+
// submitImages initiated delayed commit of all images from the comment uploaded to remark42
func (s *DataStore) submitImages(locator store.Locator, commentID string) {
diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go
index 76195d41..1a494bb9 100644
--- a/backend/app/store/service/service_test.go
+++ b/backend/app/store/service/service_test.go
@@ -1,6 +1,7 @@
package service
import (
+ "context"
"fmt"
"io/ioutil"
"math/rand"
@@ -1302,6 +1303,105 @@ func TestService_submitImages(t *testing.T) {
time.Sleep(250 * time.Millisecond)
}
+func TestService_ResubmitStagingImages(t *testing.T) {
+ mockStore := image.MockStore{}
+ imgSvc := image.NewService(&mockStore,
+ image.ServiceParams{
+ EditDuration: 10 * time.Millisecond,
+ ImageAPI: "http://127.0.0.1:8080/api/v1/picture/",
+ })
+ defer imgSvc.Close(context.TODO())
+
+ eng, teardown := prepStoreEngine(t)
+ defer teardown()
+ b := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvc}
+
+ // create comment with three images without preparing it properly
+ comment := store.Comment{
+ ID: "id-0",
+ Text: `
+ 
+
`,
+ Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local),
+ Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"},
+ User: store.User{ID: "user1", Name: "user name"},
+ }
+ _, err := b.Engine.Create(comment)
+ require.NoError(t, err)
+
+ // resubmit single comment with three images, of which two are in staging storage
+ mockStore.On("Info").Once().Return(image.StoreInfo{FirstStagingImageTS: time.Time{}.Add(time.Second)}, nil)
+ err = b.ResubmitStagingImages([]string{"radio-t"})
+ assert.NoError(t, err)
+
+ // wait for Submit goroutine to commit image
+ mockStore.On("Commit", "dev_user/bqf122eq9r8ad657n3ng").Once().Return(nil)
+ mockStore.On("Commit", "dev_user/bqf321eq9r8ad657n3ng").Once().Return(nil)
+ time.Sleep(time.Millisecond * 100)
+
+ mockStore.AssertNumberOfCalls(t, "Info", 1)
+ mockStore.AssertNumberOfCalls(t, "Commit", 2)
+
+ // empty answer
+ mockStoreEmpty := image.MockStore{}
+ imgSvcEmpty := image.NewService(&mockStoreEmpty,
+ image.ServiceParams{
+ EditDuration: 10 * time.Millisecond,
+ ImageAPI: "http://127.0.0.1:8080/api/v1/picture/",
+ })
+ defer imgSvcEmpty.Close(context.TODO())
+ bEmpty := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvcEmpty}
+
+ // resubmit receive empty timestamp and should do nothing
+ mockStoreEmpty.On("Info").Once().Return(image.StoreInfo{FirstStagingImageTS: time.Time{}}, nil)
+ err = bEmpty.ResubmitStagingImages([]string{"radio-t", "non_existent"})
+ assert.NoError(t, err)
+
+ mockStoreEmpty.AssertNumberOfCalls(t, "Info", 1)
+
+ // error from image storage
+ mockStoreError := image.MockStore{}
+ imgSvcError := image.NewService(&mockStoreError,
+ image.ServiceParams{
+ EditDuration: 10 * time.Millisecond,
+ ImageAPI: "http://127.0.0.1:8080/api/v1/picture/",
+ })
+ defer imgSvcError.Close(context.TODO())
+ bError := DataStore{Engine: eng, EditDuration: 10 * time.Millisecond, ImageService: imgSvcError}
+
+ // resubmit will receive error from image storage and should return it
+ mockStoreError.On("Info").Once().Return(image.StoreInfo{}, errors.New("mock_err"))
+ err = bError.ResubmitStagingImages([]string{"radio-t"})
+ assert.EqualError(t, err, "mock_err")
+
+ mockStoreError.AssertNumberOfCalls(t, "Info", 1)
+}
+
+func TestService_ResubmitStagingImages_EngineError(t *testing.T) {
+ mockStore := image.MockStore{}
+ imgSvc := image.NewService(&mockStore,
+ image.ServiceParams{
+ EditDuration: 10 * time.Millisecond,
+ ImageAPI: "http://127.0.0.1:8080/api/v1/picture/",
+ })
+ defer imgSvc.Close(context.TODO())
+
+ engineMock := engine.MockInterface{}
+ site1Req := engine.FindRequest{Locator: store.Locator{SiteID: "site1", URL: ""}, Sort: "time", Since: time.Time{}.Add(time.Second)}
+ site2Req := engine.FindRequest{Locator: store.Locator{SiteID: "site2", URL: ""}, Sort: "time", Since: time.Time{}.Add(time.Second)}
+ engineMock.On("Find", site1Req).Return(nil, nil)
+ engineMock.On("Find", site2Req).Return(nil, errors.New("mockError"))
+ b := DataStore{Engine: &engineMock, EditDuration: 10 * time.Millisecond, ImageService: imgSvc}
+
+ // One call without error and one with error
+ mockStore.On("Info").Once().Return(image.StoreInfo{FirstStagingImageTS: time.Time{}.Add(time.Second)}, nil)
+ err := b.ResubmitStagingImages([]string{"site1", "site2"})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "problem finding comments for site site2: mockError")
+
+ mockStore.AssertNumberOfCalls(t, "Info", 1)
+}
+
func TestService_alterComment(t *testing.T) {
engineMock := engine.MockInterface{}