@tstdl/base 0.92.131 → 0.92.134
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/api/server/api-request-token.provider.d.ts +3 -0
- package/api/server/api-request-token.provider.js +9 -0
- package/api/server/module.js +1 -1
- package/database/mongo/module.js +6 -6
- package/document-management/api/document-management.api.d.ts +20 -4
- package/document-management/api/document-management.api.js +9 -3
- package/document-management/server/api/document-management.api.d.ts +1 -0
- package/document-management/server/api/document-management.api.js +9 -2
- package/document-management/server/module.d.ts +1 -0
- package/document-management/server/module.js +1 -0
- package/document-management/server/services/document-file.service.d.ts +16 -0
- package/document-management/server/services/document-file.service.js +54 -24
- package/document-management/server/services/document-management-ancillary.service.d.ts +2 -2
- package/document-management/server/services/document.service.d.ts +5 -1
- package/document-management/server/services/document.service.js +12 -9
- package/document-management/service-models/document.service-model.d.ts +1 -0
- package/document-management/service-models/document.service-model.js +1 -0
- package/document-management/service-models/enriched/enriched-document-management-data.view.d.ts +2 -1
- package/document-management/service-models/enriched/enriched-document-management-data.view.js +10 -2
- package/examples/document-management/main.d.ts +1 -1
- package/examples/document-management/main.js +17 -8
- package/http/client/adapters/undici.adapter.js +3 -3
- package/http/client/http-client.js +29 -30
- package/http/http-body.js +4 -4
- package/http/http.error.d.ts +5 -1
- package/http/http.error.js +6 -6
- package/http/utils.js +4 -4
- package/injector/decorators.d.ts +1 -1
- package/injector/injector.d.ts +1 -1
- package/injector/interfaces.d.ts +1 -1
- package/injector/provider.d.ts +4 -4
- package/object-storage/object-storage.d.ts +37 -2
- package/object-storage/s3/s3.object-storage-provider.js +1 -1
- package/object-storage/s3/s3.object-storage.d.ts +6 -3
- package/object-storage/s3/s3.object-storage.js +86 -14
- package/object-storage/s3/s3.object.js +2 -3
- package/package.json +1 -1
- package/search-index/elastic/module.js +5 -5
- package/utils/cryptography.js +18 -18
- package/utils/object/object.d.ts +3 -2
- package/utils/object/object.js +5 -2
- package/utils/stream/size-limited-stream.js +1 -1
- package/utils/type-guards.d.ts +7 -1
- package/utils/type-guards.js +13 -1
|
@@ -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 };
|
package/api/server/module.js
CHANGED
|
@@ -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)) {
|
package/database/mongo/module.js
CHANGED
|
@@ -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.
|
|
119
|
-
return await this.#documentService.create(
|
|
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
|
}
|
|
@@ -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,
|
|
86
|
-
#filePreviewObjectStorage = inject(ObjectStorage,
|
|
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: { uploadKey: key } });
|
|
106
|
+
return { uploadId: id, uploadUrl: url };
|
|
91
107
|
}
|
|
92
108
|
async create(content, originalFileName) {
|
|
93
|
-
const
|
|
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['uploadKey']) {
|
|
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
|
|
106
|
-
|
|
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);
|
|
107
137
|
return documentFile;
|
|
108
138
|
});
|
|
109
139
|
}
|
|
110
140
|
async getContent(fileId) {
|
|
111
|
-
const
|
|
112
|
-
return this.#fileObjectStorage.getContent(
|
|
141
|
+
const objectKey = getDocumentFileKey(fileId);
|
|
142
|
+
return await this.#fileObjectStorage.getContent(objectKey);
|
|
113
143
|
}
|
|
114
144
|
getContentStream(fileId) {
|
|
115
|
-
const
|
|
116
|
-
return this.#fileObjectStorage.getContentStream(
|
|
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
|
|
153
|
+
const objectKey = getDocumentFileKey(fileId);
|
|
124
154
|
await this.createPreviewIfNotExists(fileId, page);
|
|
125
|
-
return this.#filePreviewObjectStorage.getContent(
|
|
155
|
+
return await this.#filePreviewObjectStorage.getContent(objectKey);
|
|
126
156
|
}
|
|
127
157
|
getPreviewStream(fileId, page = 1) {
|
|
128
158
|
return readableStreamFromPromise(async () => {
|
|
129
|
-
const
|
|
159
|
+
const objectKey = getDocumentFileKey(fileId);
|
|
130
160
|
await this.createPreviewIfNotExists(fileId, page);
|
|
131
|
-
return this.#filePreviewObjectStorage.getContentStream(
|
|
161
|
+
return this.#filePreviewObjectStorage.getContentStream(objectKey);
|
|
132
162
|
});
|
|
133
163
|
}
|
|
134
164
|
async getPreviewUrl(fileId, page = 1) {
|
|
135
|
-
const
|
|
165
|
+
const objectKey = getDocumentFileKey(fileId);
|
|
136
166
|
await this.createPreviewIfNotExists(fileId, page);
|
|
137
|
-
return this.#filePreviewObjectStorage.getDownloadUrl(
|
|
167
|
+
return await this.#filePreviewObjectStorage.getDownloadUrl(objectKey, currentTimestamp() + (5 * millisecondsPerMinute), {
|
|
138
168
|
'Response-Content-Type': 'image/jpeg',
|
|
139
169
|
});
|
|
140
170
|
}
|
|
@@ -175,9 +205,9 @@ 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
212
|
await this.#filePreviewObjectStorage.uploadObject(key, image, { contentLength: image.length, metadata: { 'Content-Type': 'image/jpeg' } });
|
|
183
213
|
}
|
|
@@ -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,
|
|
@@ -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
|
|
31
|
+
* Gets the subject from the request token.
|
|
32
32
|
* @param token The token of the request
|
|
33
33
|
*/
|
|
34
|
-
abstract
|
|
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.
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { type UpdatableDocumentProperties } from '../../../document-management/models/index.js';
|
|
2
2
|
import type { CreateDocumentParameters, SetDocumentPropertyParameters } from '../../../document-management/service-models/index.js';
|
|
3
3
|
import { Transactional } from '../../../orm/server/index.js';
|
|
4
|
+
import type { TypedOmit } from '../../../types.js';
|
|
4
5
|
import { Document } from '../../models/index.js';
|
|
5
6
|
export declare class DocumentService extends Transactional {
|
|
6
7
|
#private;
|
|
7
8
|
readonly repository: import("../../../orm/server/repository.js").EntityRepository<Document>;
|
|
8
|
-
create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }: CreateDocumentParameters,
|
|
9
|
+
create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }: TypedOmit<CreateDocumentParameters, 'uploadId'>, contentSource: Uint8Array | ReadableStream<Uint8Array> | {
|
|
10
|
+
uploadId: string;
|
|
11
|
+
uploadKey: string;
|
|
12
|
+
}, { createUserId }: {
|
|
9
13
|
createUserId?: string;
|
|
10
14
|
}): Promise<Document>;
|
|
11
15
|
update(id: string, update: Partial<Pick<Document, UpdatableDocumentProperties>> & {
|
|
@@ -18,7 +18,7 @@ import { toArray } from '../../../utils/array/index.js';
|
|
|
18
18
|
import { objectKeys } from '../../../utils/object/object.js';
|
|
19
19
|
import { readableStreamFromPromise } from '../../../utils/stream/from-promise.js';
|
|
20
20
|
import { tryIgnoreLogAsync } from '../../../utils/try-ignore.js';
|
|
21
|
-
import { isDefined, isString, isUndefined } from '../../../utils/type-guards.js';
|
|
21
|
+
import { isDefined, isNotReadableStream, isNotUint8Array, isString, isUndefined } from '../../../utils/type-guards.js';
|
|
22
22
|
import { Document } from '../../models/index.js';
|
|
23
23
|
import { DocumentCollectionService } from './document-collection.service.js';
|
|
24
24
|
import { DocumentFileService } from './document-file.service.js';
|
|
@@ -36,10 +36,13 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
|
|
|
36
36
|
#documentAssignmentScopeRepository = injectRepository(DocumentAssignmentScope);
|
|
37
37
|
#logger = inject(Logger, DocumentService_1.name);
|
|
38
38
|
repository = injectRepository(Document).withSession(this.session);
|
|
39
|
-
async create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata },
|
|
39
|
+
async create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }, contentSource, { createUserId }) {
|
|
40
40
|
const document = await this.transaction(async (tx) => {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const isUpload = isNotUint8Array(contentSource) && isNotReadableStream(contentSource);
|
|
42
|
+
const [documentFile, content] = isUpload
|
|
43
|
+
? await this.#documentFileService.withTransaction(tx).create(contentSource, originalFileName)
|
|
44
|
+
: [await this.#documentFileService.withTransaction(tx).create(contentSource, originalFileName), contentSource];
|
|
45
|
+
const pages = documentFile.mimeType.includes('pdf') ? await tryIgnoreLogAsync(this.#logger, async () => await getPdfPageCount(content), null) : null;
|
|
43
46
|
const document = await this.repository.withTransaction(tx).insert({
|
|
44
47
|
fileId: documentFile.id,
|
|
45
48
|
typeId: typeId ?? null,
|
|
@@ -80,7 +83,7 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
|
|
|
80
83
|
}
|
|
81
84
|
async getContent(documentOrId) {
|
|
82
85
|
const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
|
|
83
|
-
return this.#documentFileService.getContent(document.fileId);
|
|
86
|
+
return await this.#documentFileService.getContent(document.fileId);
|
|
84
87
|
}
|
|
85
88
|
getContentStream(documentOrId) {
|
|
86
89
|
return readableStreamFromPromise(async () => {
|
|
@@ -90,11 +93,11 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
|
|
|
90
93
|
}
|
|
91
94
|
async getContentUrl(documentOrId, download = false) {
|
|
92
95
|
const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
|
|
93
|
-
return this.#documentFileService.getContentUrl(document.fileId, document.title, download);
|
|
96
|
+
return await this.#documentFileService.getContentUrl(document.fileId, document.title, download);
|
|
94
97
|
}
|
|
95
98
|
async getPreview(documentOrId, page = 1) {
|
|
96
99
|
const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
|
|
97
|
-
return this.#documentFileService.getPreview(document.fileId, page);
|
|
100
|
+
return await this.#documentFileService.getPreview(document.fileId, page);
|
|
98
101
|
}
|
|
99
102
|
getPreviewStream(documentOrId, page = 1) {
|
|
100
103
|
return readableStreamFromPromise(async () => {
|
|
@@ -104,13 +107,13 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
|
|
|
104
107
|
}
|
|
105
108
|
async getPreviewUrl(documentOrId, page = 1) {
|
|
106
109
|
const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
|
|
107
|
-
return this.#documentFileService.getPreviewUrl(document.fileId, page);
|
|
110
|
+
return await this.#documentFileService.getPreviewUrl(document.fileId, page);
|
|
108
111
|
}
|
|
109
112
|
/**
|
|
110
113
|
* @returns collectionIds from either direct assignment or automatic assignment scope
|
|
111
114
|
*/
|
|
112
115
|
async createAssignment(documentId, assignment, transaction) {
|
|
113
|
-
return match(assignment)
|
|
116
|
+
return await match(assignment)
|
|
114
117
|
.with({ collections: P.select() }, async (collectionIds) => {
|
|
115
118
|
const collectionIdsArray = toArray(collectionIds);
|
|
116
119
|
await this.#documentCollectionService.withTransaction(transaction).assignDocument(documentId, collectionIdsArray);
|
|
@@ -5,6 +5,7 @@ export const metadataParameterSchema = optional(partial(pick(EntityMetadata, 'at
|
|
|
5
5
|
export const metadataParameterObjectSchema = object({ metadata: metadataParameterSchema });
|
|
6
6
|
export const setDocumentPropertyParametersSchema = assign(pick(DocumentPropertyValue, ['propertyId']), object({ value: union(string(), number(), boolean(), nullable(never())) }), metadataParameterObjectSchema);
|
|
7
7
|
export const createDocumentParametersSchema = assign(partial(pick(Document, ['typeId', 'title', 'subtitle', 'date', 'summary', 'tags', 'approval', 'comment'])), pick(DocumentFile, ['originalFileName']), object({
|
|
8
|
+
uploadId: string(),
|
|
8
9
|
assignment: union(object({ collections: oneOrMany(string(), { minimum: 1 }) }), object({ request: string() }), object({
|
|
9
10
|
automatic: object({
|
|
10
11
|
/** collection ids to assign in */
|
package/document-management/service-models/enriched/enriched-document-management-data.view.d.ts
CHANGED
|
@@ -28,9 +28,10 @@ export declare class EnrichedDocumentManagementData {
|
|
|
28
28
|
categories: Map<string, EnrichedDocumentCategory>;
|
|
29
29
|
types: Map<string, EnrichedDocumentType>;
|
|
30
30
|
};
|
|
31
|
+
get collectionIds(): string[];
|
|
31
32
|
get rootCollections(): EnrichedDocumentCollection[];
|
|
32
33
|
get collections(): EnrichedDocumentCollection[];
|
|
33
|
-
get
|
|
34
|
+
get collectionGroups(): EnrichedCollectionsGroup[];
|
|
34
35
|
get documents(): EnrichedDocument[];
|
|
35
36
|
get requests(): EnrichedDocumentRequest[];
|
|
36
37
|
get rootCategories(): EnrichedDocumentCategory[];
|
package/document-management/service-models/enriched/enriched-document-management-data.view.js
CHANGED
|
@@ -51,13 +51,16 @@ export class EnrichedDocumentManagementData {
|
|
|
51
51
|
categories: () => getEntityMap(this.categories),
|
|
52
52
|
types: () => getEntityMap(this.types),
|
|
53
53
|
});
|
|
54
|
+
get collectionIds() {
|
|
55
|
+
return this.rawData.collections.map((collection) => collection.id);
|
|
56
|
+
}
|
|
54
57
|
get rootCollections() {
|
|
55
58
|
return this.rawData.collections.filter((collection) => isNull(collection.parentId)).map((collection) => new EnrichedDocumentCollection(this, collection, null));
|
|
56
59
|
}
|
|
57
60
|
get collections() {
|
|
58
61
|
return this.rootCollections.flatMap((collection) => [collection, ...collection.childrenDeep]);
|
|
59
62
|
}
|
|
60
|
-
get
|
|
63
|
+
get collectionGroups() {
|
|
61
64
|
const groups = [...group(this.collections, (collection) => collection.group)];
|
|
62
65
|
return groups.map(([group, collections]) => new EnrichedCollectionsGroup(group, collections));
|
|
63
66
|
}
|
|
@@ -83,6 +86,11 @@ export class EnrichedDocumentManagementData {
|
|
|
83
86
|
this.rawData = data;
|
|
84
87
|
}
|
|
85
88
|
}
|
|
89
|
+
__decorate([
|
|
90
|
+
Memoize(),
|
|
91
|
+
__metadata("design:type", Array),
|
|
92
|
+
__metadata("design:paramtypes", [])
|
|
93
|
+
], EnrichedDocumentManagementData.prototype, "collectionIds", null);
|
|
86
94
|
__decorate([
|
|
87
95
|
Memoize(),
|
|
88
96
|
__metadata("design:type", Array),
|
|
@@ -97,7 +105,7 @@ __decorate([
|
|
|
97
105
|
Memoize(),
|
|
98
106
|
__metadata("design:type", Array),
|
|
99
107
|
__metadata("design:paramtypes", [])
|
|
100
|
-
], EnrichedDocumentManagementData.prototype, "
|
|
108
|
+
], EnrichedDocumentManagementData.prototype, "collectionGroups", null);
|
|
101
109
|
__decorate([
|
|
102
110
|
Memoize(),
|
|
103
111
|
__metadata("design:type", Array),
|
|
@@ -3,7 +3,7 @@ import type { DocumentCollection, DocumentWorkflowStep } from '../../document-ma
|
|
|
3
3
|
import { DocumentManagementAncillaryService, type DocumentCollectionMetadata } from '../../document-management/server/index.js';
|
|
4
4
|
export declare class ExampleDocumentManagementAncillaryService extends DocumentManagementAncillaryService {
|
|
5
5
|
_resolveMetadata(collections: DocumentCollection[]): DocumentCollectionMetadata[];
|
|
6
|
-
|
|
6
|
+
getSubject(_token?: unknown): string;
|
|
7
7
|
canCreateDocuments(_collectionId: string, _token?: unknown): boolean;
|
|
8
8
|
canUpdateDocuments(_collectionId: string, _token?: unknown): boolean;
|
|
9
9
|
canDeleteDocuments(_collectionId: string, _token?: unknown): boolean;
|