Skip to content

Commit 06ca1b5

Browse files
committed
feat: track trash actors in cozyMetadata
1 parent aa88b8a commit 06ca1b5

File tree

11 files changed

+556
-12
lines changed

11 files changed

+556
-12
lines changed

docs/files.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,12 @@ restored. Or, after some time, it will be removed from the trash and permanently
17581758
destroyed.
17591759

17601760
The file `trashed` attribute will be set to true.
1761+
When a file or folder is moved to the trash, `cozyMetadata` also records:
1762+
1763+
- `trashedAt`: the server timestamp of the trash action
1764+
- `trashedBy`: the request actor that triggered the trash action, with:
1765+
- `kind`: `member` or `anonymous-share`
1766+
- `displayName` and `domain` for `member`
17611767

17621768
### GET /files/trash
17631769

@@ -1814,6 +1820,12 @@ Content-Type: application/vnd.api+json
18141820
"createdByApp": "drive",
18151821
"createdOn": "https://cozy.example.com/",
18161822
"updatedAt": "2016-09-20T18:32:49Z",
1823+
"trashedAt": "2016-09-20T18:32:49Z",
1824+
"trashedBy": {
1825+
"kind": "member",
1826+
"displayName": "Alice",
1827+
"domain": "cozy.example.com"
1828+
},
18171829
"uploadedAt": "2016-09-20T18:32:49Z",
18181830
"uploadedOn": "https://cozy.example.com/",
18191831
"uploadedBy": {
@@ -1852,6 +1864,12 @@ Content-Type: application/vnd.api+json
18521864
"createdByApp": "drive",
18531865
"createdOn": "https://cozy.example.com/",
18541866
"updatedAt": "2016-09-20T18:32:49Z",
1867+
"trashedAt": "2016-09-20T18:32:49Z",
1868+
"trashedBy": {
1869+
"kind": "member",
1870+
"displayName": "Alice",
1871+
"domain": "cozy.example.com"
1872+
},
18551873
"uploadedAt": "2016-09-20T18:32:49Z",
18561874
"uploadedOn": "https://cozy.example.com/",
18571875
"uploadedBy": {

docs/shared-drives.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,13 @@ drive.
769769
#### POST /sharings/drives/trash/:file-id
770770
#### DELETE /sharings/drives/trash/:file-id
771771

772+
Trash operations keep the same file metadata shape as `/files`: when an item is
773+
moved to the trash, `cozyMetadata.trashedAt` and `cozyMetadata.trashedBy` are
774+
exposed on both the caller side and the replicated shared-drive copies. On
775+
shared-drive requests, `trashedBy.kind` is `member` and
776+
`trashedBy.displayName` / `trashedBy.domain` are derived from the sharing
777+
member linked to the `DriveToken`.
778+
772779
## Share-by-link permissions
773780

774781
The following routes manage share-by-link permissions scoped to files inside a

model/sharing/files.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,11 @@ func copySafeFieldsToDir(target map[string]interface{}, dir *vfs.DirDoc) {
909909
dir.CozyMetadata.UpdatedAt = at
910910
}
911911
}
912+
if trashed, ok := meta["trashedAt"].(string); ok {
913+
if at, err := time.Parse(time.RFC3339Nano, trashed); err == nil {
914+
dir.CozyMetadata.TrashedAt = &at
915+
}
916+
}
912917
if updates, ok := meta["updatedByApps"].([]map[string]interface{}); ok {
913918
for _, update := range updates {
914919
if slug, ok := update["slug"].(string); ok {
@@ -928,6 +933,21 @@ func copySafeFieldsToDir(target map[string]interface{}, dir *vfs.DirDoc) {
928933
}
929934
}
930935
}
936+
if trashedBy, ok := meta["trashedBy"].(map[string]interface{}); ok {
937+
entry := &vfs.TrashedByEntry{}
938+
if kind, ok := trashedBy["kind"].(string); ok {
939+
entry.Kind = kind
940+
}
941+
if displayName, ok := trashedBy["displayName"].(string); ok {
942+
entry.DisplayName = displayName
943+
}
944+
if domain, ok := trashedBy["domain"].(string); ok {
945+
entry.Domain = domain
946+
} else if legacyInstance, ok := trashedBy["instance"].(string); ok {
947+
entry.Domain = legacyInstance
948+
}
949+
dir.CozyMetadata.TrashedBy = entry
950+
}
931951

932952
// No upload* for directories
933953
if account, ok := meta["sourceAccount"].(string); ok {

model/vfs/cozy_metadata.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ type UploadedByEntry struct {
1818
Client map[string]string `json:"oauthClient,omitempty"`
1919
}
2020

21+
const (
22+
// TrashedByKindMember identifies a concrete authenticated/member actor.
23+
TrashedByKindMember = "member"
24+
// TrashedByKindAnonymousShare identifies anonymous/public share access.
25+
TrashedByKindAnonymousShare = "anonymous-share"
26+
)
27+
28+
// TrashedByEntry identifies who sent a file or folder to the trash.
29+
type TrashedByEntry struct {
30+
Kind string `json:"kind,omitempty"`
31+
DisplayName string `json:"displayName,omitempty"`
32+
Domain string `json:"domain,omitempty"`
33+
}
34+
2135
// FilesCozyMetadata is an extended version of cozyMetadata with some specific fields.
2236
type FilesCozyMetadata struct {
2337
metadata.CozyMetadata
@@ -29,6 +43,10 @@ type FilesCozyMetadata struct {
2943
UploadedBy *UploadedByEntry `json:"uploadedBy,omitempty"`
3044
// Instance URL where the content has been changed the last time
3145
UploadedOn string `json:"uploadedOn,omitempty"`
46+
// Date of the last trash action
47+
TrashedAt *time.Time `json:"trashedAt,omitempty"`
48+
// Information about who sent the file or folder to the trash
49+
TrashedBy *TrashedByEntry `json:"trashedBy,omitempty"`
3250
}
3351

3452
// NewCozyMetadata initializes a new FilesCozyMetadata struct
@@ -65,6 +83,17 @@ func (fcm *FilesCozyMetadata) Clone() *FilesCozyMetadata {
6583
at := *fcm.UploadedAt
6684
cloned.UploadedAt = &at
6785
}
86+
if fcm.TrashedAt != nil {
87+
at := *fcm.TrashedAt
88+
cloned.TrashedAt = &at
89+
}
90+
if fcm.TrashedBy != nil {
91+
cloned.TrashedBy = &TrashedByEntry{
92+
Kind: fcm.TrashedBy.Kind,
93+
DisplayName: fcm.TrashedBy.DisplayName,
94+
Domain: fcm.TrashedBy.Domain,
95+
}
96+
}
6897
return &cloned
6998
}
7099

@@ -149,6 +178,22 @@ func (fcm *FilesCozyMetadata) ToJSONDoc() map[string]interface{} {
149178
if fcm.UploadedOn != "" {
150179
doc["uploadedOn"] = fcm.UploadedOn
151180
}
181+
if fcm.TrashedAt != nil {
182+
doc["trashedAt"] = *fcm.TrashedAt
183+
}
184+
if fcm.TrashedBy != nil {
185+
trashed := make(map[string]interface{})
186+
if fcm.TrashedBy.Kind != "" {
187+
trashed["kind"] = fcm.TrashedBy.Kind
188+
}
189+
if fcm.TrashedBy.DisplayName != "" {
190+
trashed["displayName"] = fcm.TrashedBy.DisplayName
191+
}
192+
if fcm.TrashedBy.Domain != "" {
193+
trashed["domain"] = fcm.TrashedBy.Domain
194+
}
195+
doc["trashedBy"] = trashed
196+
}
152197
if fcm.SourceAccount != "" {
153198
doc["sourceAccount"] = fcm.SourceAccount
154199
}

model/vfs/cozy_metadata_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,33 @@ func TestUpdatedByApp(t *testing.T) {
8989
assert.Equal(t, "alice.cozy.localhost", fcm.UpdatedByApps[2].Instance)
9090
assert.Equal(t, entry.Date, fcm.UpdatedByApps[2].Date)
9191
}
92+
93+
func TestFilesCozyMetadataCloneAndToJSONDoc(t *testing.T) {
94+
trashedAt := time.Now().UTC().Round(0)
95+
fcm := NewCozyMetadata("alice.cozy.localhost")
96+
fcm.TrashedAt = &trashedAt
97+
fcm.TrashedBy = &TrashedByEntry{
98+
Kind: TrashedByKindMember,
99+
DisplayName: "Alice",
100+
Domain: "alice.cozy.localhost",
101+
}
102+
103+
cloned := fcm.Clone()
104+
if assert.NotNil(t, cloned.TrashedAt) {
105+
assert.Equal(t, trashedAt, *cloned.TrashedAt)
106+
}
107+
if assert.NotNil(t, cloned.TrashedBy) {
108+
assert.Equal(t, fcm.TrashedBy, cloned.TrashedBy)
109+
cloned.TrashedBy.DisplayName = "Bob"
110+
assert.Equal(t, "Alice", fcm.TrashedBy.DisplayName)
111+
}
112+
113+
doc := fcm.ToJSONDoc()
114+
assert.Equal(t, trashedAt, doc["trashedAt"])
115+
trashedBy, ok := doc["trashedBy"].(map[string]interface{})
116+
if assert.True(t, ok) {
117+
assert.Equal(t, TrashedByKindMember, trashedBy["kind"])
118+
assert.Equal(t, "Alice", trashedBy["displayName"])
119+
assert.Equal(t, "alice.cozy.localhost", trashedBy["domain"])
120+
}
121+
}

web/files/files.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -731,10 +731,10 @@ func applyPatch(c echo.Context, fs vfs.VFS, patch *docPatch) (err error) {
731731
}
732732
} else if patch.Trash {
733733
if dir != nil {
734-
UpdateDirCozyMetadata(c, dir)
734+
UpdateDirTrashCozyMetadata(c, dir)
735735
dir, err = vfs.TrashDir(fs, dir)
736736
} else {
737-
UpdateFileCozyMetadata(c, file, false)
737+
UpdateFileTrashCozyMetadata(c, file)
738738
file, err = vfs.TrashFile(fs, file)
739739
}
740740
} else {
@@ -783,10 +783,10 @@ func applyPatches(c echo.Context, fs vfs.VFS, patches []*docPatch) (errors []*js
783783
}
784784
} else if patch.Trash {
785785
if dir != nil {
786-
UpdateDirCozyMetadata(c, dir)
786+
UpdateDirTrashCozyMetadata(c, dir)
787787
_, errp = vfs.TrashDir(fs, dir)
788788
} else if file != nil {
789-
UpdateFileCozyMetadata(c, file, false)
789+
UpdateFileTrashCozyMetadata(c, file)
790790
_, errp = vfs.TrashFile(fs, file)
791791
}
792792
} else if dir != nil {
@@ -1446,15 +1446,15 @@ func Trash(c echo.Context, sharedDrive *sharing.Sharing) error {
14461446
ensureCleanOldTrashedTrigger(instance)
14471447

14481448
if dir != nil {
1449-
UpdateDirCozyMetadata(c, dir)
1449+
UpdateDirTrashCozyMetadata(c, dir)
14501450
doc, errt := vfs.TrashDir(instance.VFS(), dir)
14511451
if errt != nil {
14521452
return WrapVfsError(errt)
14531453
}
14541454
return DirData(c, http.StatusOK, doc, sharedDrive)
14551455
}
14561456

1457-
UpdateFileCozyMetadata(c, file, false)
1457+
UpdateFileTrashCozyMetadata(c, file)
14581458
doc, errt := vfs.TrashFile(instance.VFS(), file)
14591459
if errt != nil {
14601460
return WrapVfsError(errt)
@@ -2489,6 +2489,33 @@ func UpdateFileCozyMetadata(c echo.Context, file *vfs.FileDoc, setUploadFields b
24892489
}
24902490
}
24912491

2492+
func UpdateDirTrashCozyMetadata(c echo.Context, dir *vfs.DirDoc) {
2493+
UpdateDirCozyMetadata(c, dir)
2494+
setTrashCozyMetadata(c, dir.CozyMetadata)
2495+
}
2496+
2497+
func UpdateFileTrashCozyMetadata(c echo.Context, file *vfs.FileDoc) {
2498+
UpdateFileCozyMetadata(c, file, false)
2499+
setTrashCozyMetadata(c, file.CozyMetadata)
2500+
}
2501+
2502+
func setTrashCozyMetadata(c echo.Context, fcm *vfs.FilesCozyMetadata) {
2503+
if fcm == nil {
2504+
return
2505+
}
2506+
trashedAt := fcm.UpdatedAt
2507+
fcm.TrashedAt = &trashedAt
2508+
if actor, ok := middlewares.GetActor(c); ok {
2509+
fcm.TrashedBy = &vfs.TrashedByEntry{
2510+
Kind: actor.Kind,
2511+
DisplayName: actor.DisplayName,
2512+
Domain: actor.Domain,
2513+
}
2514+
return
2515+
}
2516+
fcm.TrashedBy = nil
2517+
}
2518+
24922519
// CozyMetadataFromClaims returns a FilesCozyMetadata struct, with the app
24932520
// fields filled with information from the permission claims.
24942521
func CozyMetadataFromClaims(c echo.Context, setUploadFields bool) (*vfs.FilesCozyMetadata, string) {

0 commit comments

Comments
 (0)