@volontariapp/domain-storage 0.7.0 → 0.8.0-snap-259dd24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/enums/attachment-refusal-reason.enum.d.ts +7 -0
- package/dist/enums/attachment-refusal-reason.enum.d.ts.map +1 -0
- package/dist/enums/attachment-refusal-reason.enum.js +8 -0
- package/dist/enums/attachment-refusal-reason.enum.js.map +1 -0
- package/dist/enums/index.d.ts +1 -0
- package/dist/enums/index.d.ts.map +1 -1
- package/dist/enums/index.js +1 -0
- package/dist/enums/index.js.map +1 -1
- package/dist/exceptions/file-attachment-refused.exception.d.ts +9 -0
- package/dist/exceptions/file-attachment-refused.exception.d.ts.map +1 -0
- package/dist/exceptions/file-attachment-refused.exception.js +9 -0
- package/dist/exceptions/file-attachment-refused.exception.js.map +1 -0
- package/dist/exceptions/index.d.ts +2 -0
- package/dist/exceptions/index.d.ts.map +1 -1
- package/dist/exceptions/index.js +2 -0
- package/dist/exceptions/index.js.map +1 -1
- package/dist/exceptions/too-many-files.exception.d.ts +6 -0
- package/dist/exceptions/too-many-files.exception.d.ts.map +1 -0
- package/dist/exceptions/too-many-files.exception.js +7 -0
- package/dist/exceptions/too-many-files.exception.js.map +1 -0
- package/dist/policies/attachment-validation.rule.d.ts +28 -0
- package/dist/policies/attachment-validation.rule.d.ts.map +1 -0
- package/dist/policies/attachment-validation.rule.js +40 -0
- package/dist/policies/attachment-validation.rule.js.map +1 -0
- package/dist/policies/index.d.ts +1 -0
- package/dist/policies/index.d.ts.map +1 -1
- package/dist/policies/index.js +1 -0
- package/dist/policies/index.js.map +1 -1
- package/dist/repositories/file-repository.types.d.ts +15 -0
- package/dist/repositories/file-repository.types.d.ts.map +1 -1
- package/dist/repositories/postgres-file.repository.d.ts +2 -1
- package/dist/repositories/postgres-file.repository.d.ts.map +1 -1
- package/dist/repositories/postgres-file.repository.js +67 -0
- package/dist/repositories/postgres-file.repository.js.map +1 -1
- package/dist/test/attachment-exceptions.unit.spec.d.ts +2 -0
- package/dist/test/attachment-exceptions.unit.spec.d.ts.map +1 -0
- package/dist/test/attachment-exceptions.unit.spec.js +28 -0
- package/dist/test/attachment-exceptions.unit.spec.js.map +1 -0
- package/dist/test/attachment-validation.rule.unit.spec.d.ts +2 -0
- package/dist/test/attachment-validation.rule.unit.spec.d.ts.map +1 -0
- package/dist/test/attachment-validation.rule.unit.spec.js +83 -0
- package/dist/test/attachment-validation.rule.unit.spec.js.map +1 -0
- package/dist/test/factories/attachment.factory.d.ts +4 -0
- package/dist/test/factories/attachment.factory.d.ts.map +1 -0
- package/dist/test/factories/attachment.factory.js +19 -0
- package/dist/test/factories/attachment.factory.js.map +1 -0
- package/dist/test/integration/postgres-file.repository.reserve.int.spec.d.ts +2 -0
- package/dist/test/integration/postgres-file.repository.reserve.int.spec.d.ts.map +1 -0
- package/dist/test/integration/postgres-file.repository.reserve.int.spec.js +205 -0
- package/dist/test/integration/postgres-file.repository.reserve.int.spec.js.map +1 -0
- package/dist/test/mocks/file-update-failure.mock.d.ts +2 -0
- package/dist/test/mocks/file-update-failure.mock.d.ts.map +1 -0
- package/dist/test/mocks/file-update-failure.mock.js +17 -0
- package/dist/test/mocks/file-update-failure.mock.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`5cf1c51`](https://github.com/Volontariapp/npm-packages/commit/5cf1c51503e441ac7f232b9ab96d7df33470632a) Thanks [@VictorAgahi](https://github.com/VictorAgahi)! - Add the shared attachment validation rule (`classifyFileForAttachment`) and `PostgresFileRepository.reserve`, the synchronous all-or-nothing reservation of `ConfirmFileAttachment`, with the `FileAttachmentRefusedException` and `TooManyFilesException` domain errors.
|
|
8
|
+
|
|
3
9
|
## 0.7.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare enum AttachmentRefusalReason {
|
|
2
|
+
ENTITY_TYPE_MISMATCH = "ENTITY_TYPE_MISMATCH",
|
|
3
|
+
SCAN_STATUS_NOT_ACCEPTED = "SCAN_STATUS_NOT_ACCEPTED",
|
|
4
|
+
ATTACHED_TO_ANOTHER_ENTITY = "ATTACHED_TO_ANOTHER_ENTITY",
|
|
5
|
+
FILE_RELEASED = "FILE_RELEASED"
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=attachment-refusal-reason.enum.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-refusal-reason.enum.d.ts","sourceRoot":"","sources":["../../src/enums/attachment-refusal-reason.enum.ts"],"names":[],"mappings":"AACA,oBAAY,uBAAuB;IAEjC,oBAAoB,yBAAyB;IAE7C,wBAAwB,6BAA6B;IAErD,0BAA0B,+BAA+B;IAEzD,aAAa,kBAAkB;CAChC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export var AttachmentRefusalReason;
|
|
2
|
+
(function (AttachmentRefusalReason) {
|
|
3
|
+
AttachmentRefusalReason["ENTITY_TYPE_MISMATCH"] = "ENTITY_TYPE_MISMATCH";
|
|
4
|
+
AttachmentRefusalReason["SCAN_STATUS_NOT_ACCEPTED"] = "SCAN_STATUS_NOT_ACCEPTED";
|
|
5
|
+
AttachmentRefusalReason["ATTACHED_TO_ANOTHER_ENTITY"] = "ATTACHED_TO_ANOTHER_ENTITY";
|
|
6
|
+
AttachmentRefusalReason["FILE_RELEASED"] = "FILE_RELEASED";
|
|
7
|
+
})(AttachmentRefusalReason || (AttachmentRefusalReason = {}));
|
|
8
|
+
//# sourceMappingURL=attachment-refusal-reason.enum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-refusal-reason.enum.js","sourceRoot":"","sources":["../../src/enums/attachment-refusal-reason.enum.ts"],"names":[],"mappings":"AACA,MAAM,CAAN,IAAY,uBASX;AATD,WAAY,uBAAuB;IAEjC,wEAA6C,CAAA;IAE7C,gFAAqD,CAAA;IAErD,oFAAyD,CAAA;IAEzD,0DAA+B,CAAA;AACjC,CAAC,EATW,uBAAuB,KAAvB,uBAAuB,QASlC"}
|
package/dist/enums/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/enums/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/enums/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qCAAqC,CAAC"}
|
package/dist/enums/index.js
CHANGED
package/dist/enums/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/enums/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/enums/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseApiError, GrpcStatus } from '@volontariapp/errors';
|
|
2
|
+
import type { AttachmentRefusalReason } from '../enums/attachment-refusal-reason.enum.js';
|
|
3
|
+
import type { EntityType } from '../enums/entity-type.enum.js';
|
|
4
|
+
export declare class FileAttachmentRefusedException extends BaseApiError {
|
|
5
|
+
readonly statusCode = 422;
|
|
6
|
+
readonly grpcCode = GrpcStatus.FAILED_PRECONDITION;
|
|
7
|
+
constructor(fileId: string, reason: AttachmentRefusalReason, entityType: EntityType, entityId: string);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=file-attachment-refused.exception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-attachment-refused.exception.d.ts","sourceRoot":"","sources":["../../src/exceptions/file-attachment-refused.exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAU/D,qBAAa,8BAA+B,SAAQ,YAAY;IAC9D,SAAgB,UAAU,OAAO;IACjC,SAAgB,QAAQ,kCAAkC;gBAGxD,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,EAC/B,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM;CAQnB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseApiError, GrpcStatus } from '@volontariapp/errors';
|
|
2
|
+
export class FileAttachmentRefusedException extends BaseApiError {
|
|
3
|
+
statusCode = 422;
|
|
4
|
+
grpcCode = GrpcStatus.FAILED_PRECONDITION;
|
|
5
|
+
constructor(fileId, reason, entityType, entityId) {
|
|
6
|
+
super(`File '${fileId}' cannot be attached to ${entityType} '${entityId}': ${reason}.`, 'FILE_ATTACHMENT_REFUSED', { fileId, reason, entityType, entityId });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=file-attachment-refused.exception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-attachment-refused.exception.js","sourceRoot":"","sources":["../../src/exceptions/file-attachment-refused.exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAYhE,MAAM,OAAO,8BAA+B,SAAQ,YAAY;IAC9C,UAAU,GAAG,GAAG,CAAC;IACjB,QAAQ,GAAG,UAAU,CAAC,mBAAmB,CAAC;IAE1D,YACE,MAAc,EACd,MAA+B,EAC/B,UAAsB,EACtB,QAAgB;QAEhB,KAAK,CACH,SAAS,MAAM,2BAA2B,UAAU,KAAK,QAAQ,MAAM,MAAM,GAAG,EAChF,yBAAyB,EACzB,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CACzC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -2,4 +2,6 @@ export * from './file-not-found.exception.js';
|
|
|
2
2
|
export * from './file-size-not-allowed.exception.js';
|
|
3
3
|
export * from './invalid-entity-type.exception.js';
|
|
4
4
|
export * from './invalid-file-extension.exception.js';
|
|
5
|
+
export * from './file-attachment-refused.exception.js';
|
|
6
|
+
export * from './too-many-files.exception.js';
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exceptions/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exceptions/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC"}
|
package/dist/exceptions/index.js
CHANGED
|
@@ -2,4 +2,6 @@ export * from './file-not-found.exception.js';
|
|
|
2
2
|
export * from './file-size-not-allowed.exception.js';
|
|
3
3
|
export * from './invalid-entity-type.exception.js';
|
|
4
4
|
export * from './invalid-file-extension.exception.js';
|
|
5
|
+
export * from './file-attachment-refused.exception.js';
|
|
6
|
+
export * from './too-many-files.exception.js';
|
|
5
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exceptions/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exceptions/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BadRequestError } from '@volontariapp/errors';
|
|
2
|
+
import type { EntityType } from '../enums/entity-type.enum.js';
|
|
3
|
+
export declare class TooManyFilesException extends BadRequestError {
|
|
4
|
+
constructor(entityType: EntityType, count: number, maxPerEntity: number);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=too-many-files.exception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"too-many-files.exception.d.ts","sourceRoot":"","sources":["../../src/exceptions/too-many-files.exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,qBAAa,qBAAsB,SAAQ,eAAe;gBAC5C,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAOxE"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BadRequestError } from '@volontariapp/errors';
|
|
2
|
+
export class TooManyFilesException extends BadRequestError {
|
|
3
|
+
constructor(entityType, count, maxPerEntity) {
|
|
4
|
+
super(`${String(count)} files exceed the limit of ${String(maxPerEntity)} for entity type '${entityType}'.`, 'TOO_MANY_FILES', { entityType, count, maxPerEntity });
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=too-many-files.exception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"too-many-files.exception.js","sourceRoot":"","sources":["../../src/exceptions/too-many-files.exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,MAAM,OAAO,qBAAsB,SAAQ,eAAe;IACxD,YAAY,UAAsB,EAAE,KAAa,EAAE,YAAoB;QACrE,KAAK,CACH,GAAG,MAAM,CAAC,KAAK,CAAC,8BAA8B,MAAM,CAAC,YAAY,CAAC,qBAAqB,UAAU,IAAI,EACrG,gBAAgB,EAChB,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,CACpC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AttachmentRefusalReason } from '../enums/attachment-refusal-reason.enum.js';
|
|
2
|
+
import type { EntityType } from '../enums/entity-type.enum.js';
|
|
3
|
+
import { FileStatus } from '../enums/file-status.enum.js';
|
|
4
|
+
import { ScanStatus } from '../enums/scan-status.enum.js';
|
|
5
|
+
export interface AttachmentCandidate {
|
|
6
|
+
ownerId: string;
|
|
7
|
+
entityType: EntityType;
|
|
8
|
+
entityId: string | null;
|
|
9
|
+
status: FileStatus;
|
|
10
|
+
scanStatus: ScanStatus;
|
|
11
|
+
}
|
|
12
|
+
export interface AttachmentTarget {
|
|
13
|
+
ownerId: string;
|
|
14
|
+
entityType: EntityType;
|
|
15
|
+
entityId: string;
|
|
16
|
+
}
|
|
17
|
+
export type AttachmentVerdict = {
|
|
18
|
+
readonly kind: 'RESERVE';
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: 'ALREADY_HELD';
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'NOT_FOUND';
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'REFUSED';
|
|
25
|
+
readonly reason: AttachmentRefusalReason;
|
|
26
|
+
};
|
|
27
|
+
export declare function classifyFileForAttachment(file: AttachmentCandidate | null, target: AttachmentTarget): AttachmentVerdict;
|
|
28
|
+
//# sourceMappingURL=attachment-validation.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-validation.rule.d.ts","sourceRoot":"","sources":["../../src/policies/attachment-validation.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAI1D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;CACxB;AAGD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,iBAAiB,GAEzB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAE5B;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAEjC;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC;AA6B3E,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,mBAAmB,GAAG,IAAI,EAChC,MAAM,EAAE,gBAAgB,GACvB,iBAAiB,CA6BnB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AttachmentRefusalReason } from '../enums/attachment-refusal-reason.enum.js';
|
|
2
|
+
import { FileStatus } from '../enums/file-status.enum.js';
|
|
3
|
+
import { ScanStatus } from '../enums/scan-status.enum.js';
|
|
4
|
+
import { getValidationPolicy } from './get-validation-policy.js';
|
|
5
|
+
const RESERVE = { kind: 'RESERVE' };
|
|
6
|
+
const ALREADY_HELD = { kind: 'ALREADY_HELD' };
|
|
7
|
+
const NOT_FOUND = { kind: 'NOT_FOUND' };
|
|
8
|
+
const refused = (reason) => ({
|
|
9
|
+
kind: 'REFUSED',
|
|
10
|
+
reason,
|
|
11
|
+
});
|
|
12
|
+
const isSameEntity = (file, target) => file.entityType === target.entityType &&
|
|
13
|
+
file.entityId?.toLowerCase() === target.entityId.toLowerCase();
|
|
14
|
+
export function classifyFileForAttachment(file, target) {
|
|
15
|
+
if (file?.ownerId !== target.ownerId) {
|
|
16
|
+
return NOT_FOUND;
|
|
17
|
+
}
|
|
18
|
+
if (file.entityType !== target.entityType) {
|
|
19
|
+
return refused(AttachmentRefusalReason.ENTITY_TYPE_MISMATCH);
|
|
20
|
+
}
|
|
21
|
+
const { asyncAllowed } = getValidationPolicy(target.entityType);
|
|
22
|
+
const scanAccepted = file.scanStatus === ScanStatus.CLEAN ||
|
|
23
|
+
(file.scanStatus === ScanStatus.SCANNING && asyncAllowed);
|
|
24
|
+
if (!scanAccepted) {
|
|
25
|
+
return refused(AttachmentRefusalReason.SCAN_STATUS_NOT_ACCEPTED);
|
|
26
|
+
}
|
|
27
|
+
switch (file.status) {
|
|
28
|
+
case FileStatus.PENDING:
|
|
29
|
+
return RESERVE;
|
|
30
|
+
case FileStatus.RESERVED:
|
|
31
|
+
case FileStatus.ATTACHED:
|
|
32
|
+
return isSameEntity(file, target)
|
|
33
|
+
? ALREADY_HELD
|
|
34
|
+
: refused(AttachmentRefusalReason.ATTACHED_TO_ANOTHER_ENTITY);
|
|
35
|
+
case FileStatus.ORPHANED:
|
|
36
|
+
case FileStatus.DELETED:
|
|
37
|
+
return refused(AttachmentRefusalReason.FILE_RELEASED);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=attachment-validation.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-validation.rule.js","sourceRoot":"","sources":["../../src/policies/attachment-validation.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAErF,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AA2BjE,MAAM,OAAO,GAAsB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACvD,MAAM,YAAY,GAAsB,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;AACjE,MAAM,SAAS,GAAsB,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAE3D,MAAM,OAAO,GAAG,CAAC,MAA+B,EAAqB,EAAE,CAAC,CAAC;IACvE,IAAI,EAAE,SAAS;IACf,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,CAAC,IAAyB,EAAE,MAAwB,EAAW,EAAE,CACpF,IAAI,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU;IACrC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;AAgBjE,MAAM,UAAU,yBAAyB,CACvC,IAAgC,EAChC,MAAwB;IAExB,IAAI,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1C,OAAO,OAAO,CAAC,uBAAuB,CAAC,oBAAoB,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,EAAE,YAAY,EAAE,GAAG,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAChE,MAAM,YAAY,GAChB,IAAI,CAAC,UAAU,KAAK,UAAU,CAAC,KAAK;QACpC,CAAC,IAAI,CAAC,UAAU,KAAK,UAAU,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC;IAC5D,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,uBAAuB,CAAC,wBAAwB,CAAC,CAAC;IACnE,CAAC;IAED,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,OAAO,CAAC;QACjB,KAAK,UAAU,CAAC,QAAQ,CAAC;QACzB,KAAK,UAAU,CAAC,QAAQ;YACtB,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;gBAC/B,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,0BAA0B,CAAC,CAAC;QAClE,KAAK,UAAU,CAAC,QAAQ,CAAC;QACzB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,OAAO,CAAC,uBAAuB,CAAC,aAAa,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC"}
|
package/dist/policies/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/policies/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/policies/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC"}
|
package/dist/policies/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policies/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policies/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EntityType } from '../enums/entity-type.enum.js';
|
|
2
2
|
import type { FileStatus } from '../enums/file-status.enum.js';
|
|
3
|
+
import type { ScanStatus } from '../enums/scan-status.enum.js';
|
|
3
4
|
import type { RejectionReason } from '../enums/rejection-reason.enum.js';
|
|
4
5
|
import type { ValidationMode } from '../enums/validation-mode.enum.js';
|
|
5
6
|
export interface CreatePendingFileInput {
|
|
@@ -21,6 +22,20 @@ export interface ConfirmUploadResult {
|
|
|
21
22
|
id: string;
|
|
22
23
|
validationMode: ValidationMode;
|
|
23
24
|
}
|
|
25
|
+
export interface ReserveFilesInput {
|
|
26
|
+
fileIds: readonly string[];
|
|
27
|
+
entityType: EntityType;
|
|
28
|
+
entityId: string;
|
|
29
|
+
ownerId: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ReservationRow {
|
|
32
|
+
id: string;
|
|
33
|
+
owner_id: string;
|
|
34
|
+
entity_type: EntityType;
|
|
35
|
+
entity_id: string | null;
|
|
36
|
+
status: FileStatus;
|
|
37
|
+
scan_status: ScanStatus;
|
|
38
|
+
}
|
|
24
39
|
export interface CompleteScanInput {
|
|
25
40
|
fileId: string;
|
|
26
41
|
publicKey: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-repository.types.d.ts","sourceRoot":"","sources":["../../src/repositories/file-repository.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IAErC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;CACzB;AAGD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,EAAE,UAAU,CAAC;IAEnB,YAAY,EAAE,OAAO,CAAC;CACvB;AAGD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
|
1
|
+
{"version":3,"file":"file-repository.types.d.ts","sourceRoot":"","sources":["../../src/repositories/file-repository.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IAErC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAGD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;CACzB;AAGD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,EAAE,UAAU,CAAC;IAEnB,YAAY,EAAE,OAAO,CAAC;CACvB;AAGD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DataSource } from 'typeorm';
|
|
2
2
|
import { FileModel } from '../models/file.model.js';
|
|
3
|
-
import type { CompleteScanInput, ConfirmUploadInput, ConfirmUploadResult, CreatePendingFileInput, RejectScanInput, ScanTransitionResult } from './file-repository.types.js';
|
|
3
|
+
import type { CompleteScanInput, ConfirmUploadInput, ConfirmUploadResult, CreatePendingFileInput, RejectScanInput, ReserveFilesInput, ScanTransitionResult } from './file-repository.types.js';
|
|
4
4
|
export declare const RESET_UPLOAD_DELAY_MINUTES = 15;
|
|
5
5
|
export declare class PostgresFileRepository {
|
|
6
6
|
private readonly dataSource;
|
|
@@ -10,6 +10,7 @@ export declare class PostgresFileRepository {
|
|
|
10
10
|
confirmUpload(input: ConfirmUploadInput): Promise<ConfirmUploadResult | null>;
|
|
11
11
|
switchToAsync(fileId: string): Promise<boolean>;
|
|
12
12
|
resetToAwaitingUpload(fileId: string): Promise<boolean>;
|
|
13
|
+
reserve(input: ReserveFilesInput): Promise<FileModel[]>;
|
|
13
14
|
completeScan(input: CompleteScanInput): Promise<ScanTransitionResult | null>;
|
|
14
15
|
rejectScan(input: RejectScanInput): Promise<ScanTransitionResult | null>;
|
|
15
16
|
private finishScan;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-file.repository.d.ts","sourceRoot":"","sources":["../../src/repositories/postgres-file.repository.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"postgres-file.repository.d.ts","sourceRoot":"","sources":["../../src/repositories/postgres-file.repository.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,SAAS,CAAC;AAOzD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AASpD,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EAEf,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,4BAA4B,CAAC;AAGpC,eAAO,MAAM,0BAA0B,KAAK,CAAC;AA0B7C,qBAAa,sBAAsB;IAGrB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwD;gBAElD,UAAU,EAAE,UAAU;IAG7C,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,SAAS,CAAC;IAkBhE,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAsC7E,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyB/C,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BvD,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA2FvD,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAkB5E,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;YAiBhE,UAAU;YAmCV,gBAAgB;YAahB,YAAY;IAQ1B,OAAO,CAAC,aAAa;CAOtB"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { EventQueueModel, JobsOutboxModel } from '@volontariapp/database';
|
|
2
2
|
import { Logger } from '@volontariapp/logger';
|
|
3
|
+
import { In } from 'typeorm';
|
|
3
4
|
import { FileStatus } from '../enums/file-status.enum.js';
|
|
4
5
|
import { ScanStatus } from '../enums/scan-status.enum.js';
|
|
5
6
|
import { ValidationMode } from '../enums/validation-mode.enum.js';
|
|
7
|
+
import { FileAttachmentRefusedException } from '../exceptions/file-attachment-refused.exception.js';
|
|
8
|
+
import { FileNotFoundException } from '../exceptions/file-not-found.exception.js';
|
|
9
|
+
import { TooManyFilesException } from '../exceptions/too-many-files.exception.js';
|
|
6
10
|
import { FileModel } from '../models/file.model.js';
|
|
11
|
+
import { classifyFileForAttachment } from '../policies/attachment-validation.rule.js';
|
|
12
|
+
import { getValidationPolicy } from '../policies/get-validation-policy.js';
|
|
7
13
|
import { buildFileRejectedEvent, buildFileScannedEvent, buildScanFileJob, } from './file-outbox.builders.js';
|
|
8
14
|
export const RESET_UPLOAD_DELAY_MINUTES = 15;
|
|
9
15
|
export class PostgresFileRepository {
|
|
@@ -75,6 +81,67 @@ export class PostgresFileRepository {
|
|
|
75
81
|
this.logTransition('resetToAwaitingUpload', fileId, reset);
|
|
76
82
|
return reset;
|
|
77
83
|
}
|
|
84
|
+
async reserve(input) {
|
|
85
|
+
const fileIds = [...new Set(input.fileIds)];
|
|
86
|
+
const { maxPerEntity } = getValidationPolicy(input.entityType);
|
|
87
|
+
if (fileIds.length > maxPerEntity) {
|
|
88
|
+
throw new TooManyFilesException(input.entityType, fileIds.length, maxPerEntity);
|
|
89
|
+
}
|
|
90
|
+
if (fileIds.length === 0)
|
|
91
|
+
return [];
|
|
92
|
+
const label = `${input.entityType} ${input.entityId}`;
|
|
93
|
+
const outcome = await this.runInTransaction('reserve', label, async (manager) => {
|
|
94
|
+
const rows = await manager.query(`SELECT id, owner_id, entity_type, entity_id, status, scan_status
|
|
95
|
+
FROM files
|
|
96
|
+
WHERE id = ANY($1::uuid[])
|
|
97
|
+
ORDER BY id
|
|
98
|
+
FOR UPDATE`, [fileIds]);
|
|
99
|
+
const rowById = new Map(rows.map((row) => [row.id, row]));
|
|
100
|
+
const verdicts = fileIds.map((fileId) => {
|
|
101
|
+
const row = rowById.get(fileId);
|
|
102
|
+
return [
|
|
103
|
+
fileId,
|
|
104
|
+
classifyFileForAttachment(row
|
|
105
|
+
? {
|
|
106
|
+
ownerId: row.owner_id,
|
|
107
|
+
entityType: row.entity_type,
|
|
108
|
+
entityId: row.entity_id,
|
|
109
|
+
status: row.status,
|
|
110
|
+
scanStatus: row.scan_status,
|
|
111
|
+
}
|
|
112
|
+
: null, input),
|
|
113
|
+
];
|
|
114
|
+
});
|
|
115
|
+
const missing = verdicts.find(([, verdict]) => verdict.kind === 'NOT_FOUND');
|
|
116
|
+
if (missing)
|
|
117
|
+
return { failure: new FileNotFoundException(missing[0]) };
|
|
118
|
+
for (const [fileId, verdict] of verdicts) {
|
|
119
|
+
if (verdict.kind === 'REFUSED') {
|
|
120
|
+
return {
|
|
121
|
+
failure: new FileAttachmentRefusedException(fileId, verdict.reason, input.entityType, input.entityId),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const toReserve = verdicts
|
|
126
|
+
.filter(([, verdict]) => verdict.kind === 'RESERVE')
|
|
127
|
+
.map(([fileId]) => fileId);
|
|
128
|
+
if (toReserve.length > 0) {
|
|
129
|
+
await manager.query(`UPDATE files
|
|
130
|
+
SET status = $1, entity_id = $2, reserved_at = now(), updated_at = now()
|
|
131
|
+
WHERE id = ANY($3::uuid[]) AND status = $4`, [FileStatus.RESERVED, input.entityId, toReserve, FileStatus.PENDING]);
|
|
132
|
+
}
|
|
133
|
+
const files = await manager.find(FileModel, { where: { id: In(fileIds) } });
|
|
134
|
+
const fileById = new Map(files.map((file) => [file.id, file]));
|
|
135
|
+
return { files: fileIds.flatMap((fileId) => fileById.get(fileId) ?? []) };
|
|
136
|
+
});
|
|
137
|
+
if ('failure' in outcome) {
|
|
138
|
+
const { failure } = outcome;
|
|
139
|
+
this.logger.warn(`reserve refused for ${label}: ${failure.message}`);
|
|
140
|
+
throw failure;
|
|
141
|
+
}
|
|
142
|
+
this.logger.log(`reserve applied for ${label}: ${String(outcome.files.length)} file(s)`);
|
|
143
|
+
return outcome.files;
|
|
144
|
+
}
|
|
78
145
|
async completeScan(input) {
|
|
79
146
|
return this.finishScan({
|
|
80
147
|
assignments: `scan_status = $1, public_key = $2, scanned_at = now()`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-file.repository.js","sourceRoot":"","sources":["../../src/repositories/postgres-file.repository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"postgres-file.repository.js","sourceRoot":"","sources":["../../src/repositories/postgres-file.repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-exceptions.unit.spec.d.ts","sourceRoot":"","sources":["../../src/test/attachment-exceptions.unit.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { GrpcStatus } from '@volontariapp/errors';
|
|
3
|
+
import { AttachmentRefusalReason, EntityType, FileAttachmentRefusedException, TooManyFilesException, } from '../index.js';
|
|
4
|
+
describe('Attachment exceptions', () => {
|
|
5
|
+
it('FileAttachmentRefusedException should be a 422 carrying its reason', () => {
|
|
6
|
+
const error = new FileAttachmentRefusedException('file-1', AttachmentRefusalReason.FILE_RELEASED, EntityType.POST, 'post-1');
|
|
7
|
+
expect(error.statusCode).toBe(422);
|
|
8
|
+
expect(error.grpcCode).toBe(GrpcStatus.FAILED_PRECONDITION);
|
|
9
|
+
expect(error.code).toBe('FILE_ATTACHMENT_REFUSED');
|
|
10
|
+
expect(error.details).toEqual({
|
|
11
|
+
fileId: 'file-1',
|
|
12
|
+
reason: AttachmentRefusalReason.FILE_RELEASED,
|
|
13
|
+
entityType: EntityType.POST,
|
|
14
|
+
entityId: 'post-1',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it('TooManyFilesException should be a 400 carrying the limit', () => {
|
|
18
|
+
const error = new TooManyFilesException(EntityType.EVENT_COVER, 2, 1);
|
|
19
|
+
expect(error.statusCode).toBe(400);
|
|
20
|
+
expect(error.code).toBe('TOO_MANY_FILES');
|
|
21
|
+
expect(error.details).toEqual({
|
|
22
|
+
entityType: EntityType.EVENT_COVER,
|
|
23
|
+
count: 2,
|
|
24
|
+
maxPerEntity: 1,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=attachment-exceptions.unit.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-exceptions.unit.spec.js","sourceRoot":"","sources":["../../src/test/attachment-exceptions.unit.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,8BAA8B,EAC9B,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,KAAK,GAAG,IAAI,8BAA8B,CAC9C,QAAQ,EACR,uBAAuB,CAAC,aAAa,EACrC,UAAU,CAAC,IAAI,EACf,QAAQ,CACT,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;YAC5B,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,uBAAuB,CAAC,aAAa;YAC7C,UAAU,EAAE,UAAU,CAAC,IAAI;YAC3B,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,KAAK,GAAG,IAAI,qBAAqB,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAEtE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;YAC5B,UAAU,EAAE,UAAU,CAAC,WAAW;YAClC,KAAK,EAAE,CAAC;YACR,YAAY,EAAE,CAAC;SAChB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-validation.rule.unit.spec.d.ts","sourceRoot":"","sources":["../../src/test/attachment-validation.rule.unit.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { AttachmentRefusalReason, EntityType, FileStatus, InvalidEntityTypeException, ScanStatus, classifyFileForAttachment, } from '../index.js';
|
|
3
|
+
import { buildAttachmentCandidate, buildAttachmentTarget } from './factories/attachment.factory.js';
|
|
4
|
+
const refused = (reason) => ({ kind: 'REFUSED', reason });
|
|
5
|
+
describe('classifyFileForAttachment', () => {
|
|
6
|
+
it('should answer NOT_FOUND for an unknown file', () => {
|
|
7
|
+
expect(classifyFileForAttachment(null, buildAttachmentTarget())).toEqual({
|
|
8
|
+
kind: 'NOT_FOUND',
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
it('should answer NOT_FOUND for a file of another owner, whatever its state', () => {
|
|
12
|
+
const target = buildAttachmentTarget();
|
|
13
|
+
const other = buildAttachmentCandidate(target, {
|
|
14
|
+
ownerId: buildAttachmentTarget().ownerId,
|
|
15
|
+
entityType: EntityType.USER_AVATAR,
|
|
16
|
+
scanStatus: ScanStatus.REJECTED,
|
|
17
|
+
});
|
|
18
|
+
expect(classifyFileForAttachment(other, target)).toEqual({ kind: 'NOT_FOUND' });
|
|
19
|
+
});
|
|
20
|
+
it('should refuse a file declared for another entity type', () => {
|
|
21
|
+
const target = buildAttachmentTarget({ entityType: EntityType.POST });
|
|
22
|
+
const file = buildAttachmentCandidate(target, { entityType: EntityType.EVENT_COVER });
|
|
23
|
+
expect(classifyFileForAttachment(file, target)).toEqual(refused(AttachmentRefusalReason.ENTITY_TYPE_MISMATCH));
|
|
24
|
+
});
|
|
25
|
+
it.each([ScanStatus.AWAITING_UPLOAD, ScanStatus.REJECTED])('should refuse a %s file', (scanStatus) => {
|
|
26
|
+
const target = buildAttachmentTarget();
|
|
27
|
+
const file = buildAttachmentCandidate(target, { scanStatus });
|
|
28
|
+
expect(classifyFileForAttachment(file, target)).toEqual(refused(AttachmentRefusalReason.SCAN_STATUS_NOT_ACCEPTED));
|
|
29
|
+
});
|
|
30
|
+
it.each([EntityType.POST, EntityType.EVENT_COVER])('should reserve a SCANNING file for %s, which allows ASYNC', (entityType) => {
|
|
31
|
+
const target = buildAttachmentTarget({ entityType });
|
|
32
|
+
const file = buildAttachmentCandidate(target, { scanStatus: ScanStatus.SCANNING });
|
|
33
|
+
expect(classifyFileForAttachment(file, target)).toEqual({ kind: 'RESERVE' });
|
|
34
|
+
});
|
|
35
|
+
it.each([EntityType.USER_AVATAR, EntityType.BADGE_ICON])('should refuse a SCANNING file for %s, which does not allow ASYNC', (entityType) => {
|
|
36
|
+
const target = buildAttachmentTarget({ entityType });
|
|
37
|
+
const file = buildAttachmentCandidate(target, { scanStatus: ScanStatus.SCANNING });
|
|
38
|
+
expect(classifyFileForAttachment(file, target)).toEqual(refused(AttachmentRefusalReason.SCAN_STATUS_NOT_ACCEPTED));
|
|
39
|
+
});
|
|
40
|
+
it.each(Object.values(EntityType))('should reserve a PENDING CLEAN file for %s', (entityType) => {
|
|
41
|
+
const target = buildAttachmentTarget({ entityType });
|
|
42
|
+
expect(classifyFileForAttachment(buildAttachmentCandidate(target), target)).toEqual({
|
|
43
|
+
kind: 'RESERVE',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it.each([FileStatus.RESERVED, FileStatus.ATTACHED])('should recognize a %s file of the same entity as already held', (status) => {
|
|
47
|
+
const target = buildAttachmentTarget();
|
|
48
|
+
const file = buildAttachmentCandidate(target, {
|
|
49
|
+
status,
|
|
50
|
+
entityId: target.entityId.toUpperCase(),
|
|
51
|
+
});
|
|
52
|
+
expect(classifyFileForAttachment(file, target)).toEqual({ kind: 'ALREADY_HELD' });
|
|
53
|
+
});
|
|
54
|
+
it.each([FileStatus.RESERVED, FileStatus.ATTACHED])('should refuse a %s file held by another entity', (status) => {
|
|
55
|
+
const target = buildAttachmentTarget();
|
|
56
|
+
const file = buildAttachmentCandidate(target, {
|
|
57
|
+
status,
|
|
58
|
+
entityId: buildAttachmentTarget().entityId,
|
|
59
|
+
});
|
|
60
|
+
expect(classifyFileForAttachment(file, target)).toEqual(refused(AttachmentRefusalReason.ATTACHED_TO_ANOTHER_ENTITY));
|
|
61
|
+
});
|
|
62
|
+
it.each([FileStatus.ORPHANED, FileStatus.DELETED])('should refuse a %s file, even for its former entity', (status) => {
|
|
63
|
+
const target = buildAttachmentTarget();
|
|
64
|
+
const file = buildAttachmentCandidate(target, { status, entityId: target.entityId });
|
|
65
|
+
expect(classifyFileForAttachment(file, target)).toEqual(refused(AttachmentRefusalReason.FILE_RELEASED));
|
|
66
|
+
});
|
|
67
|
+
it('should check the scan status before the attachment status', () => {
|
|
68
|
+
const target = buildAttachmentTarget();
|
|
69
|
+
const file = buildAttachmentCandidate(target, {
|
|
70
|
+
status: FileStatus.ATTACHED,
|
|
71
|
+
entityId: target.entityId,
|
|
72
|
+
scanStatus: ScanStatus.REJECTED,
|
|
73
|
+
});
|
|
74
|
+
expect(classifyFileForAttachment(file, target)).toEqual(refused(AttachmentRefusalReason.SCAN_STATUS_NOT_ACCEPTED));
|
|
75
|
+
});
|
|
76
|
+
it('should raise InvalidEntityTypeException for an unknown entity type', () => {
|
|
77
|
+
const target = buildAttachmentTarget();
|
|
78
|
+
const file = buildAttachmentCandidate(target);
|
|
79
|
+
const unknown = buildAttachmentTarget({ ownerId: target.ownerId, entityType: 'NOPE' });
|
|
80
|
+
expect(() => classifyFileForAttachment({ ...file, entityType: unknown.entityType }, unknown)).toThrow(InvalidEntityTypeException);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=attachment-validation.rule.unit.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-validation.rule.unit.spec.js","sourceRoot":"","sources":["../../src/test/attachment-validation.rule.unit.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AttachmentCandidate, AttachmentTarget } from '../../policies/attachment-validation.rule.js';
|
|
2
|
+
export declare const buildAttachmentTarget: (overrides?: Partial<AttachmentTarget>) => AttachmentTarget;
|
|
3
|
+
export declare const buildAttachmentCandidate: (target: AttachmentTarget, overrides?: Partial<AttachmentCandidate>) => AttachmentCandidate;
|
|
4
|
+
//# sourceMappingURL=attachment.factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.factory.d.ts","sourceRoot":"","sources":["../../../src/test/factories/attachment.factory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,8CAA8C,CAAC;AAGtD,eAAO,MAAM,qBAAqB,eACrB,OAAO,CAAC,gBAAgB,CAAC,KACnC,gBAKD,CAAC;AAGH,eAAO,MAAM,wBAAwB,WAC3B,gBAAgB,cACb,OAAO,CAAC,mBAAmB,CAAC,KACtC,mBAOD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EntityType } from '../../enums/entity-type.enum.js';
|
|
2
|
+
import { FileStatus } from '../../enums/file-status.enum.js';
|
|
3
|
+
import { ScanStatus } from '../../enums/scan-status.enum.js';
|
|
4
|
+
import { FileId } from '../../value-objects/file-id.vo.js';
|
|
5
|
+
export const buildAttachmentTarget = (overrides = {}) => ({
|
|
6
|
+
ownerId: FileId.generate().getValue(),
|
|
7
|
+
entityType: EntityType.POST,
|
|
8
|
+
entityId: FileId.generate().getValue(),
|
|
9
|
+
...overrides,
|
|
10
|
+
});
|
|
11
|
+
export const buildAttachmentCandidate = (target, overrides = {}) => ({
|
|
12
|
+
ownerId: target.ownerId,
|
|
13
|
+
entityType: target.entityType,
|
|
14
|
+
entityId: null,
|
|
15
|
+
status: FileStatus.PENDING,
|
|
16
|
+
scanStatus: ScanStatus.CLEAN,
|
|
17
|
+
...overrides,
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=attachment.factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.factory.js","sourceRoot":"","sources":["../../../src/test/factories/attachment.factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAK7D,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,YAAuC,EAAE,EACvB,EAAE,CAAC,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACrC,UAAU,EAAE,UAAU,CAAC,IAAI;IAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtC,GAAG,SAAS;CACb,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,MAAwB,EACxB,YAA0C,EAAE,EACvB,EAAE,CAAC,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC,OAAO;IACvB,UAAU,EAAE,MAAM,CAAC,UAAU;IAC7B,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,UAAU,CAAC,OAAO;IAC1B,UAAU,EAAE,UAAU,CAAC,KAAK;IAC5B,GAAG,SAAS;CACb,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-file.repository.reserve.int.spec.d.ts","sourceRoot":"","sources":["../../../src/test/integration/postgres-file.repository.reserve.int.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest, } from '@jest/globals';
|
|
2
|
+
import { AttachmentRefusalReason, EntityType, FileAttachmentRefusedException, FileId, FileNotFoundException, FileStatus, ScanStatus, TooManyFilesException, } from '../../index.js';
|
|
3
|
+
import { PostgresFileRepository } from '../../repositories/index.js';
|
|
4
|
+
import { closeTestDb, initializeTestDb, testDataSource, truncateAll } from '../data-source.js';
|
|
5
|
+
import { insertFile, readEvents, readFile, readJobs } from '../helpers/file-db.helper.js';
|
|
6
|
+
import { mockFileUpdateFailure } from '../mocks/file-update-failure.mock.js';
|
|
7
|
+
const newId = () => FileId.generate().getValue();
|
|
8
|
+
describe('PostgresFileRepository.reserve() (integration)', () => {
|
|
9
|
+
let repository;
|
|
10
|
+
let restoreUpdates;
|
|
11
|
+
const ownerId = newId();
|
|
12
|
+
const entityId = newId();
|
|
13
|
+
const reserveInput = (fileIds, overrides = {}) => ({
|
|
14
|
+
fileIds,
|
|
15
|
+
entityType: EntityType.POST,
|
|
16
|
+
entityId,
|
|
17
|
+
ownerId,
|
|
18
|
+
...overrides,
|
|
19
|
+
});
|
|
20
|
+
const cleanFile = async (overrides = {}) => insertFile({ ownerId, scanStatus: ScanStatus.CLEAN, ...overrides });
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
await initializeTestDb();
|
|
23
|
+
repository = new PostgresFileRepository(testDataSource);
|
|
24
|
+
});
|
|
25
|
+
afterAll(async () => {
|
|
26
|
+
await closeTestDb();
|
|
27
|
+
});
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
await truncateAll();
|
|
30
|
+
});
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
jest.restoreAllMocks();
|
|
33
|
+
await restoreUpdates?.();
|
|
34
|
+
restoreUpdates = undefined;
|
|
35
|
+
});
|
|
36
|
+
it('reserves CLEAN and SCANNING files of a post, in input order, and writes no event', async () => {
|
|
37
|
+
const clean = await cleanFile();
|
|
38
|
+
const scanning = await cleanFile({ scanStatus: ScanStatus.SCANNING });
|
|
39
|
+
const reserved = await repository.reserve(reserveInput([scanning.id, clean.id]));
|
|
40
|
+
expect(reserved.map((file) => file.id)).toEqual([scanning.id, clean.id]);
|
|
41
|
+
for (const file of reserved) {
|
|
42
|
+
expect(file.status).toBe(FileStatus.RESERVED);
|
|
43
|
+
expect(file.entityId).toBe(entityId);
|
|
44
|
+
expect(file.reservedAt).toBeInstanceOf(Date);
|
|
45
|
+
}
|
|
46
|
+
expect(reserved[0]?.scanStatus).toBe(ScanStatus.SCANNING);
|
|
47
|
+
expect((await readFile(clean.id)).status).toBe(FileStatus.RESERVED);
|
|
48
|
+
expect(await readEvents()).toHaveLength(0);
|
|
49
|
+
expect(await readJobs()).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
it('ignores duplicated ids', async () => {
|
|
52
|
+
const file = await cleanFile();
|
|
53
|
+
const reserved = await repository.reserve(reserveInput([file.id, file.id, file.id]));
|
|
54
|
+
expect(reserved).toHaveLength(1);
|
|
55
|
+
});
|
|
56
|
+
it('does nothing and queries nothing for an empty list', async () => {
|
|
57
|
+
await expect(repository.reserve(reserveInput([]))).resolves.toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
it('refuses more distinct files than the policy allows, before touching anything', async () => {
|
|
60
|
+
const files = [await cleanFile(), await cleanFile()];
|
|
61
|
+
await expect(repository.reserve(reserveInput(files.map((file) => file.id), { entityType: EntityType.EVENT_COVER }))).rejects.toThrow(TooManyFilesException);
|
|
62
|
+
expect((await readFile(files[0]?.id ?? '')).status).toBe(FileStatus.PENDING);
|
|
63
|
+
});
|
|
64
|
+
it('accepts exactly the maximum of the policy', async () => {
|
|
65
|
+
const files = await Promise.all(Array.from({ length: 10 }, () => cleanFile()));
|
|
66
|
+
const reserved = await repository.reserve(reserveInput(files.map((file) => file.id)));
|
|
67
|
+
expect(reserved).toHaveLength(10);
|
|
68
|
+
});
|
|
69
|
+
it('replaces an avatar: a new file can be reserved while the old one is still ATTACHED', async () => {
|
|
70
|
+
const old = await cleanFile({
|
|
71
|
+
entityType: EntityType.USER_AVATAR,
|
|
72
|
+
status: FileStatus.ATTACHED,
|
|
73
|
+
entityId,
|
|
74
|
+
});
|
|
75
|
+
const next = await cleanFile({ entityType: EntityType.USER_AVATAR });
|
|
76
|
+
const reserved = await repository.reserve(reserveInput([next.id], { entityType: EntityType.USER_AVATAR }));
|
|
77
|
+
expect(reserved.map((file) => file.status)).toEqual([FileStatus.RESERVED]);
|
|
78
|
+
expect((await readFile(old.id)).status).toBe(FileStatus.ATTACHED);
|
|
79
|
+
});
|
|
80
|
+
it('answers NOT_FOUND for an unknown file', async () => {
|
|
81
|
+
await expect(repository.reserve(reserveInput([newId()]))).rejects.toThrow(FileNotFoundException);
|
|
82
|
+
});
|
|
83
|
+
it('answers NOT_FOUND for the file of another owner and does not touch it', async () => {
|
|
84
|
+
const foreign = await insertFile({ ownerId: newId(), scanStatus: ScanStatus.CLEAN });
|
|
85
|
+
await expect(repository.reserve(reserveInput([foreign.id]))).rejects.toThrow(FileNotFoundException);
|
|
86
|
+
expect((await readFile(foreign.id)).status).toBe(FileStatus.PENDING);
|
|
87
|
+
});
|
|
88
|
+
it('refuses a file declared for another entity type', async () => {
|
|
89
|
+
const file = await cleanFile({ entityType: EntityType.EVENT_COVER });
|
|
90
|
+
const error = await repository.reserve(reserveInput([file.id])).catch((e) => e);
|
|
91
|
+
expect(error).toBeInstanceOf(FileAttachmentRefusedException);
|
|
92
|
+
expect(error.details?.reason).toBe(AttachmentRefusalReason.ENTITY_TYPE_MISMATCH);
|
|
93
|
+
});
|
|
94
|
+
it.each([ScanStatus.AWAITING_UPLOAD, ScanStatus.REJECTED])('refuses a %s file', async (scanStatus) => {
|
|
95
|
+
const file = await cleanFile({ scanStatus });
|
|
96
|
+
await expect(repository.reserve(reserveInput([file.id]))).rejects.toThrow(FileAttachmentRefusedException);
|
|
97
|
+
expect((await readFile(file.id)).status).toBe(FileStatus.PENDING);
|
|
98
|
+
});
|
|
99
|
+
it('refuses a SCANNING avatar, which must be CLEAN', async () => {
|
|
100
|
+
const file = await cleanFile({
|
|
101
|
+
entityType: EntityType.USER_AVATAR,
|
|
102
|
+
scanStatus: ScanStatus.SCANNING,
|
|
103
|
+
});
|
|
104
|
+
await expect(repository.reserve(reserveInput([file.id], { entityType: EntityType.USER_AVATAR }))).rejects.toThrow(FileAttachmentRefusedException);
|
|
105
|
+
});
|
|
106
|
+
it('refuses a file already reserved or attached for another entity', async () => {
|
|
107
|
+
const reservedElsewhere = await cleanFile({
|
|
108
|
+
status: FileStatus.RESERVED,
|
|
109
|
+
entityId: newId(),
|
|
110
|
+
});
|
|
111
|
+
const attachedElsewhere = await cleanFile({
|
|
112
|
+
status: FileStatus.ATTACHED,
|
|
113
|
+
entityId: newId(),
|
|
114
|
+
});
|
|
115
|
+
for (const file of [reservedElsewhere, attachedElsewhere]) {
|
|
116
|
+
const error = await repository.reserve(reserveInput([file.id])).catch((e) => e);
|
|
117
|
+
expect(error.details?.reason).toBe(AttachmentRefusalReason.ATTACHED_TO_ANOTHER_ENTITY);
|
|
118
|
+
expect((await readFile(file.id)).entityId).not.toBe(entityId);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
it.each([FileStatus.ORPHANED, FileStatus.DELETED])('never brings a %s file back, even for its former entity', async (status) => {
|
|
122
|
+
const file = await cleanFile({ status, entityId });
|
|
123
|
+
const error = await repository.reserve(reserveInput([file.id])).catch((e) => e);
|
|
124
|
+
expect(error.details?.reason).toBe(AttachmentRefusalReason.FILE_RELEASED);
|
|
125
|
+
expect((await readFile(file.id)).status).toBe(status);
|
|
126
|
+
});
|
|
127
|
+
it('is idempotent for the same entity: no second write, same files returned', async () => {
|
|
128
|
+
const file = await cleanFile();
|
|
129
|
+
const first = await repository.reserve(reserveInput([file.id]));
|
|
130
|
+
const reservedAt = first[0]?.reservedAt;
|
|
131
|
+
const replay = await repository.reserve(reserveInput([file.id]));
|
|
132
|
+
expect(replay[0]?.status).toBe(FileStatus.RESERVED);
|
|
133
|
+
expect(replay[0]?.reservedAt).toEqual(reservedAt);
|
|
134
|
+
expect(replay[0]?.updatedAt).toEqual(first[0]?.updatedAt);
|
|
135
|
+
});
|
|
136
|
+
it('recognizes an ATTACHED file of the same entity without writing', async () => {
|
|
137
|
+
const file = await cleanFile({ status: FileStatus.ATTACHED, entityId });
|
|
138
|
+
const before = await readFile(file.id);
|
|
139
|
+
const reserved = await repository.reserve(reserveInput([file.id]));
|
|
140
|
+
expect(reserved[0]?.status).toBe(FileStatus.ATTACHED);
|
|
141
|
+
expect((await readFile(file.id)).updatedAt).toEqual(before.updatedAt);
|
|
142
|
+
});
|
|
143
|
+
it('mixes new and already held files of the same entity', async () => {
|
|
144
|
+
const held = await cleanFile({ status: FileStatus.RESERVED, entityId });
|
|
145
|
+
const fresh = await cleanFile();
|
|
146
|
+
const reserved = await repository.reserve(reserveInput([held.id, fresh.id]));
|
|
147
|
+
expect(reserved.map((file) => file.status)).toEqual([FileStatus.RESERVED, FileStatus.RESERVED]);
|
|
148
|
+
});
|
|
149
|
+
it('is all or nothing: one refused file leaves the valid ones untouched', async () => {
|
|
150
|
+
const valid = await cleanFile();
|
|
151
|
+
const rejected = await cleanFile({ scanStatus: ScanStatus.REJECTED });
|
|
152
|
+
await expect(repository.reserve(reserveInput([valid.id, rejected.id]))).rejects.toThrow(FileAttachmentRefusedException);
|
|
153
|
+
const row = await readFile(valid.id);
|
|
154
|
+
expect(row.status).toBe(FileStatus.PENDING);
|
|
155
|
+
expect(row.entityId).toBeNull();
|
|
156
|
+
expect(row.reservedAt).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
it('answers NOT_FOUND rather than a refusal when both occur', async () => {
|
|
159
|
+
const rejected = await cleanFile({ scanStatus: ScanStatus.REJECTED });
|
|
160
|
+
await expect(repository.reserve(reserveInput([rejected.id, newId()]))).rejects.toThrow(FileNotFoundException);
|
|
161
|
+
});
|
|
162
|
+
it('rolls the whole UPDATE back when one row cannot be written', async () => {
|
|
163
|
+
const first = await cleanFile();
|
|
164
|
+
const second = await cleanFile();
|
|
165
|
+
restoreUpdates = await mockFileUpdateFailure(second.id);
|
|
166
|
+
await expect(repository.reserve(reserveInput([first.id, second.id]))).rejects.toThrow('simulated file update failure');
|
|
167
|
+
expect((await readFile(first.id)).status).toBe(FileStatus.PENDING);
|
|
168
|
+
expect((await readFile(second.id)).status).toBe(FileStatus.PENDING);
|
|
169
|
+
});
|
|
170
|
+
it('lets exactly one of two concurrent reservations of a file for different entities win', async () => {
|
|
171
|
+
const file = await cleanFile();
|
|
172
|
+
const otherEntityId = newId();
|
|
173
|
+
const results = await Promise.allSettled([
|
|
174
|
+
repository.reserve(reserveInput([file.id])),
|
|
175
|
+
repository.reserve({ ...reserveInput([file.id]), entityId: otherEntityId }),
|
|
176
|
+
]);
|
|
177
|
+
expect(results.filter((result) => result.status === 'fulfilled')).toHaveLength(1);
|
|
178
|
+
const rejected = results.find((result) => result.status === 'rejected');
|
|
179
|
+
expect(rejected?.reason).toBeInstanceOf(FileAttachmentRefusedException);
|
|
180
|
+
const row = await readFile(file.id);
|
|
181
|
+
expect(row.status).toBe(FileStatus.RESERVED);
|
|
182
|
+
expect([entityId, otherEntityId]).toContain(row.entityId);
|
|
183
|
+
});
|
|
184
|
+
it('survives concurrent reservations of overlapping files in opposite order (no deadlock)', async () => {
|
|
185
|
+
const a = await cleanFile();
|
|
186
|
+
const b = await cleanFile();
|
|
187
|
+
const results = await Promise.all([
|
|
188
|
+
repository.reserve(reserveInput([a.id, b.id])),
|
|
189
|
+
repository.reserve(reserveInput([b.id, a.id])),
|
|
190
|
+
]);
|
|
191
|
+
expect(results.map((files) => files.length)).toEqual([2, 2]);
|
|
192
|
+
});
|
|
193
|
+
it('commutes with the end of the scan: a file reserved while SCANNING is CLEAN afterwards and still RESERVED', async () => {
|
|
194
|
+
const file = await cleanFile({ scanStatus: ScanStatus.SCANNING });
|
|
195
|
+
await repository.reserve(reserveInput([file.id]));
|
|
196
|
+
const completed = await repository.completeScan({ fileId: file.id, publicKey: 'public/x' });
|
|
197
|
+
expect(completed).toEqual({
|
|
198
|
+
fileId: file.id,
|
|
199
|
+
status: FileStatus.RESERVED,
|
|
200
|
+
eventEmitted: false,
|
|
201
|
+
});
|
|
202
|
+
expect((await readFile(file.id)).status).toBe(FileStatus.RESERVED);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
//# sourceMappingURL=postgres-file.repository.reserve.int.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-file.repository.reserve.int.spec.js","sourceRoot":"","sources":["../../../src/test/integration/postgres-file.repository.reserve.int.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-update-failure.mock.d.ts","sourceRoot":"","sources":["../../../src/test/mocks/file-update-failure.mock.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB,WAAkB,MAAM,KAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAmBvF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { testDataSource } from '../data-source.js';
|
|
2
|
+
export const mockFileUpdateFailure = async (fileId) => {
|
|
3
|
+
await testDataSource.query(`CREATE OR REPLACE FUNCTION fail_file_update() RETURNS trigger AS $$
|
|
4
|
+
BEGIN
|
|
5
|
+
IF NEW.id = '${fileId}'::uuid THEN RAISE EXCEPTION 'simulated file update failure'; END IF;
|
|
6
|
+
RETURN NEW;
|
|
7
|
+
END;
|
|
8
|
+
$$ LANGUAGE plpgsql`);
|
|
9
|
+
await testDataSource.query(`DROP TRIGGER IF EXISTS fail_file_update ON files`);
|
|
10
|
+
await testDataSource.query(`CREATE TRIGGER fail_file_update BEFORE UPDATE ON files
|
|
11
|
+
FOR EACH ROW EXECUTE FUNCTION fail_file_update()`);
|
|
12
|
+
return async () => {
|
|
13
|
+
await testDataSource.query(`DROP TRIGGER IF EXISTS fail_file_update ON files`);
|
|
14
|
+
await testDataSource.query(`DROP FUNCTION IF EXISTS fail_file_update()`);
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=file-update-failure.mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-update-failure.mock.js","sourceRoot":"","sources":["../../../src/test/mocks/file-update-failure.mock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EAAE,MAAc,EAAgC,EAAE;IAC1F,MAAM,cAAc,CAAC,KAAK,CACxB;;sBAEkB,MAAM;;;yBAGH,CACtB,CAAC;IACF,MAAM,cAAc,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IAC/E,MAAM,cAAc,CAAC,KAAK,CACxB;sDACkD,CACnD,CAAC;IAEF,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,cAAc,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAC/E,MAAM,cAAc,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC3E,CAAC,CAAC;AACJ,CAAC,CAAC"}
|