@tstdl/base 0.92.132 → 0.92.135

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.
Files changed (57) hide show
  1. package/api/response.js +6 -6
  2. package/api/server/api-request-token.provider.d.ts +3 -0
  3. package/api/server/api-request-token.provider.js +9 -0
  4. package/api/server/module.js +1 -1
  5. package/database/mongo/module.js +6 -6
  6. package/document-management/api/document-management.api.d.ts +20 -4
  7. package/document-management/api/document-management.api.js +9 -3
  8. package/document-management/server/api/document-management.api.d.ts +1 -0
  9. package/document-management/server/api/document-management.api.js +9 -2
  10. package/document-management/server/module.d.ts +1 -0
  11. package/document-management/server/module.js +1 -0
  12. package/document-management/server/services/document-file.service.d.ts +16 -0
  13. package/document-management/server/services/document-file.service.js +55 -25
  14. package/document-management/server/services/document-management-ai.service.js +1 -1
  15. package/document-management/server/services/document-management-ancillary.service.d.ts +2 -2
  16. package/document-management/server/services/document-management.service.js +23 -11
  17. package/document-management/server/services/document-workflow.service.d.ts +1 -0
  18. package/document-management/server/services/document-workflow.service.js +15 -4
  19. package/document-management/server/services/document.service.d.ts +5 -1
  20. package/document-management/server/services/document.service.js +13 -10
  21. package/document-management/service-models/document-management.view-model.d.ts +15 -4
  22. package/document-management/service-models/document-management.view-model.js +42 -12
  23. package/document-management/service-models/document.service-model.d.ts +1 -0
  24. package/document-management/service-models/document.service-model.js +1 -0
  25. package/document-management/service-models/enriched/enriched-document-assignment.view.d.ts +13 -4
  26. package/document-management/service-models/enriched/enriched-document-assignment.view.js +29 -7
  27. package/document-management/service-models/enriched/enriched-document-collection.view.js +1 -1
  28. package/document-management/service-models/enriched/enriched-document-request.view.d.ts +1 -1
  29. package/document-management/service-models/enriched/enriched-document.view.d.ts +2 -2
  30. package/document-management/service-models/enriched/enriched-document.view.js +2 -6
  31. package/examples/document-management/main.d.ts +1 -1
  32. package/examples/document-management/main.js +20 -8
  33. package/http/client/adapters/undici.adapter.js +3 -3
  34. package/http/client/http-client.js +29 -30
  35. package/http/http-body.js +4 -4
  36. package/http/http.error.d.ts +5 -1
  37. package/http/http.error.js +6 -6
  38. package/http/utils.js +4 -4
  39. package/injector/decorators.d.ts +1 -1
  40. package/injector/injector.d.ts +1 -1
  41. package/injector/interfaces.d.ts +1 -1
  42. package/injector/provider.d.ts +4 -4
  43. package/object-storage/object-storage.d.ts +38 -2
  44. package/object-storage/s3/s3.object-storage-provider.js +1 -1
  45. package/object-storage/s3/s3.object-storage.d.ts +6 -3
  46. package/object-storage/s3/s3.object-storage.js +88 -14
  47. package/object-storage/s3/s3.object.js +2 -3
  48. package/orm/server/repository.js +37 -37
  49. package/package.json +1 -1
  50. package/schema/schema.error.js +4 -7
  51. package/search-index/elastic/module.js +5 -5
  52. package/utils/cryptography.js +18 -18
  53. package/utils/object/object.d.ts +3 -2
  54. package/utils/object/object.js +5 -2
  55. package/utils/stream/size-limited-stream.js +1 -1
  56. package/utils/type-guards.d.ts +7 -1
  57. package/utils/type-guards.js +13 -1
package/api/response.js CHANGED
@@ -35,8 +35,8 @@ export function createErrorResponse(errorOrName, message = '', details) {
35
35
  name: errorOrName.name,
36
36
  message: errorOrName.message,
37
37
  details,
38
- data
39
- }
38
+ data,
39
+ },
40
40
  };
41
41
  }
42
42
  else {
@@ -44,8 +44,8 @@ export function createErrorResponse(errorOrName, message = '', details) {
44
44
  error: {
45
45
  name: errorOrName.name,
46
46
  message: errorOrName.message,
47
- details
48
- }
47
+ details,
48
+ },
49
49
  };
50
50
  }
51
51
  }
@@ -54,8 +54,8 @@ export function createErrorResponse(errorOrName, message = '', details) {
54
54
  error: {
55
55
  name: errorOrName,
56
56
  message,
57
- details
58
- }
57
+ details,
58
+ },
59
59
  };
60
60
  }
61
61
  return response;
