diff --git a/docs/files.md b/docs/files.md index ca045f4953e..fe9a277a4db 100644 --- a/docs/files.md +++ b/docs/files.md @@ -372,6 +372,134 @@ Content-Type: application/vnd.api+json } ``` +### POST `/files/_all_docs` + +This route allows to fetch several files in one request. It is the same as the +`_all_docs` request for CouchDB, except the response is in the JSON-API format, +(with thumbnails and path for the files). + +### Request + +```http +POST /files/_all_docs HTTP/1.1 +``` + +```json +{ + "keys": ["e8c1561846c730428180a5f6c6107914", "e8c1561846c730428180a5f6c6109007"] +} +``` + +### Response + +```http +HTTP/1.1 200 OK +Date: Mon, 27 Sept 2016 12:28:53 GMT +Content-Length: ... +Content-Type: application/json +``` + +```json +{ + "data": [ + { + "type": "io.cozy.files", + "id": "e8c1561846c730428180a5f6c6107914", + "attributes": { + "type": "file", + "name": "nicepic1.jpg", + "dir_id": "f49b4087cbf946dfc759214394009a6c", + "created_at": "2020-02-13T16:35:47.568155477+01:00", + "updated_at": "2020-02-13T16:35:47.568155477+01:00", + "size": "345385", + "md5sum": "12cGYwT+RiNjFxf4f7AmzQ==", + "mime": "image/jpeg", + "class": "image", + "executable": false, + "trashed": false, + "tags": [], + "path": "/Pictures/nicepic1.jpg", + "metadata": { + "datetime": "2020-02-13T16:35:47.568155477+01:00", + "extractor_version": 2, + "height": 1080, + "width": 1920 + } + }, + "meta": { + "rev": "2-235e715b1d82a93285be1b0bd691b779" + }, + "links": { + "self": "/files/e8c1561846c730428180a5f6c6107914", + "tiny": "/files/e8c1561846c730428180a5f6c6107914/thumbnails/377327a8e20d6a50/tiny", + "small": "/files/e8c1561846c730428180a5f6c6107914/thumbnails/377327a8e20d6a50/small", + "medium": "/files/e8c1561846c730428180a5f6c6107914/thumbnails/377327a8e20d6a50/medium", + "large": "/files/e8c1561846c730428180a5f6c6107914/thumbnails/377327a8e20d6a50/large" + }, + "relationships": { + "parent": { + "links": { + "related": "/files/f49b4087cbf946dfc759214394009a6c" + }, + "data": { + "id": "f49b4087cbf946dfc759214394009a6c", + "type": "io.cozy.files" + } + } + } + }, + { + "type": "io.cozy.files", + "id": "e8c1561846c730428180a5f6c6109007", + "attributes": { + "type": "file", + "name": "nicepic2.jpg", + "dir_id": "f49b4087cbf946dfc759214394009a6c", + "created_at": "2020-02-13T16:35:47.845049743+01:00", + "updated_at": "2020-02-13T16:35:47.845049743+01:00", + "size": "323009", + "md5sum": "Fla3ucNXuW2Xw/TK8pfsPA==", + "mime": "image/jpeg", + "class": "image", + "executable": false, + "trashed": false, + "tags": [], + "path": "/Pictures/nicepic2.jpg", + "metadata": { + "datetime": "2020-02-13T16:35:47.845049743+01:00", + "extractor_version": 2, + "height": 1080, + "width": 1920 + } + }, + "meta": { + "rev": "2-4883d6b8ccad32f8fb056af9b7f8b37f" + }, + "links": { + "self": "/files/e8c1561846c730428180a5f6c6109007", + "tiny": "/files/e8c1561846c730428180a5f6c6109007/thumbnails/58a4aea31b00c99d/tiny", + "small": "/files/e8c1561846c730428180a5f6c6109007/thumbnails/58a4aea31b00c99d/small", + "medium": "/files/e8c1561846c730428180a5f6c6109007/thumbnails/58a4aea31b00c99d/medium", + "large": "/files/e8c1561846c730428180a5f6c6109007/thumbnails/58a4aea31b00c99d/large" + }, + "relationships": { + "parent": { + "links": { + "related": "/files/f49b4087cbf946dfc759214394009a6c" + }, + "data": { + "id": "f49b4087cbf946dfc759214394009a6c", + "type": "io.cozy.files" + } + } + } + } + ] +} +``` + + + ### GET `/files/_changes` This endpoint is similar to the changes feed of CouchDB for io.cozy.files. diff --git a/web/files/files.go b/web/files/files.go index 7743c23a36a..79d9cd8c2f3 100644 --- a/web/files/files.go +++ b/web/files/files.go @@ -1524,6 +1524,43 @@ func DestroyFileHandler(c echo.Context) error { return c.NoContent(204) } +// GetAllDocs is the handler for POST /files/_all_docs +func GetAllDocs(c echo.Context) error { + inst := middlewares.GetInstance(c) + if err := middlewares.AllowWholeType(c, permission.GET, consts.Files); err != nil { + return err + } + var allDocsReq struct { + Keys []string `json:"keys"` + } + if err := json.NewDecoder(c.Request().Body).Decode(&allDocsReq); err != nil { + return jsonapi.Errorf(http.StatusBadRequest, "%s", err) + } + + req := &couchdb.AllDocsRequest{Keys: allDocsReq.Keys} + var results []vfs.DirOrFileDoc + if err := couchdb.GetAllDocs(inst, consts.Files, req, &results); err != nil { + return err + } + + out := make([]jsonapi.Object, 0) + fp := vfs.NewFilePatherWithCache(inst.VFS()) + for _, result := range results { + if result.ID() == consts.TrashDirID { + continue + } + d, f := result.Refine() + if d != nil { + out = append(out, newDir(d)) + } else { + file := NewFile(f, inst) + file.IncludePath(fp) + out = append(out, file) + } + } + return jsonapi.DataList(c, http.StatusOK, out, nil) +} + // FindFilesMango is the route POST /files/_find // used to retrieve files and their metadata from a mango query. func FindFilesMango(c echo.Context) error { @@ -1938,6 +1975,7 @@ func Routes(router *echo.Group) { router.POST("/:file-id/versions", CopyVersionHandler) router.DELETE("/versions", ClearOldVersions) + router.POST("/_all_docs", GetAllDocs) router.POST("/_find", FindFilesMango) router.GET("/_changes", ChangesFeed) diff --git a/web/files/files_test.go b/web/files/files_test.go index c688feb0a5c..2598d049773 100644 --- a/web/files/files_test.go +++ b/web/files/files_test.go @@ -3305,6 +3305,33 @@ func TestFiles(t *testing.T) { }) }) + t.Run("GetAllDocs", func(t *testing.T) { + e := testutils.CreateTestClient(t, ts.URL) + + obj := e.POST("/files/_all_docs"). + WithHeader("Content-Type", "application/json"). + WithHeader("Authorization", "Bearer "+token). + WithBytes([]byte(fmt.Sprintf(`{ + "keys": ["io.cozy.files.root-dir", "%s"] + }`, fileID))). + Expect().Status(200). + JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). + Object() + + data := obj.Value("data").Array() + data.Length().IsEqual(2) + + elem := data.Value(0).Object() + attrs := elem.Value("attributes").Object() + attrs.HasValue("path", "/") + + elem = data.Value(1).Object() + attrs = elem.Value("attributes").Object() + attrs.Value("path").String().NotEmpty() + links := elem.Value("links").Object() + links.Value("tiny").String().NotEmpty() + }) + t.Run("Find", func(t *testing.T) { e := testutils.CreateTestClient(t, ts.URL)