@tmlmobilidade/interfaces 20250906.141.55 → 20250908.1919.56

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.
@@ -1,6 +1,7 @@
1
1
  import { MongoCollectionClass } from '../../mongo-collection.js';
2
2
  import { CreateFileDto, File, UpdateFileDto } from '@tmlmobilidade/types';
3
3
  import { DeleteOptions, DeleteResult, IndexDescription, InsertOneOptions, WithId } from 'mongodb';
4
+ import { Readable } from 'node:stream';
4
5
  import { z } from 'zod';
5
6
  declare class FilesClass extends MongoCollectionClass<File, CreateFileDto, UpdateFileDto> {
6
7
  private static _instance;
@@ -44,11 +45,15 @@ declare class FilesClass extends MongoCollectionClass<File, CreateFileDto, Updat
44
45
  }): Promise<string>;
45
46
  /**
46
47
  * Uploads a file to the storage service and inserts it into the database.
47
- * @param file - The file to upload.
48
+ * @param file - The file to upload, either as a string, buffer, or readable stream.
48
49
  * @param createFileDto - The file type to create.
49
50
  * @returns The file that was uploaded.
50
51
  */
51
- upload(file: Buffer, createFileDto: CreateFileDto, options?: InsertOneOptions): Promise<File>;
52
+ upload(file: Buffer | Readable | ReadableStream | string, createFileDto: CreateFileDto & {
53
+ _id?: string;
54
+ }, options?: InsertOneOptions & {
55
+ override?: boolean;
56
+ }): Promise<File>;
52
57
  protected getCollectionIndexes(): IndexDescription[];
53
58
  protected getCollectionName(): string;
54
59
  protected getEnvName(): string;
@@ -151,14 +151,56 @@ class FilesClass extends MongoCollectionClass {
151
151
  }
152
152
  /**
153
153
  * Uploads a file to the storage service and inserts it into the database.
154
- * @param file - The file to upload.
154
+ * @param file - The file to upload, either as a string, buffer, or readable stream.
155
155
  * @param createFileDto - The file type to create.
156
156
  * @returns The file that was uploaded.
157
157
  */
158
158
  async upload(file, createFileDto, options) {
159
- const _id = generateRandomString({ length: 5 });
160
- await this.storageService.uploadFile(`${createFileDto.scope}/${createFileDto.resource_id}/${_id}.${Files.getFileExtension(createFileDto.name)}`, file, Files.getMimeTypeFromFileExtension(createFileDto.name));
161
- return await this.insertOne({ ...createFileDto, _id }, { options });
159
+ //
160
+ //
161
+ // A. Define variables
162
+ if (createFileDto._id && !options?.override) {
163
+ throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, 'When File ID is provided, override must be true');
164
+ }
165
+ const fileId = createFileDto._id || generateRandomString({ length: 5 });
166
+ const fileExtension = Files.getFileExtension(createFileDto.name);
167
+ const mimeType = Files.getMimeTypeFromFileExtension(createFileDto.name);
168
+ const filePath = `${createFileDto.scope}/${createFileDto.resource_id}/${fileId}.${fileExtension}`;
169
+ //
170
+ // C. Handle database transaction
171
+ const session = this.getMongoConnector().client.startSession();
172
+ let result;
173
+ try {
174
+ session.startTransaction();
175
+ //
176
+ // C.1. Handle file override if specified
177
+ if (options?.override) {
178
+ const existingFile = await this.findOne({ _id: fileId });
179
+ if (existingFile) {
180
+ const existingFileExtension = Files.getFileExtension(existingFile.name);
181
+ const existingFilePath = `${existingFile.scope}/${existingFile.resource_id}/${existingFile._id}.${existingFileExtension}`;
182
+ if (existingFilePath !== filePath) {
183
+ throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, 'File ID is provided, but the file path is different from the existing file', { cause: { existingFilePath, filePath } });
184
+ }
185
+ await super.deleteById(fileId);
186
+ }
187
+ }
188
+ //
189
+ // C.2. Upload file to storage
190
+ await this.storageService.uploadFile(filePath, file, mimeType);
191
+ //
192
+ // C.3. Insert file record
193
+ result = await this.insertOne({ ...createFileDto, _id: fileId }, { options });
194
+ await session.commitTransaction();
195
+ }
196
+ catch (error) {
197
+ await session.abortTransaction();
198
+ throw error;
199
+ }
200
+ finally {
201
+ session.endSession();
202
+ }
203
+ return result;
162
204
  }