@@ -5,3 +5,6 @@ export declare abstract class ApiRequestTokenProvider {
5
5
  export declare class NoopApiRequestTokenProvider extends ApiRequestTokenProvider {
6
6
  getToken<T>(): T;
7
7
  }
8
+ export declare class MockApiRequestTokenProvider extends ApiRequestTokenProvider {
9
+ getToken<T>(): T;
10
+ }
@@ -16,3 +16,12 @@ NoopApiRequestTokenProvider = __decorate([
16
16
  Singleton({ alias: ApiRequestTokenProvider })
17
17
  ], NoopApiRequestTokenProvider);
18
18
  export { NoopApiRequestTokenProvider };
19
+ let MockApiRequestTokenProvider = class MockApiRequestTokenProvider extends ApiRequestTokenProvider {
20
+ getToken() {
21
+ return {};
22
+ }
23
+ };
24
+ MockApiRequestTokenProvider = __decorate([
25
+ Singleton({ alias: ApiRequestTokenProvider })
26
+ ], MockApiRequestTokenProvider);
27
+ export { MockApiRequestTokenProvider };
@@ -4,7 +4,7 @@ import { ensureApiController } from './api-controller.js';
4
4
  import { ApiRequestTokenProvider } from './api-request-token.provider.js';
5
5
  import { API_CONTROLLER, API_MODULE_OPTIONS } from './tokens.js';
6
6
  export const apiModuleOptions = {
7
- controllers: []
7
+ controllers: [],
8
8
  };
9
9
  export function configureApiServer(options) {
10
10
  if (isDefined(options.controllers)) {
@@ -6,7 +6,7 @@ import { Collection, Database, MongoClient } from './classes.js';
6
6
  export const mongoModuleConfig = {
7
7
  defaultConnection: { url: 'mongodb://localhost:27017/test-db' },
8
8
  defaultDatabase: undefined,
9
- logPrefix: 'Mongo'
9
+ logPrefix: 'Mongo',
10
10
  };
11
11
  export function configureMongo(config) {
12
12
  mongoModuleConfig.defaultDatabase = config.defaultDatabase ?? mongoModuleConfig.defaultDatabase;
@@ -31,10 +31,10 @@ Injector.registerSingleton(MongoClient, {
31
31
  },
32
32
  async afterResolve(client, _argument, { cancellationSignal, data: { url, logger } }) {
33
33
  await connect(`mongo at ${url}`, async () => client.connect(), logger, cancellationSignal);
34
- }
34
+ },
35
35
  }, {
36
36
  defaultArgumentProvider: () => mongoModuleConfig.defaultConnection,
37
- argumentIdentityProvider: JSON.stringify
37
+ argumentIdentityProvider: JSON.stringify,
38
38
  });
39
39
  Injector.registerSingleton(Database, {
40
40
  useFactory: (argument, context) => {
@@ -45,7 +45,7 @@ Injector.registerSingleton(Database, {
45
45
  },
46
46
  defaultArgumentProvider: () => ({ database: mongoModuleConfig.defaultDatabase, connection: mongoModuleConfig.defaultConnection }),
47
47
  }, {
48
- argumentIdentityProvider: JSON.stringify
48
+ argumentIdentityProvider: JSON.stringify,
49
49
  });
50
50
  Injector.registerSingleton(Collection, {
51
51
  useFactory: (config, context) => {
@@ -62,7 +62,7 @@ Injector.registerSingleton(Collection, {
62
62
  }
63
63
  }
64
64
  await database.createCollection(config.collection);
65
- }
65
+ },
66
66
  }, {
67
- argumentIdentityProvider: JSON.stringify
67
+ argumentIdentityProvider: JSON.stringify,
68
68
  });
@@ -71,6 +71,15 @@ export declare const documentManagementApiDefinition: {
71
71
  result: typeof DocumentType;
72
72
  credentials: true;
73
73
  };
74
+ initiateDocumentUpload: {
75
+ resource: string;
76
+ method: "POST";
77
+ result: import("../../schema/index.js").ObjectSchema<{
78
+ uploadId: string;
79
+ uploadUrl: string;
80
+ }>;
81
+ credentials: true;
82
+ };
74
83
  createDocument: {
75
84
  resource: string;
76
85
  method: "POST";
@@ -94,6 +103,7 @@ export declare const documentManagementApiDefinition: {
94
103
  scope: string | string[];
95
104
  };
96
105
  };
106
+ uploadId: string;
97
107
  properties?: {
98
108
  propertyId: import("../../orm/schemas/uuid.js").Uuid;
99
109
  value: string | number | boolean | null;
@@ -105,8 +115,6 @@ export declare const documentManagementApiDefinition: {
105
115
  attributes: import("../../orm/types.js").HasDefault<import("../../orm/schemas/json.js").Json<import("../../orm/entity.js").EntityMetadataAttributes>>;
106
116
  }> | undefined;
107
117
  }>;
108
- body: Uint8ArrayConstructor;
109
- maxBytes: number;
110
118
  result: typeof Document;
111
119
  credentials: true;
112
120
  };
@@ -370,6 +378,15 @@ declare const _DocumentManagementApi: import("../../api/client/index.js").ApiCli
370
378
  result: typeof DocumentType;
371
379
  credentials: true;
372
380
  };
381
+ initiateDocumentUpload: {
382
+ resource: string;
383
+ method: "POST";
384
+ result: import("../../schema/index.js").ObjectSchema<{
385
+ uploadId: string;
386
+ uploadUrl: string;
387
+ }>;
388
+ credentials: true;
389
+ };
373
390
  createDocument: {
374
391
  resource: string;
375
392
  method: "POST";
@@ -393,6 +410,7 @@ declare const _DocumentManagementApi: import("../../api/client/index.js").ApiCli
393
410
  scope: string | string[];
394
411
  };
395
412
  };
413
+ uploadId: string;
396
414
  properties?: {
397
415
  propertyId: import("../../orm/schemas/uuid.js").Uuid;
398
416
  value: string | number | boolean | null;
@@ -404,8 +422,6 @@ declare const _DocumentManagementApi: import("../../api/client/index.js").ApiCli
404
422
  attributes: import("../../orm/types.js").HasDefault<import("../../orm/schemas/json.js").Json<import("../../orm/entity.js").EntityMetadataAttributes>>;
405
423
  }> | undefined;
