Skip to content

Commit 4821dcc

Browse files
authored
Enrich metadata of File with tags (#419)
* feat: add tags to files * fix: tests * test: add test to load file with tags * fix: add tags to FileDVO * fix: add tags to getFilesQuery * test: add test for custom query * chore: make tags optional in DTO and DVO
1 parent cabb8dd commit 4821dcc

File tree

13 files changed

+123
-3
lines changed

13 files changed

+123
-3
lines changed

packages/runtime/src/dataViews/transport/FileDVO.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IdentityDVO } from "./IdentityDVO";
44
export interface FileDVO extends DataViewObject {
55
type: "FileDVO";
66
filename: string;
7+
tags?: string[];
78
filesize: number;
89
createdAt: string;
910
createdBy: IdentityDVO;

packages/runtime/src/types/transport/FileDTO.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface FileDTO {
22
id: string;
33
filename: string;
4+
tags?: string[];
45
filesize: number;
56
createdAt: string;
67
createdBy: string;

packages/runtime/src/useCases/common/Schemas.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20436,6 +20436,19 @@ export const GetFilesRequest: any = {
2043620436
}
2043720437
}
2043820438
]
20439+
},
20440+
"tags": {
20441+
"anyOf": [
20442+
{
20443+
"type": "string"
20444+
},
20445+
{
20446+
"type": "array",
20447+
"items": {
20448+
"type": "string"
20449+
}
20450+
}
20451+
]
2043920452
}
2044020453
},
2044120454
"additionalProperties": false
@@ -20548,6 +20561,12 @@ export const UploadOwnFileRequest: any = {
2054820561
},
2054920562
"description": {
2055020563
"type": "string"
20564+
},
20565+
"tags": {
20566+
"type": "array",
20567+
"items": {
20568+
"type": "string"
20569+
}
2055120570
}
2055220571
},
2055320572
"required": [
@@ -20587,6 +20606,12 @@ export const UploadOwnFileValidatableRequest: any = {
2058720606
"description": {
2058820607
"type": "string"
2058920608
},
20609+
"tags": {
20610+
"type": "array",
20611+
"items": {
20612+
"type": "string"
20613+
}
20614+
},
2059020615
"content": {
2059120616
"type": "object"
2059220617
}

packages/runtime/src/useCases/transport/files/FileMapper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class FileMapper {
2424
return {
2525
id: file.id.toString(),
2626
filename: file.cache.filename,
27+
tags: file.cache.tags,
2728
filesize: file.cache.filesize,
2829
createdAt: file.cache.createdAt.toString(),
2930
createdBy: file.cache.createdBy.toString(),

packages/runtime/src/useCases/transport/files/GetFiles.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface GetFilesQuery {
1818
mimetype?: string | string[];
1919
title?: string | string[];
2020
isOwn?: string | string[];
21+
tags?: string | string[];
2122
}
2223

2324
export interface GetFilesRequest {
@@ -43,6 +44,7 @@ export class GetFilesUseCase extends UseCase<GetFilesRequest, FileDTO[]> {
4344
[nameof<FileDTO>((c) => c.filesize)]: true,
4445
[nameof<FileDTO>((c) => c.mimetype)]: true,
4546
[nameof<FileDTO>((c) => c.title)]: true,
47+
[nameof<FileDTO>((c) => c.tags)]: true,
4648
[nameof<FileDTO>((c) => c.isOwn)]: true
4749
},
4850
alias: {
@@ -55,7 +57,23 @@ export class GetFilesUseCase extends UseCase<GetFilesRequest, FileDTO[]> {
5557
[nameof<FileDTO>((c) => c.filesize)]: `${nameof<File>((f) => f.cache)}.${nameof<CachedFile>((c) => c.filesize)}`,
5658
[nameof<FileDTO>((c) => c.mimetype)]: `${nameof<File>((f) => f.cache)}.${nameof<CachedFile>((c) => c.mimetype)}`,
5759
[nameof<FileDTO>((c) => c.title)]: `${nameof<File>((f) => f.cache)}.${nameof<CachedFile>((c) => c.title)}`,
60+
[nameof<FileDTO>((c) => c.tags)]: `${nameof<File>((f) => f.cache)}.${nameof<CachedFile>((c) => c.tags)}`,
5861
[nameof<FileDTO>((c) => c.isOwn)]: nameof<File>((f) => f.isOwn)
62+
},
63+
custom: {
64+
// content.tags
65+
[`${nameof<FileDTO>((x) => x.tags)}`]: (query: any, input: string | string[]) => {
66+
if (typeof input === "string") {
67+
query[`${nameof<FileDTO>((x) => x.tags)}`] = { $contains: input };
68+
return;
69+
}
70+
const allowedTags = [];
71+
for (const tag of input) {
72+
const tagQuery = { [`${nameof<FileDTO>((x) => x.tags)}`]: { $contains: tag } };
73+
allowedTags.push(tagQuery);
74+
}
75+
query["$or"] = allowedTags;
76+
}
5977
}
6078
});
6179

packages/runtime/src/useCases/transport/files/UploadOwnFile.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface UploadOwnFileRequest {
1515
expiresAt?: ISO8601DateTimeString;
1616
title?: string;
1717
description?: string;
18+
tags?: string[];
1819
}
1920

2021
export interface UploadOwnFileValidatableRequest extends Omit<UploadOwnFileRequest, "content"> {
@@ -74,7 +75,8 @@ export class UploadOwnFileUseCase extends UseCase<UploadOwnFileRequest, FileDTO>
7475
description: request.description,
7576
filename: request.filename,
7677
mimetype: request.mimetype,
77-
expiresAt: CoreDate.from(request.expiresAt ?? "9999-12-31T00:00:00.000Z")
78+
expiresAt: CoreDate.from(request.expiresAt ?? "9999-12-31T00:00:00.000Z"),
79+
tags: request.tags
7880
});
7981

8082
await this.accountController.syncDatawallet();

packages/runtime/test/transport/files.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ describe("File upload", () => {
112112
expect(CoreDate.from(file.expiresAt).isSame(defaultDate)).toBe(true);
113113
});
114114

115+
test("can upload a file with tags", async () => {
116+
const response = await transportServices1.files.uploadOwnFile(await makeUploadRequest({ tags: ["tag1", "tag2"] }));
117+
expect(response).toBeSuccessful();
118+
119+
expect(response.value.tags).toStrictEqual(["tag1", "tag2"]);
120+
});
121+
115122
test("cannot upload a file with expiry date in the past", async () => {
116123
const response = await transportServices1.files.uploadOwnFile(await makeUploadRequest({ expiresAt: "1970" }));
117124
expect(response).toBeAnError("'expiresAt' must be in the future", "error.runtime.validation.invalidPropertyValue");
@@ -136,6 +143,39 @@ describe("Get file", () => {
136143
expect(response.value).toMatchObject(file);
137144
});
138145

146+
test("can get file by tags", async () => {
147+
const uploadFileResult = await transportServices1.files.uploadOwnFile(
148+
await makeUploadRequest({
149+
tags: ["aTag", "anotherTag"]
150+
})
151+
);
152+
const file = uploadFileResult.value;
153+
154+
const uploadFileResult2 = await transportServices1.files.uploadOwnFile(
155+
await makeUploadRequest({
156+
tags: ["aThirdTag", "aFourthTag"]
157+
})
158+
);
159+
const file2 = uploadFileResult2.value;
160+
161+
const getResult = await transportServices1.files.getFiles({ query: { tags: ["aTag"] } });
162+
163+
expect(getResult).toBeSuccessful();
164+
expect(getResult.value[0].id).toStrictEqual(file.id);
165+
166+
const getResult2 = await transportServices1.files.getFiles({ query: { tags: ["aTag", "anotherTag"] } });
167+
168+
expect(getResult2).toBeSuccessful();
169+
expect(getResult2.value[0].id).toStrictEqual(file.id);
170+
171+
const getResult3 = await transportServices1.files.getFiles({ query: { tags: ["aTag", "aThirdTag"] } });
172+
173+
expect(getResult3).toBeSuccessful();
174+
const result3Ids = getResult3.value.map((file) => file.id);
175+
expect(result3Ids).toContain(file.id);
176+
expect(result3Ids).toContain(file2.id);
177+
});
178+
139179
test("accessing not existing file id causes an error", async () => {
140180
const response = await transportServices1.files.getFile({ id: UNKNOWN_FILE_ID });
141181
expect(response).toBeAnError("File not found. Make sure the ID exists and the record is not expired.", "error.runtime.recordNotFound");
@@ -295,6 +335,18 @@ describe("Load peer file with token reference", () => {
295335
expect(response.value).toContainEqual({ ...file, isOwn: false });
296336
});
297337

338+
test("should load a peer file with its tags", async () => {
339+
const uploadOwnFileResult = await transportServices1.files.uploadOwnFile(
340+
await makeUploadRequest({
341+
tags: ["tag1", "tag2"]
342+
})
343+
);
344+
const token = (await transportServices1.files.createTokenForFile({ fileId: uploadOwnFileResult.value.id })).value;
345+
const loadFileResult = await transportServices2.files.getOrLoadFile({ reference: token.truncatedReference });
346+
347+
expect(loadFileResult.value.tags).toStrictEqual(["tag1", "tag2"]);
348+
});
349+
298350
test("cannot pass token id as truncated token reference", async () => {
299351
const file = await uploadFile(transportServices1);
300352
const token = (await transportServices1.files.createTokenForFile({ fileId: file.id })).value;

packages/transport/src/modules/files/FileController.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ export class FileController extends TransportController {
192192
plaintextHash: plaintextHash,
193193
secretKey: fileDownloadSecretKey,
194194
filemodified: input.filemodified,
195-
mimetype: input.mimetype
195+
mimetype: input.mimetype,
196+
tags: input.tags
196197
});
197198

198199
const serializedMetadata = metadata.serialize();
@@ -218,6 +219,7 @@ export class FileController extends TransportController {
218219
title: input.title,
219220
description: input.description,
220221
filename: input.filename,
222+
tags: input.tags,
221223
filesize: fileSize,
222224
filemodified: input.filemodified,
223225
cipherKey: fileDownloadSecretKey,

packages/transport/src/modules/files/local/CachedFile.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { FileMetadata } from "../transmission/FileMetadata";
88
export interface ICachedFile extends ISerializable {
99
title?: string;
1010
filename: string;
11+
tags?: string[];
1112
filesize: number;
1213
filemodified?: CoreDate;
1314
mimetype: string;
@@ -37,6 +38,10 @@ export class CachedFile extends Serializable implements ICachedFile {
3738
@serialize()
3839
public filename: string;
3940

41+
@validate({ nullable: true })
42+
@serialize({ type: String })
43+
public tags?: string[];
44+
4045
@validate()
4146
@serialize()
4247
public filesize: number;
@@ -112,6 +117,7 @@ export class CachedFile extends Serializable implements ICachedFile {
112117
cipherKey: metadata.secretKey,
113118
filemodified: metadata.filemodified,
114119
filename: metadata.filename,
120+
tags: metadata.tags,
115121
filesize: metadata.filesize,
116122
plaintextHash: metadata.plaintextHash,
117123
deletedAt: backboneResponse.deletedAt ? CoreDate.from(backboneResponse.deletedAt) : undefined,

packages/transport/src/modules/files/local/SendFileParameters.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface ISendFileParameters extends ISerializable {
1010
expiresAt: ICoreDate;
1111
filemodified?: ICoreDate;
1212
buffer: ICoreBuffer;
13+
tags?: string[];
1314
}
1415

1516
@type("SendFileParameters")
@@ -42,6 +43,10 @@ export class SendFileParameters extends Serializable implements ISendFileParamet
4243
@serialize()
4344
public buffer: CoreBuffer;
4445

46+
@validate({ nullable: true })
47+
@serialize({ type: String })
48+
public tags?: string[];
49+
4550
public static from(value: ISendFileParameters): SendFileParameters {
4651
return this.fromAny(value);
4752
}

0 commit comments

Comments
 (0)