163
205
  getCollectionIndexes() {
164
206
  return [
@@ -1,4 +1,5 @@
1
1
  import { IStorageProvider } from './storage.interface.js';
2
+ import { Readable } from 'node:stream';
2
3
  export interface OCIStorageProviderConfiguration {
3
4
  bucket_name: string;
4
5
  fingerprint: string;
@@ -25,5 +26,5 @@ export declare class OCIStorageProvider implements IStorageProvider {
25
26
  fileExists(key: string): Promise<boolean>;
26
27
  getFileUrl(key: string): Promise<string>;
27
28
  listFiles(prefix?: string): Promise<string[]>;
28
- uploadFile(key: string, body: Buffer, mimeType?: string): Promise<void>;
29
+ uploadFile(key: string, body: Buffer | Readable | ReadableStream, mimeType?: string): Promise<void>;
29
30
  }
@@ -88,17 +88,23 @@ export class OCIStorageProvider {
88
88
  async uploadFile(key, body, mimeType) {
89
89
  const isImage = mimeType === mimeTypes.png || mimeType === mimeTypes.jpg || mimeType === mimeTypes.jpeg || mimeType === mimeTypes.gif || mimeType === mimeTypes.svg;
90
90
  const uploadManager = new UploadManager(this.ociClient, { enforceMD5: true });
91
- await uploadManager.upload({
92
- content: {
93
- blob: new Blob([body], { type: mimeType }),
94
- },
95
- requestDetails: {
96
- bucketName: this.bucketName,
97
- contentDisposition: isImage ? 'inline' : 'attachment',
98
- contentType: mimeType,
99
- namespaceName: this.namespace,
100
- objectName: key,
101
- },
102
- });
91
+ try {
92
+ await uploadManager.upload({
93
+ content: body instanceof Buffer
94
+ ? { blob: new Blob([body], { type: mimeType }) }
95
+ : { stream: body },
96
+ requestDetails: {
97
+ bucketName: this.bucketName,
98
+ contentDisposition: isImage ? 'inline' : 'attachment',
99
+ contentType: mimeType,
100
+ namespaceName: this.namespace,
101
+ objectName: key,
102
+ },
103
+ });
104
+ }
105
+ catch (error) {
106
+ console.error('Error uploading file:', JSON.stringify(error, null, 2));
107
+ throw error;
108
+ }
103
109
  }
104
110
  }
@@ -60,5 +60,5 @@ export declare class S3StorageProvider implements IStorageProvider {
60
60
  * @param key - The file path and name in S3.
61
61
  * @param body - The content to upload, either as a string, buffer, or readable stream.
62
62
  */
63
- uploadFile(key: string, body: Buffer | Readable | string, mimeType?: string): Promise<void>;
63
+ uploadFile(key: string, body: Buffer | Readable | ReadableStream | string, mimeType?: string): Promise<void>;
64
64
  }
@@ -1,3 +1,4 @@
1
+ import { Readable } from 'node:stream';
1
2
  export interface IStorageProvider {
2
3
  copyFile(source: string, destination: string): Promise<void>;
3
4
  deleteFile(key: string): Promise<void>;
@@ -5,5 +6,5 @@ export interface IStorageProvider {
5
6
  fileExists(key: string): Promise<boolean>;
6
7
  getFileUrl(key: string): Promise<string>;
7
8
  listFiles(prefix?: string): Promise<string[]>;
8
- uploadFile(key: string, body: Buffer, mimeType?: string): Promise<void>;
9
+ uploadFile(key: string, body: Buffer | Readable | ReadableStream | string, mimeType?: string): Promise<void>;
9
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/interfaces",
3
- "version": "20250906.141.55",
3
+ "version": "20250908.1919.56",
4
4
  "author": "João de Vasconcelos & Jusi Monteiro",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "homepage": "https://github.com/tmlmobilidade/services#readme",