406
424
  }>;
407
- body: Uint8ArrayConstructor;
408
- maxBytes: number;
409
425
  result: typeof Document;
410
426
  credentials: true;
411
427
  };
@@ -8,7 +8,6 @@ import { compileClient } from '../../api/client/index.js';
8
8
  import { defineApi } from '../../api/index.js';
9
9
  import { ReplaceClass } from '../../injector/decorators.js';
10
10
  import { array, boolean, literal, object, optional, string } from '../../schema/index.js';
11
- import { megabyte } from '../../utils/units.js';
12
11
  import { Document, DocumentCategory, DocumentRequest, DocumentRequestsTemplate, DocumentRequestTemplate, DocumentType } from '../models/index.js';
13
12
  import { addOrArchiveDocumentToOrFromCollectionParametersSchema, applyDocumentRequestsTemplateParametersSchema, createDocumentCategoryParametersSchema, createDocumentParametersSchema, createDocumentRequestParametersSchema, createDocumentRequestsTemplateParametersSchema, createDocumentRequestTemplateParametersSchema, createDocumentTypeParametersSchema, deleteDocumentRequestParametersSchema, deleteDocumentRequestsTemplateParametersSchema, deleteDocumentRequestTemplateParametersSchema, DocumentCategoryView, DocumentManagementData, DocumentRequestsTemplateData, loadDataParametersSchema, updateDocumentParametersSchema, updateDocumentRequestParametersSchema, updateDocumentRequestsTemplateParametersSchema, updateDocumentRequestTemplateParametersSchema } from '../service-models/index.js';
