Skip to content

Commit

Permalink
Fix cloning a note
Browse files Browse the repository at this point in the history
  • Loading branch information
nono committed Feb 12, 2024
1 parent 9b3a5ef commit bf54b0d
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
84 changes: 84 additions & 0 deletions model/note/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package note

import (
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/vfs"
"github.com/cozy/prosemirror-go/model"
"github.com/gofrs/uuid/v5"
)

// CopyFile is an overloaded version of Fs.CopyFile that take care of also
// copying the images in the note.
func CopyFile(inst *instance.Instance, olddoc, newdoc *vfs.FileDoc) error {
// Load data from the source note
noteDoc, err := get(inst, olddoc)
if err != nil {
return err
}
content, err := noteDoc.Content()
if err != nil {
return err
}
srcImages, err := getImages(inst, olddoc.ID())
if err != nil {
return err
}

// We need a fileID for saving images
uuidv7, _ := uuid.NewV7()
newdoc.SetID(uuidv7.String())

// id of the image in the source doc -> image in the destination doc
mapping := make(map[string]*Image)
var dstImages []*Image
for _, img := range srcImages {
if img.ToRemove {
continue
}
copied, err := CopyImageToAnotherNote(inst, img.ID(), newdoc)
if err != nil {
return err
}
mapping[img.ID()] = copied
dstImages = append(dstImages, copied)
}

updateProsemirrorImageURLs(content, mapping)

md := markdownSerializer(dstImages).Serialize(content)
if err != nil {
return err
}
body := []byte(md)
if hasImages(dstImages) {
body, _ = buildArchive(inst, []byte(md), dstImages)
}
newdoc.ByteSize = int64(len(body))
newdoc.MD5Sum = nil
newdoc.Metadata["content"] = content.ToJSON()

file, err := inst.VFS().CreateFile(newdoc, nil)
if err != nil {
return err
}
_, err = file.Write(body)
if cerr := file.Close(); cerr != nil && err == nil {
err = cerr
}
return err
}

func updateProsemirrorImageURLs(node *model.Node, mapping map[string]*Image) {
if node.Type.Name == "media" {
nodeURL, _ := node.Attrs["url"].(string)
for id, img := range mapping {
if nodeURL == id {
node.Attrs["url"] = img.ID()
}
}
}

node.ForEach(func(child *model.Node, _ int, _ int) {
updateProsemirrorImageURLs(child, mapping)
})
}
Binary file added tests/fixtures/note-with-an-image.cozy-note
Binary file not shown.
7 changes: 6 additions & 1 deletion web/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,12 @@ func FileCopyHandler(c echo.Context) error {
newdoc.ResetFullpath()
updateFileCozyMetadata(c, newdoc, true)

err = fs.CopyFile(olddoc, newdoc)
if olddoc.Mime == consts.NoteMimeType {
// We need a special copy for notes because of their images
err = note.CopyFile(inst, olddoc, newdoc)
} else {
err = fs.CopyFile(olddoc, newdoc)
}
if err != nil {
return WrapVfsError(err)
}
Expand Down
87 changes: 87 additions & 0 deletions web/notes/notes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,93 @@ func TestNotes(t *testing.T) {
data.HasValue("id", fileID)
data.Path("$.attributes.instance").IsEqual(inst.Domain)
})

t.Run("CopyNoteWithAnImage", func(t *testing.T) {
e := testutils.CreateTestClient(t, ts.URL)

toImport, err := os.ReadFile("../../tests/fixtures/note-with-an-image.cozy-note")
require.NoError(t, err)

obj := e.POST("/files/io.cozy.files.root-dir").
WithQuery("Type", "file").
WithQuery("Name", "Note with an image.cozy-note").
WithHeader("Authorization", "Bearer "+token).
WithHeader("Content-Type", "text/plain").
WithBytes(toImport).
Expect().Status(201).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

data := obj.Value("data").Object()
data.HasValue("type", "io.cozy.files")
srcID := data.Value("id").String().NotEmpty().Raw()

obj = e.GET("/files/"+srcID).
WithHeader("Authorization", "Bearer "+token).
Expect().Status(200).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

image := obj.Value("included").Array().
Find(func(_ int, value *httpexpect.Value) bool {
value.Object().NotHasValue("type", consts.FilesVersions)
return true
}).
Object()

image.HasValue("type", consts.NotesImages)
image.Value("id").String().NotEmpty()
image.Value("meta").Object().NotEmpty()

attrs := image.Value("attributes").Object()
attrs.Value("name").String().NotEmpty()
attrs.Value("cozyMetadata").Object().NotEmpty()
attrs.HasValue("mime", "image/png")

link := data.Path("$.links.self").String().NotEmpty().Raw()

e.GET(link).
WithHeader("Authorization", "Bearer "+token).
Expect().Status(200)

obj = e.POST("/files/"+srcID+"/copy").
WithHeader("Authorization", "Bearer "+token).
Expect().Status(201).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

data = obj.Value("data").Object()
data.HasValue("type", "io.cozy.files")
dstID := data.Value("id").String().NotEmpty().Raw()

obj = e.GET("/files/"+dstID).
WithHeader("Authorization", "Bearer "+token).
Expect().Status(200).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

image = obj.Value("included").Array().
Find(func(_ int, value *httpexpect.Value) bool {
value.Object().NotHasValue("type", consts.FilesVersions)
return true
}).
Object()

image.HasValue("type", consts.NotesImages)
image.Value("id").String().NotEmpty()
image.Value("meta").Object().NotEmpty()

attrs = image.Value("attributes").Object()
attrs.Value("name").String().NotEmpty()
attrs.Value("cozyMetadata").Object().NotEmpty()
attrs.HasValue("mime", "image/png")

link = data.Path("$.links.self").String().NotEmpty().Raw()

e.GET(link).
WithHeader("Authorization", "Bearer "+token).
Expect().Status(200)
})
}

func assertInitialNote(t *testing.T, obj *httpexpect.Object) {
Expand Down

0 comments on commit bf54b0d

Please sign in to comment.