github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/files/notsynchronized.go (about)

     1  package files
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/cozy/cozy-stack/model/permission"
     9  	"github.com/cozy/cozy-stack/pkg/consts"
    10  	"github.com/cozy/cozy-stack/pkg/couchdb"
    11  	"github.com/cozy/cozy-stack/pkg/jsonapi"
    12  	"github.com/cozy/cozy-stack/web/middlewares"
    13  	"github.com/labstack/echo/v4"
    14  )
    15  
    16  // AddNotSynchronizedOn is the echo.handler for adding not_synchronized_on to
    17  // a directory
    18  // POST /files/:file-id/relationships/not_synchronized_on
    19  func AddNotSynchronizedOn(c echo.Context) error {
    20  	instance := middlewares.GetInstance(c)
    21  
    22  	fileID := c.Param("file-id")
    23  	dir, err := instance.VFS().DirByID(fileID)
    24  	if err != nil {
    25  		return WrapVfsError(err)
    26  	}
    27  
    28  	if err = middlewares.AllowVFS(c, permission.PATCH, dir); err != nil {
    29  		return err
    30  	}
    31  
    32  	references, err := jsonapi.BindRelations(c.Request())
    33  	if err != nil {
    34  		return WrapVfsError(err)
    35  	}
    36  
    37  	dir.AddNotSynchronizedOn(references...)
    38  	updateDirCozyMetadata(c, dir)
    39  	if err = couchdb.UpdateDoc(instance, dir); err != nil {
    40  		return WrapVfsError(err)
    41  	}
    42  
    43  	refs := dir.NotSynchronizedOn
    44  	count := len(refs)
    45  	meta := jsonapi.Meta{Rev: dir.Rev(), Count: &count}
    46  	return jsonapi.DataRelations(c, http.StatusOK, refs, &meta, nil, nil)
    47  }
    48  
    49  // RemoveNotSynchronizedOn is the echo.handler for removing not_synchronized_on to
    50  // a directory
    51  // DELETE /files/:file-id/relationships/not_synchronized_on
    52  func RemoveNotSynchronizedOn(c echo.Context) error {
    53  	instance := middlewares.GetInstance(c)
    54  
    55  	fileID := c.Param("file-id")
    56  	dir, err := instance.VFS().DirByID(fileID)
    57  	if err != nil {
    58  		return WrapVfsError(err)
    59  	}
    60  
    61  	if err = middlewares.AllowVFS(c, permission.PATCH, dir); err != nil {
    62  		return err
    63  	}
    64  
    65  	references, err := jsonapi.BindRelations(c.Request())
    66  	if err != nil {
    67  		return WrapVfsError(err)
    68  	}
    69  
    70  	dir.RemoveNotSynchronizedOn(references...)
    71  	updateDirCozyMetadata(c, dir)
    72  	if err = couchdb.UpdateDoc(instance, dir); err != nil {
    73  		return WrapVfsError(err)
    74  	}
    75  
    76  	refs := dir.NotSynchronizedOn
    77  	count := len(refs)
    78  	meta := jsonapi.Meta{Rev: dir.Rev(), Count: &count}
    79  	return jsonapi.DataRelations(c, http.StatusOK, refs, &meta, nil, nil)
    80  }
    81  
    82  // ListNotSynchronizedOn list all directories not synchronized on a device
    83  // GET /data/:type/:id/relationships/not_synchronizing
    84  // Beware, this is actually used in the web/data Routes
    85  func ListNotSynchronizedOn(c echo.Context) error {
    86  	instance := middlewares.GetInstance(c)
    87  	doctype := c.Param("doctype")
    88  	id := getDocID(c)
    89  	includeDocs := c.QueryParam("include") == "files"
    90  
    91  	if err := middlewares.AllowTypeAndID(c, permission.GET, doctype, id); err != nil {
    92  		if middlewares.AllowWholeType(c, permission.GET, consts.Files) != nil {
    93  			return err
    94  		}
    95  	}
    96  
    97  	cursor, err := jsonapi.ExtractPaginationCursor(c, defaultRefsPerPage, maxRefsPerPage)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	start := []string{doctype, id}
   103  	end := []string{doctype, id, couchdb.MaxString}
   104  	req := &couchdb.ViewRequest{
   105  		StartKey:    start,
   106  		EndKey:      end,
   107  		IncludeDocs: includeDocs,
   108  	}
   109  	cursor.ApplyTo(req)
   110  
   111  	var res couchdb.ViewResponse
   112  	if err := couchdb.ExecView(instance, couchdb.DirNotSynchronizedOnView, req, &res); err != nil {
   113  		return err
   114  	}
   115  
   116  	cursor.UpdateFrom(&res)
   117  	links := &jsonapi.LinksList{}
   118  	if cursor.HasMore() {
   119  		params, err2 := jsonapi.PaginationCursorToParams(cursor)
   120  		if err2 != nil {
   121  			return err2
   122  		}
   123  		links.Next = fmt.Sprintf("%s?%s",
   124  			c.Request().URL.Path, params.Encode())
   125  	}
   126  
   127  	meta := &jsonapi.Meta{Count: &res.Total}
   128  
   129  	refs := make([]couchdb.DocReference, len(res.Rows))
   130  	var docs []jsonapi.Object
   131  	if includeDocs {
   132  		docs = make([]jsonapi.Object, len(res.Rows))
   133  	}
   134  
   135  	for i, row := range res.Rows {
   136  		refs[i] = couchdb.DocReference{
   137  			ID:   row.ID,
   138  			Type: consts.Files,
   139  		}
   140  
   141  		if includeDocs {
   142  			docs[i], err = rawMessageToObject(instance, row.Doc)
   143  			if err != nil {
   144  				return err
   145  			}
   146  		}
   147  	}
   148  
   149  	return jsonapi.DataRelations(c, http.StatusOK, refs, meta, links, docs)
   150  }
   151  
   152  // AddBulkNotSynchronizedOn add some not_synchronized_on for a device
   153  // POST /data/:type/:id/relationships/not_synchronizing
   154  // Beware, this is actually used in the web/data Routes
   155  func AddBulkNotSynchronizedOn(c echo.Context) error {
   156  	instance := middlewares.GetInstance(c)
   157  	doctype := c.Param("doctype")
   158  	id := getDocID(c)
   159  
   160  	references, err := jsonapi.BindRelations(c.Request())
   161  	if err != nil {
   162  		return WrapVfsError(err)
   163  	}
   164  
   165  	docRef := couchdb.DocReference{
   166  		Type: doctype,
   167  		ID:   id,
   168  	}
   169  
   170  	if err = middlewares.AllowTypeAndID(c, permission.PUT, doctype, id); err != nil {
   171  		if middlewares.AllowWholeType(c, permission.PATCH, consts.Files) != nil {
   172  			return err
   173  		}
   174  	}
   175  
   176  	docs := make([]interface{}, len(references))
   177  	oldDocs := make([]interface{}, len(references))
   178  
   179  	for i, ref := range references {
   180  		dir, _, err := instance.VFS().DirOrFileByID(ref.ID)
   181  		if err != nil {
   182  			return WrapVfsError(err)
   183  		}
   184  		if dir == nil {
   185  			return jsonapi.BadRequest(errors.New("Cannot add not_synchronized_on on files"))
   186  		}
   187  		oldDocs[i] = dir.Clone()
   188  		dir.AddNotSynchronizedOn(docRef)
   189  		updateDirCozyMetadata(c, dir)
   190  		docs[i] = dir
   191  	}
   192  
   193  	// Use bulk update for better performances
   194  	err = couchdb.BulkUpdateDocs(instance, consts.Files, docs, oldDocs)
   195  	if err != nil {
   196  		return WrapVfsError(err)
   197  	}
   198  	return c.NoContent(204)
   199  }
   200  
   201  // RemoveBulkNotSynchronizedOn removes some not_synchronized_on for several
   202  // directories.
   203  // DELETE /data/:type/:id/relationships/not_synchronizing
   204  // Beware, this is actually used in the web/data Routes
   205  func RemoveBulkNotSynchronizedOn(c echo.Context) error {
   206  	instance := middlewares.GetInstance(c)
   207  	doctype := c.Param("doctype")
   208  	id := getDocID(c)
   209  
   210  	references, err := jsonapi.BindRelations(c.Request())
   211  	if err != nil {
   212  		return WrapVfsError(err)
   213  	}
   214  
   215  	docRef := couchdb.DocReference{
   216  		Type: doctype,
   217  		ID:   id,
   218  	}
   219  
   220  	if err := middlewares.AllowTypeAndID(c, permission.DELETE, doctype, id); err != nil {
   221  		if middlewares.AllowWholeType(c, permission.PATCH, consts.Files) != nil {
   222  			return err
   223  		}
   224  	}
   225  
   226  	docs := make([]interface{}, len(references))
   227  	oldDocs := make([]interface{}, len(references))
   228  
   229  	for i, ref := range references {
   230  		dir, _, err := instance.VFS().DirOrFileByID(ref.ID)
   231  		if err != nil {
   232  			return WrapVfsError(err)
   233  		}
   234  		if dir == nil {
   235  			return jsonapi.BadRequest(errors.New("Cannot add not_synchronized_on on files"))
   236  		}
   237  		oldDocs[i] = dir.Clone()
   238  		dir.RemoveNotSynchronizedOn(docRef)
   239  		updateDirCozyMetadata(c, dir)
   240  		docs[i] = dir
   241  	}
   242  
   243  	// Use bulk update for better performances
   244  	err = couchdb.BulkUpdateDocs(instance, consts.Files, docs, oldDocs)
   245  	if err != nil {
   246  		return WrapVfsError(err)
   247  	}
   248  	return c.NoContent(204)
   249  }
   250  
   251  // NotSynchronizedOnRoutes adds the /data/:doctype/:docid/relationships/not_synchronizing routes.
   252  func NotSynchronizedOnRoutes(router *echo.Group) {
   253  	router.GET("/:docid/relationships/not_synchronizing", ListNotSynchronizedOn)
   254  	router.POST("/:docid/relationships/not_synchronizing", AddBulkNotSynchronizedOn)
   255  	router.DELETE("/:docid/relationships/not_synchronizing", RemoveBulkNotSynchronizedOn)
   256  }