14
13
  export const documentManagementApiDefinition = defineApi({
@@ -67,12 +66,19 @@ export const documentManagementApiDefinition = defineApi({
67
66
  result: DocumentType,
68
67
  credentials: true,
69
68
  },
69
+ initiateDocumentUpload: {
70
+ resource: 'document-uploads',
71
+ method: 'POST',
72
+ result: object({
73
+ uploadId: string(),
74
+ uploadUrl: string(),
75
+ }),
76
+ credentials: true,
77
+ },
70
78
  createDocument: {
71
79
  resource: 'documents',
72
80
  method: 'POST',
73
81
  parameters: createDocumentParametersSchema,
74
- body: Uint8Array,
75
- maxBytes: 50 * megabyte,
76
82
  result: Document,
77
83
  credentials: true,
78
84
  },
@@ -9,6 +9,7 @@ export declare class DocumentManagementApiController implements ApiController<Do
9
9
  getContentUrl(context: ApiRequestContext<DocumentManagementApiDefinition, 'getContentUrl'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'getContentUrl'>>;
10
10
  createCategory(context: ApiRequestContext<DocumentManagementApiDefinition, 'createCategory'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createCategory'>>;
11
11
  createType(context: ApiRequestContext<DocumentManagementApiDefinition, 'createType'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createType'>>;
12
+ initiateDocumentUpload(context: ApiRequestContext<DocumentManagementApiDefinition, 'initiateDocumentUpload'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'initiateDocumentUpload'>>;
12
13
  createDocument(context: ApiRequestContext<DocumentManagementApiDefinition, 'createDocument'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createDocument'>>;
13
14
  createDocumentRequestsTemplate(context: ApiRequestContext<DocumentManagementApiDefinition, 'createDocumentRequestsTemplate'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createDocumentRequestsTemplate'>>;
14
15
  updateDocumentRequestsTemplate(_context: ApiRequestContext<DocumentManagementApiDefinition, 'updateDocumentRequestsTemplate'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'updateDocumentRequestsTemplate'>>;
@@ -92,8 +92,15 @@ let DocumentManagementApiController = DocumentManagementApiController_1 = class
92
92
  }
93
93
  return await this.#documentCategoryTypeService.createType(context.parameters.label, context.parameters.categoryId);
94
94
  }
95
+ async initiateDocumentUpload(context) {
96
+ const token = await context.getToken();
97
+ const subject = await this.#ancillaryService.getSubject(token);
98
+ return await this.#documentFileService.initiateUpload({ key: subject });
99
+ }
95
100
  async createDocument(context) {
96
101
  const token = await context.getToken();
102
+ const subject = await this.#ancillaryService.getSubject(token);
103
+ const { uploadId, ...createParameters } = context.parameters;
97
104
  const [collectionIds, requiresAssign] = await match(context.parameters.assignment)
98
105
  .with({ collections: P.select() }, (collectionIds) => [toArray(collectionIds), true])
99
106
  .with({ request: P.select() }, async (requestId) => {
@@ -115,8 +122,8 @@ let DocumentManagementApiController = DocumentManagementApiController_1 = class
115
122
  }
116
123
  }
117
124
  }
118
- const actionUserId = await this.#ancillaryService.getUserId(token);
119
- return await this.#documentService.create(context.parameters, context.body, { createUserId: actionUserId });
125
+ const actionUserId = await this.#ancillaryService.getSubject(token);
126
+ return await this.#documentService.create(createParameters, { uploadId, uploadKey: subject }, { createUserId: actionUserId });
120
127
  }
121
128
  async createDocumentRequestsTemplate(context) {
122
129
  const token = await context.getToken();
@@ -5,6 +5,7 @@ import { DocumentManagementAncillaryService } from './services/document-manageme
5
5
  export declare class DocumentManagementConfig {
6
6
  ancillaryService: InjectionToken<DocumentManagementAncillaryService>;
7
7
  fileObjectStorageModule: string;
8
+ fileUploadObjectStorageModule: string;
8
9
  filePreviewObjectStorageModule: string;
9
10
  database?: DatabaseConfig;
10
11
  }
@@ -5,6 +5,7 @@ import { DocumentManagementAncillaryService } from './services/document-manageme
5
5
  export class DocumentManagementConfig {
6
6
  ancillaryService;
7
7
  fileObjectStorageModule;
8
+ fileUploadObjectStorageModule;
8
9
  filePreviewObjectStorageModule;
9
10
  database;
10
11
  }
@@ -4,7 +4,23 @@ import { DocumentFile } from '../../models/index.js';
4
4
  export declare class DocumentFileService extends Transactional {
5
5
  #private;
6
6
  load(id: string): Promise<DocumentFile>;
7
+ /**
8
+ * Initiates a file upload
9
+ * @param key - key which can be used to authorize the creation of the file. Same key must be provided to {@link create} method, or it will throw an error.
10
+ * The key could be the user id from the request token. This ensures that the file can only be created by the user who initiated the upload.
11
+ * @returns upload information
12
+ */
13
+ initiateUpload({ key }: {
14
+ key: string;
15
+ }): Promise<{
16
+ uploadId: string;
17
+ uploadUrl: string;
18
+ }>;
7
19
  create(content: Uint8Array | ReadableStream<Uint8Array>, originalFileName: string | null): Promise<DocumentFile>;
20
+ create(content: {
21
+ uploadId: string;
22
+ uploadKey: string;
23
+ }, originalFileName: string | null): Promise<[DocumentFile, Uint8Array]>;
8
24
  getContent(fileId: string): Promise<Uint8Array>;
9
25
  getContentStream(fileId: string): ReadableStream<Uint8Array>;
10
26
  getContentUrl(fileId: string, title: string | null, download?: boolean): Promise<string>;
@@ -61,6 +61,7 @@ var DocumentFileService_1;
61
61
  import sharp, {} from 'sharp';
62
62
  import { match } from 'ts-pattern';
63
63
  import { AiService } from '../../../ai/ai.service.js';
64
+ import { ForbiddenError } from '../../../errors/forbidden.error.js';
64
65
  import { NotImplementedError } from '../../../errors/not-implemented.error.js';
65
66
  import { getMimeType, getMimeTypeExtensions } from '../../../file/index.js';
66
67
  import { TemporaryFile } from '../../../file/server/index.js';
@@ -70,30 +71,55 @@ import { ObjectStorage } from '../../../object-storage/index.js';
70
71
  import { Transactional } from '../../../orm/server/index.js';
71
72
  import { injectRepository } from '../../../orm/server/repository.js';
72
73
  import { pdfToImage } from '../../../pdf/index.js';
74
+ import { Alphabet } from '../../../utils/alphabet.js';
73
75
  import { digest } from '../../../utils/cryptography.js';
74
76
  import { currentTimestamp } from '../../../utils/date-time.js';
77
+ import { getRandomString } from '../../../utils/random.js';
75
78
  import { readableStreamFromPromise, readBinaryStream } from '../../../utils/stream/index.js';
76
- import { isDefined, isUint8Array } from '../../../utils/type-guards.js';
77
- import { millisecondsPerMinute } from '../../../utils/units.js';
79
+ import { isDefined, isNotReadableStream, isNotUint8Array, isUint8Array } from '../../../utils/type-guards.js';
80
+ import { millisecondsPerMinute, secondsPerMinute } from '../../../utils/units.js';
78
81
  import { Document, DocumentFile } from '../../models/index.js';
79
82
  import { DocumentManagementConfig } from '../module.js';
80
83
  import { DocumentManagementSingleton } from './singleton.js';
81
84
  let DocumentFileService = DocumentFileService_1 = class DocumentFileService extends Transactional {
85
+ #config = inject(DocumentManagementConfig);
82
86
  #documentFileRepository = injectRepository(DocumentFile);
83
87
  #documentRepository = injectRepository(Document);
84
88
  #aiService = inject(AiService);
85
- #fileObjectStorage = inject(ObjectStorage, inject(DocumentManagementConfig).fileObjectStorageModule);
86
- #filePreviewObjectStorage = inject(ObjectStorage, inject(DocumentManagementConfig).filePreviewObjectStorageModule);
89
+ #fileObjectStorage = inject(ObjectStorage, this.#config.fileObjectStorageModule);
90
+ #filePreviewObjectStorage = inject(ObjectStorage, this.#config.filePreviewObjectStorageModule);
91
+ #fileUploadObjectStorage = inject(ObjectStorage, { module: this.#config.fileUploadObjectStorageModule, configuration: { lifecycle: { expiration: { after: 5 * secondsPerMinute } } } });
87
92
  #logger = inject(Logger, DocumentFileService_1.name);
88
93
  #aiFilePartCache = new Map();
89
94
  async load(id) {
90
- return this.#documentFileRepository.load(id);
95
+ return await this.#documentFileRepository.load(id);
96
+ }
97
+ /**
98
+ * Initiates a file upload
99
+ * @param key - key which can be used to authorize the creation of the file. Same key must be provided to {@link create} method, or it will throw an error.
100
+ * The key could be the user id from the request token. This ensures that the file can only be created by the user who initiated the upload.
101
+ * @returns upload information
102
+ */
103
+ async initiateUpload({ key }) {
104
+ const id = getRandomString(64, Alphabet.LowerUpperCaseNumbers);
105
+ const url = await this.#fileUploadObjectStorage.getUploadUrl(id, currentTimestamp() + (5 * millisecondsPerMinute), { metadata: { 'upload-key': key } });
106
+ return { uploadId: id, uploadUrl: url };
91
107
  }
92
108
  async create(content, originalFileName) {
93
- const contentAsUint8Array = isUint8Array(content) ? content : await readBinaryStream(content);
109
+ const isUpload = isNotUint8Array(content) && isNotReadableStream(content);
110
+ if (isUpload) {
111
+ const object = await this.#fileUploadObjectStorage.getObject(content.uploadId);
112
+ const objectMetadata = await object.getMetadata();
113
+ if (content.uploadKey != objectMetadata['upload-key']) {
114
+ throw new ForbiddenError(`Invalid upload key`);
115
+ }
116
+ }
117
+ const contentAsUint8Array = isUpload
118
+ ? await this.#fileUploadObjectStorage.getContent(content.uploadId)
119
+ : (isUint8Array(content) ? content : await readBinaryStream(content));
94
120
  const hash = await digest('SHA-256', contentAsUint8Array).toHex();
95
121
  const mimeType = await getMimeType(contentAsUint8Array);
96
- return this.transaction(async (tx) => {
122
+ return await this.transaction(async (tx) => {
97
123
  const documentFile = await this.#documentFileRepository
98
124
  .withTransaction(tx)
99
125
  .insert({
@@ -102,39 +128,43 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
102
128
  hash,
103
129
  size: contentAsUint8Array.length,
104
130
  });
105
- const key = getDocumentFileKey(documentFile.id);
106
- await this.#fileObjectStorage.uploadObject(key, contentAsUint8Array);
131
+ const objectKey = getDocumentFileKey(documentFile.id);
132
+ if (isUpload) {
133
+ await this.#fileUploadObjectStorage.moveObject(content.uploadId, [this.#fileObjectStorage, objectKey]);
134
+ return [documentFile, contentAsUint8Array];
135
+ }
136
+ await this.#fileObjectStorage.uploadObject(objectKey, contentAsUint8Array, { contentLength: contentAsUint8Array.length, contentType: mimeType });
107
137
  return documentFile;
108
138
  });
109
139
  }
110
140
  async getContent(fileId) {
111
- const key = getDocumentFileKey(fileId);
112
- return this.#fileObjectStorage.getContent(key);
141
+ const objectKey = getDocumentFileKey(fileId);
142
+ return await this.#fileObjectStorage.getContent(objectKey);
113
143
  }
114
144
  getContentStream(fileId) {
115
- const key = getDocumentFileKey(fileId);
116
- return this.#fileObjectStorage.getContentStream(key);
145
+ const objectKey = getDocumentFileKey(fileId);
146
+ return this.#fileObjectStorage.getContentStream(objectKey);
117
147
  }
118
148
  async getContentUrl(fileId, title, download = false) {
119
149
  const file = await this.#documentFileRepository.load(fileId);
120
- return this.getDocumentFileContentObjectUrl(title ?? fileId, file, download);
150
+ return await this.getDocumentFileContentObjectUrl(title ?? fileId, file, download);
121
151
  }
122
152
  async getPreview(fileId, page = 1) {
123
- const key = getDocumentFileKey(fileId);
153
+ const objectKey = getDocumentFileKey(fileId);
124
154
  await this.createPreviewIfNotExists(fileId, page);
125
- return this.#filePreviewObjectStorage.getContent(key);
155
+ return await this.#filePreviewObjectStorage.getContent(objectKey);
126
156
  }
127
157
  getPreviewStream(fileId, page = 1) {
128
158
  return readableStreamFromPromise(async () => {
129
- const key = getDocumentFileKey(fileId);
159
+ const objectKey = getDocumentFileKey(fileId);
130
160
  await this.createPreviewIfNotExists(fileId, page);
131
- return this.#filePreviewObjectStorage.getContentStream(key);
161
+ return this.#filePreviewObjectStorage.getContentStream(objectKey);
132
162
  });
133
163
  }
134
164
  async getPreviewUrl(fileId, page = 1) {
135
- const key = getDocumentFileKey(fileId);
165
+ const objectKey = getDocumentFileKey(fileId);
136
166
  await this.createPreviewIfNotExists(fileId, page);
137
- return this.#filePreviewObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
167
+ return await this.#filePreviewObjectStorage.getDownloadUrl(objectKey, currentTimestamp() + (5 * millisecondsPerMinute), {
138
168
  'Response-Content-Type': 'image/jpeg',
139
169
  });
140
170
  }
@@ -175,11 +205,11 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
175
205
  const image = await match(file.mimeType)
176
206
  .with('application/pdf', async () => {
177
207
  const imageBytes = await pdfToImage(content, page, 768, 'jpeg');
178
- return imageToPreview(imageBytes);
208
+ return await imageToPreview(imageBytes);
179
209
  })
180
- .with('image/*', async () => imageToPreview(content))
210
+ .with('image/*', async () => await imageToPreview(content))
181
211
  .otherwise(() => { throw new NotImplementedError('Preview generation is not implemented for this file type.'); });
182
- await this.#filePreviewObjectStorage.uploadObject(key, image, { contentLength: image.length, metadata: { 'Content-Type': 'image/jpeg' } });
212
+ await this.#filePreviewObjectStorage.uploadObject(key, image, { contentLength: image.length, contentType: 'image/jpeg' });
183
213
  }
184
214
  }
185
215
  async getDocumentFileContentObjectUrl(title, file, download) {
@@ -187,7 +217,7 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
187
217
  const fileExtension = getMimeTypeExtensions(file.mimeType)[0] ?? 'bin';
188
218
  const disposition = download ? 'attachment' : 'inline';
189
219
  const filename = `${title}.${fileExtension}`;
190
- return this.#fileObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
220
+ return await this.#fileObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
191
221
  'Response-Content-Type': file.mimeType,
192
222
  'Response-Content-Disposition': `${disposition}; filename = "${encodeURIComponent(filename)}"`,
193
223
  });
@@ -201,7 +231,7 @@ function getDocumentFileKey(id) {
201
231
  return `${id.slice(0, 2)}/${id.slice(0, 4)}/${id}`;
202
232
  }
203
233
  async function imageToPreview(input) {
204
- return sharp(input)
234
+ return await sharp(input)
205
235
  .resize({
206
236
  width: 768,
207
237
  height: 768,
@@ -93,7 +93,7 @@ let DocumentManagementAiService = DocumentManagementAiService_1 = class Document
93
93
  try {
94
94
  const document = await this.#documentRepository.load(documentId);
95
95
  const file = await this.#documentFileService.load(document.fileId);
96
- const fileContentStream = this.#documentFileService.getContentStream(document.id);
96
+ const fileContentStream = this.#documentFileService.getContentStream(document.fileId);
97
97
  const tmpFile = __addDisposableResource(env_1, await TemporaryFile.from(fileContentStream), true);
98
98
  const filePart = await this.#aiService.processFile({ path: tmpFile.path, mimeType: file.mimeType });
99
99
  const categories = await this.#documentCategoryTypeService.loadCategoryViews();
@@ -28,10 +28,10 @@ export declare abstract class DocumentManagementAncillaryService<Token = unknown
28
28
  */
29
29
  abstract _resolveMetadata(collections: DocumentCollection[]): DocumentCollectionMetadata[] | Promise<DocumentCollectionMetadata[]>;
30
30
  /**
31
- * Gets the user ID from the request token.
31
+ * Gets the subject from the request token.
32
32
  * @param token The token of the request
33
33
  */
34
- abstract getUserId(token?: Token): string | Promise<string>;
34
+ abstract getSubject(token?: Token): string | Promise<string>;
35
35
  /**
36
36
  * Checks if a user can read/view a specific document collection and its documents.
37
37
  * @param collectionId The ID of the document collection.
@@ -6,14 +6,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { Enumerable } from '../../../enumerable/index.js';
8
8
  import { inject } from '../../../injector/index.js';
9
- import { getEntityMap } from '../../../orm/index.js';
10
9
  import { Transactional, injectRepository } from '../../../orm/server/index.js';
11
10
  import { distinct } from '../../../utils/array/index.js';
12
11
  import { compareByValueSelectionToOrder } from '../../../utils/comparison.js';
13
12
  import { groupToMap, groupToSingleMap } from '../../../utils/iterable-helpers/index.js';
14
13
  import { fromEntries, objectEntries } from '../../../utils/object/index.js';
15
- import { assertDefinedPass, isNotNull, isNotNullOrUndefined, isNull, isUndefined } from '../../../utils/type-guards.js';
16
- import { DocumentApproval, DocumentCategory, DocumentCollection, DocumentCollectionAssignment, DocumentFile, DocumentRequest, DocumentRequestCollectionAssignment, DocumentRequestTemplate, DocumentRequestsTemplate, DocumentType, DocumentValidationExecution, DocumentWorkflowStep } from '../../models/index.js';
14
+ import { assertDefinedPass, isDefined, isNotNull, isNotNullOrUndefined, isNull, isUndefined } from '../../../utils/type-guards.js';
15
+ import { DocumentApproval, DocumentAssignmentScope, DocumentAssignmentTask, DocumentCategory, DocumentCollection, DocumentCollectionAssignment, DocumentFile, DocumentRequest, DocumentRequestCollectionAssignment, DocumentRequestTemplate, DocumentRequestsTemplate, DocumentType, DocumentValidationExecution, DocumentWorkflowStep } from '../../models/index.js';
17
16
  import { DocumentCategoryTypeService, enumTypeKey } from './document-category-type.service.js';
18
17
  import { DocumentManagementAncillaryService } from './document-management-ancillary.service.js';
19
18
  import { DocumentPropertyService } from './document-property.service.js';
@@ -30,6 +29,8 @@ let DocumentManagementService = class DocumentManagementService extends Transact
30
29
  #documentService = inject(DocumentService);
31
30
  #documentPropertyService = inject(DocumentPropertyService);
32
31
  #documentRequestCollectionAssignmentRepository = injectRepository(DocumentRequestCollectionAssignment);
32
+ #documentAssignmentTaskRepository = injectRepository(DocumentAssignmentTask);
33
+ #documentAssignmentScopeRepository = injectRepository(DocumentAssignmentScope);
33
34
  #documentRequestRepository = injectRepository(DocumentRequest);
34
35
  #documentRequestsTemplateRepository = injectRepository(DocumentRequestsTemplate);
35
36
  #documentRequestTemplateRepository = injectRepository(DocumentRequestTemplate);
@@ -38,19 +39,25 @@ let DocumentManagementService = class DocumentManagementService extends Transact
38
39
  #documentValidationExecutionRepository = injectRepository(DocumentValidationExecution);
39
40
  async loadData(collectionIds) {
40
41
  return await this.transaction(async (tx) => {
41
- const [collections, collectionsMetadataMap, documentAssignments, requestAssignments, categories, types] = await Promise.all([
42
+ const [collections, collectionsMetadataMap, documentCollectionAssignments, requestAssignments, assignmentScopes, categories, types] = await Promise.all([
42
43
  this.#documentCollectionRepository.withTransaction(tx).loadMany(collectionIds),
43
44
  this.#ancillaryService.resolveMetadataMap(...collectionIds),
44
45
  this.#documentCollectionAssignmentRepository.withTransaction(tx).loadManyByQuery({ collectionId: { $in: collectionIds } }),
45
46
  this.#documentRequestCollectionAssignmentRepository.withTransaction(tx).loadManyByQuery({ collectionId: { $in: collectionIds } }),
47
+ this.#documentAssignmentScopeRepository.withTransaction(tx).loadManyByQuery({ collectionId: { $in: collectionIds } }),
46
48
  this.#documentCategoryRepository.withTransaction(tx).loadManyByQuery({}, { order: 'label' }),
47
49
  this.#documentTypeRepository.withTransaction(tx).loadManyByQuery({}, { order: 'label' }),
48
50
  ]);
49
51
  const requestIds = requestAssignments.map((requestCollection) => requestCollection.requestId);
50
- const requests = await this.#documentRequestRepository.withTransaction(tx).loadManyByQuery({ id: { $in: requestIds } }, { order: { 'metadata.createTimestamp': 'desc' } });
51
- const assignmentDocumentIds = documentAssignments.map((assignment) => assignment.documentId);
52
+ const taskIds = assignmentScopes.map((scope) => scope.taskId);
53
+ const [requests, assignmentTasks] = await Promise.all([
54
+ this.#documentRequestRepository.withTransaction(tx).loadManyByQuery({ id: { $in: requestIds } }, { order: { 'metadata.createTimestamp': 'desc' } }),
55
+ this.#documentAssignmentTaskRepository.withTransaction(tx).loadMany(taskIds),
56
+ ]);
57
+ const assignmentDocumentIds = documentCollectionAssignments.map((assignment) => assignment.documentId);
52
58
  const requestDocumentIds = requests.map((request) => request.documentId).filter(isNotNull);
53
- const documentIds = distinct([...assignmentDocumentIds, ...requestDocumentIds]);
59
+ const assignmentTaskDocumentIds = assignmentTasks.map((task) => task.documentId);
60
+ const documentIds = distinct([...assignmentDocumentIds, ...requestDocumentIds, ...assignmentTaskDocumentIds]);
54
61
  const [documents, propertyValues] = await Promise.all([
55
62
  this.#documentService.withTransaction(tx).repository.loadManyByQuery({ id: { $in: documentIds } }, { order: { 'metadata.createTimestamp': 'desc' } }),
56
63
  this.#documentPropertyService.withTransaction(tx).loadDocumentProperties(documentIds),
@@ -61,7 +68,6 @@ let DocumentManagementService = class DocumentManagementService extends Transact
61
68
  this.#documentFileRepository.withTransaction(tx).loadManyByQuery({ id: { $in: documentFileIds } }, { order: { 'metadata.createTimestamp': 'desc' } }),
62
69
  this.#documentWorkflowService.loadLatestWorkflows(workflowRelevantDocumentIds),
63
70
  ]);
64
- const documentIdMap = getEntityMap(documents);
65
71
  const documentWorkflowMap = groupToSingleMap(currentWorkflows, (workflow) => workflow.documentId);
66
72
  const valuesMap = Enumerable.from(propertyValues).groupToMap((value) => value.documentId);
67
73
  const validationWorkflowIds = currentWorkflows.map((workflow) => (workflow.step == DocumentWorkflowStep.Validation) ? workflow.id : null).filter(isNotNull);
@@ -78,9 +84,16 @@ let DocumentManagementService = class DocumentManagementService extends Transact
78
84
  const documentViews = documents.map((document) => {
79
85
  const currentWorkflow = documentWorkflowMap.get(document.id) ?? null;
80
86
  const validations = (isNotNull(currentWorkflow) && (currentWorkflow.step == DocumentWorkflowStep.Validation)) ? workflowValidationMap.get(currentWorkflow.id) ?? [] : null;
87
+ const assignmentTask = assignmentTasks.find((task) => task.documentId == document.id);
88
+ const assignmentTaskScope = isDefined(assignmentTask) ? assignmentScopes.filter((scope) => scope.taskId == assignmentTask.id).map((scope) => scope.collectionId) : [];
81
89
  return {
82
90
  ...document,
83
- collectionAssignments: documentAssignments.filter((collectionDocument) => collectionDocument.documentId == document.id),
91
+ assignment: {
92
+ collections: documentCollectionAssignments.filter((collectionDocument) => collectionDocument.documentId == document.id),
93
+ assignmentTask: isDefined(assignmentTask)
94
+ ? { target: assignmentTask.target, scope: assignmentTaskScope }
95
+ : null,
96
+ },
84
97
  properties: valuesMap.get(document.id) ?? [],
85
98
  currentWorkflow,
86
99
  validations,
@@ -88,8 +101,7 @@ let DocumentManagementService = class DocumentManagementService extends Transact
88
101
  });
89
102
  const requestViews = requests.map((request) => ({
90
103
  ...request,
91
- collectionIds: requestAssignments.filter((requestCollection) => requestCollection.requestId == request.id).map((requestCollection) => requestCollection.collectionId),
92
- document: isNull(request.documentId) ? null : documentIdMap.get(request.documentId) ?? null,
104
+ collectionIds: requestAssignments.filter((requestCollection) => requestCollection.requestId == request.id).map((requestCollection) => requestCollection.collectionId)
93
105
  }));
94
106
  return {
95
107
  collections: collectionViews,
@@ -4,6 +4,7 @@ import { afterResolve } from '../../../injector/interfaces.js';
4
4
  import { Transactional } from '../../../orm/server/transactional.js';
5
5
  export declare class DocumentWorkflowService extends Transactional {
6
6
  #private;
7
+ private readonly documentService;
7
8
  readonly repository: import("../../../orm/server/repository.js").EntityRepository<DocumentWorkflow>;
8
9
  [afterResolve](_: unknown, { cancellationSignal }: AfterResolveContext<any>): void;
9
10
  loadLatestWorkflow(documentId: string): Promise<DocumentWorkflow>;
@@ -21,6 +21,8 @@ import { Transactional } from '../../../orm/server/transactional.js';
21
21
  import { Queue } from '../../../queue/queue.js';
22
22
  import { _throw } from '../../../utils/throw.js';
23
23
  import { isNotNull, isNull } from '../../../utils/type-guards.js';
24
+ import { desc, inArray } from 'drizzle-orm';
25
+ import { documentWorkflow } from '../schemas.js';
24
26
  import { DocumentCollectionService } from './document-collection.service.js';
25
27
  import { DocumentManagementAiService } from './document-management-ai.service.js';
26
28
  import { DocumentRequestService } from './document-request.service.js';
@@ -28,13 +30,13 @@ import { DocumentService } from './document.service.js';
28
30
  import { DocumentManagementSingleton } from './singleton.js';
29
31
  let DocumentWorkflowService = DocumentWorkflowService_1 = class DocumentWorkflowService extends Transactional {
30
32
  #documentManagementAiService = inject(DocumentManagementAiService);
31
- #documentService = inject(DocumentService);
32
33
  #documentCollectionService = inject(DocumentCollectionService);
33
34
  #documentRequestService = inject(DocumentRequestService);
34
35
  #documentAssignmentTaskRepository = injectRepository(DocumentAssignmentTask);
35
36
  #documentAssignmentScopeRepository = injectRepository(DocumentAssignmentScope);
36
37
  #queue = inject((Queue), { name: 'DocumentWorkflow', processTimeout: 5 * 60 * 1000, maxTries: 3 });
37
38
  #logger = inject(Logger, DocumentWorkflowService_1.name);
39
+ documentService = inject(DocumentService, undefined, { forwardRef: true });
38
40
  repository = injectRepository(DocumentWorkflow).withSession(this.session);
39
41
  [afterResolve](_, { cancellationSignal }) {
40
42
  if (this.isInTransaction) {
@@ -49,7 +51,16 @@ let DocumentWorkflowService = DocumentWorkflowService_1 = class DocumentWorkflow
49
51
  return await this.repository.tryLoadByQuery({ documentId }, { order: { 'metadata.createTimestamp': 'desc' } });
50
52
  }
51
53
  async loadLatestWorkflows(documentIds) {
52
- return await this.repository.loadManyByQuery({ documentId: { $in: documentIds } }, { distinct: ['metadata.createTimestamp', 'documentId'], order: { 'metadata.createTimestamp': 'desc' } });
54
+ const orderedDocumentWorkflows = this.repository.session.$with('orderedDocumentWorkflows').as((qb) => qb
55
+ .select()
56
+ .from(documentWorkflow)
57
+ .where(inArray(documentWorkflow.documentId, documentIds))
58
+ .orderBy(desc(documentWorkflow.createTimestamp)));
59
+ const latestWorkflows = await this.repository.session
60
+ .with(orderedDocumentWorkflows)
61
+ .selectDistinctOn([orderedDocumentWorkflows.documentId])
62
+ .from(orderedDocumentWorkflows);
63
+ return await this.repository.mapManyToEntity(latestWorkflows);
53
64
  }
54
65
  async proceedWorkflow(documentId, userId) {
55
66
  await this.transaction(async (tx) => {
@@ -104,11 +115,11 @@ let DocumentWorkflowService = DocumentWorkflowService_1 = class DocumentWorkflow
104
115
  }
105
116
  async processClassificationWorkflow(workflow) {
106
117
  const typeId = await this.#documentManagementAiService.classifyDocumentType(workflow.documentId);
107
- await this.#documentService.repository.update(workflow.documentId, { typeId, approval: DocumentApproval.Pending });
118
+ await this.documentService.repository.update(workflow.documentId, { typeId, approval: DocumentApproval.Pending });
108
119
  }
109
120
  async processExtractionWorkflow(workflow) {
110
121
  const extraction = await this.#documentManagementAiService.extractDocumentInformation(workflow.documentId);
111
- await this.#documentService.update(workflow.documentId, extraction);
122
+ await this.documentService.update(workflow.documentId, extraction);
112
123
  }
113
124
  async processAssignmentWorkflow(workflow) {
114
125
  const assignmentTask = await this.#documentAssignmentTaskRepository.loadByQuery({ documentId: workflow.documentId });