diff --git a/backend/app/rest/api/rest_private_test.go b/backend/app/rest/api/rest_private_test.go index cf053e2a..33663b6e 100644 --- a/backend/app/rest/api/rest_private_test.go +++ b/backend/app/rest/api/rest_private_test.go @@ -9,15 +9,18 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "os" "strings" "testing" "time" + "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/umputun/remark/backend/app/store" + "github.com/umputun/remark/backend/app/store/image" ) func TestRest_Create(t *testing.T) { @@ -531,3 +534,74 @@ func TestRest_SavePictureCtrl(t *testing.T) { assert.Equal(t, "file content 123", string(body)) assert.Equal(t, "image/png", resp.Header.Get("Content-Type")) } + +func TestRest_CreateWithPictures(t *testing.T) { + ts, svc, teardown := startupT(t) + defer func() { + teardown() + os.RemoveAll("/tmp/remark42") + }() + lgr.Setup(lgr.Debug, lgr.CallerFile, lgr.CallerFunc) + + svc.ImageService = &image.Service{ + Store: &image.FileSystem{ + Staging: "/tmp/remark42/images.staging", + Location: "/tmp/remark42/images", + MaxSize: 1000, + }, + TTL: time.Millisecond * 100, + } + svc.DataService.EditDuration = time.Millisecond * 100 + svc.DataService.ImageService = svc.ImageService + + uploadPicture := func(file, content string) (id string) { + r := strings.NewReader(content) + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + fileWriter, err := bodyWriter.CreateFormFile("file", file) + require.NoError(t, err) + _, err = io.Copy(fileWriter, r) + require.NoError(t, err) + contentType := bodyWriter.FormDataContentType() + require.NoError(t, bodyWriter.Close()) + client := http.Client{} + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/picture", ts.URL), bodyBuf) + require.NoError(t, err) + req.Header.Add("Content-Type", contentType) + req.Header.Add("X-JWT", devToken) + resp, err := client.Do(req) + assert.Nil(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, err := ioutil.ReadAll(resp.Body) + require.Nil(t, err) + m := map[string]string{} + err = json.Unmarshal(body, &m) + assert.Contains(t, m["id"], ".png") + return m["id"] + } + + id1 := uploadPicture("pic1.png", "file content 123") + id2 := uploadPicture("pic2.png", "file content 12345") + id3 := uploadPicture("pic3.png", "file content xyz12365789") + + text := fmt.Sprintf(`text 123 ![](/api/v1/picture/%s) *xxx* ![](/api/v1/picture/%s) ![](/api/v1/picture/%s)`, id1, id2, id3) + body := fmt.Sprintf(`{"text": "%s", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`, text) + + resp, err := post(t, ts.URL+"/api/v1/comment", body) + assert.Nil(t, err) + b, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + require.Equal(t, http.StatusCreated, resp.StatusCode, string(b)) + + _, err = os.Stat("/tmp/remark42/images/" + id1) + assert.NotNil(t, err, "not moved from staging yet") + + time.Sleep(300 * time.Millisecond) + _, err = os.Stat("/tmp/remark42/images/" + id1) + assert.NoError(t, err, "moved from staging") + _, err = os.Stat("/tmp/remark42/images/" + id2) + assert.NoError(t, err, "moved from staging") + _, err = os.Stat("/tmp/remark42/images/" + id3) + assert.NoError(t, err, "moved from staging") +} diff --git a/backend/app/store/image/fs_store.go b/backend/app/store/image/fs_store.go index b65de287..79049635 100644 --- a/backend/app/store/image/fs_store.go +++ b/backend/app/store/image/fs_store.go @@ -74,6 +74,7 @@ func (f *FileSystem) Save(fileName string, userID string, r io.Reader) (id strin // Commit file stored in staging location by moving it to permanent location func (f *FileSystem) Commit(id string) error { + log.Printf("[DEBUG] commit image %s", id) stagingImage, permImage := f.location(f.Staging, id), f.location(f.Location, id) if err := os.MkdirAll(path.Dir(permImage), 0700); err != nil { diff --git a/backend/app/store/image/image.go b/backend/app/store/image/image.go index 5f370014..69230375 100644 --- a/backend/app/store/image/image.go +++ b/backend/app/store/image/image.go @@ -48,12 +48,12 @@ type submitReq struct { // Submit multiple ids via function for delayed commit func (s *Service) Submit(idsFn func() []string) { - if idsFn == nil { + if idsFn == nil || s == nil { return } s.once.Do(func() { - log.Printf("[DEBUG] image submiter activate") + log.Printf("[DEBUG] image submitter activated") s.submitCh = make(chan submitReq, submitQueueSize) s.wg.Add(1) go func() { @@ -69,7 +69,7 @@ func (s *Service) Submit(idsFn func() []string) { } } } - log.Printf("[INFO] image submiter terminated") + log.Printf("[INFO] image submitter terminated") }() }) diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 6f9634ac..1a71846b 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -92,14 +92,20 @@ func (s *DataStore) Create(comment store.Comment) (commentID string, err error) comment.PostTitle = title }() - // submit comment images to delayed processing + s.submitImages(comment) + return s.Interface.Create(comment) +} + +// submitImages initiated delayed commit of all images from the comment uploaded to remark42 +func (s *DataStore) submitImages(comment store.Comment) { + s.ImageService.Submit(func() []string { c := comment - cc, e := s.Get(c.Locator, c.ID) // this can be called after last edit, we have to retrieve fresh comment - if e != nil { + cc, err := s.Get(c.Locator, c.ID) // this can be called after last edit, we have to retrieve fresh comment + if err != nil { return nil } - imgIds, e := s.ImageService.ExtractPictures(cc.Text) + imgIds, err := s.ImageService.ExtractPictures(cc.Text) if err != nil { return nil } @@ -108,8 +114,6 @@ func (s *DataStore) Create(comment store.Comment) (commentID string, err error) } return imgIds }) - - return s.Interface.Create(comment) } // prepareNewComment sets new comment fields, hashing and sanitizing data