@ackplus/nest-file-storage 1.1.11 → 1.1.13

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 (62) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +658 -6
  3. package/{src → dist}/index.d.ts +0 -1
  4. package/dist/index.js +21 -0
  5. package/dist/index.js.map +1 -0
  6. package/{src → dist}/lib/constants.d.ts +0 -1
  7. package/{src → dist}/lib/constants.js +1 -0
  8. package/dist/lib/constants.js.map +1 -0
  9. package/{src → dist}/lib/file-storage.service.d.ts +0 -1
  10. package/{src → dist}/lib/file-storage.service.js +3 -3
  11. package/dist/lib/file-storage.service.js.map +1 -0
  12. package/{src → dist}/lib/index.d.ts +0 -1
  13. package/dist/lib/index.js +22 -0
  14. package/dist/lib/index.js.map +1 -0
  15. package/{src → dist}/lib/interceptor/file-storage.interceptor.d.ts +0 -4
  16. package/{src → dist}/lib/interceptor/file-storage.interceptor.js +5 -17
  17. package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -0
  18. package/{src → dist}/lib/nest-file-storage.module.d.ts +0 -1
  19. package/{src → dist}/lib/nest-file-storage.module.js +9 -3
  20. package/dist/lib/nest-file-storage.module.js.map +1 -0
  21. package/{src → dist}/lib/storage/azure.storage.d.ts +0 -1
  22. package/{src → dist}/lib/storage/azure.storage.js +49 -10
  23. package/dist/lib/storage/azure.storage.js.map +1 -0
  24. package/{src → dist}/lib/storage/local.storage.d.ts +0 -15
  25. package/{src → dist}/lib/storage/local.storage.js +44 -32
  26. package/dist/lib/storage/local.storage.js.map +1 -0
  27. package/{src → dist}/lib/storage/s3.storage.d.ts +0 -1
  28. package/{src → dist}/lib/storage/s3.storage.js +43 -8
  29. package/dist/lib/storage/s3.storage.js.map +1 -0
  30. package/{src → dist}/lib/storage.factory.d.ts +0 -1
  31. package/dist/lib/storage.factory.js +46 -0
  32. package/dist/lib/storage.factory.js.map +1 -0
  33. package/{src → dist}/lib/types.d.ts +0 -14
  34. package/{src → dist}/lib/types.js +1 -0
  35. package/dist/lib/types.js.map +1 -0
  36. package/dist/tsconfig.build.tsbuildinfo +1 -0
  37. package/examples/1-basic-local-storage.example.ts +22 -0
  38. package/examples/10-testing.example.ts +233 -0
  39. package/examples/2-s3-storage.example.ts +40 -0
  40. package/examples/3-azure-storage.example.ts +35 -0
  41. package/examples/4-upload-controller.example.ts +117 -0
  42. package/examples/5-custom-configuration.example.ts +75 -0
  43. package/examples/6-file-service.example.ts +124 -0
  44. package/examples/7-user-avatar.example.ts +139 -0
  45. package/examples/8-document-management.example.ts +265 -0
  46. package/examples/9-dynamic-storage.example.ts +175 -0
  47. package/examples/README.md +122 -0
  48. package/package.json +86 -6
  49. package/src/index.d.ts.map +0 -1
  50. package/src/index.js +0 -7
  51. package/src/lib/constants.d.ts.map +0 -1
  52. package/src/lib/file-storage.service.d.ts.map +0 -1
  53. package/src/lib/index.d.ts.map +0 -1
  54. package/src/lib/index.js +0 -8
  55. package/src/lib/interceptor/file-storage.interceptor.d.ts.map +0 -1
  56. package/src/lib/nest-file-storage.module.d.ts.map +0 -1
  57. package/src/lib/storage/azure.storage.d.ts.map +0 -1
  58. package/src/lib/storage/local.storage.d.ts.map +0 -1
  59. package/src/lib/storage/s3.storage.d.ts.map +0 -1
  60. package/src/lib/storage.factory.d.ts.map +0 -1
  61. package/src/lib/storage.factory.js +0 -81
  62. package/src/lib/types.d.ts.map +0 -1
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Example 1: Basic Local Storage Configuration
3
+ *
4
+ * This example shows how to set up local file storage with basic configuration.
5
+ */
6
+
7
+ import { Module } from '@nestjs/common';
8
+ import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
9
+
10
+ @Module({
11
+ imports: [
12
+ NestFileStorageModule.forRoot({
13
+ storage: FileStorageEnum.LOCAL,
14
+ localConfig: {
15
+ rootPath: './uploads', // Directory where files will be stored
16
+ baseUrl: 'http://localhost:3000/uploads', // Base URL for accessing files
17
+ },
18
+ }),
19
+ ],
20
+ })
21
+ export class AppModule {}
22
+
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Example 10: Testing File Storage
3
+ *
4
+ * This example demonstrates how to write tests for file storage functionality.
5
+ */
6
+
7
+ import { Test, TestingModule } from '@nestjs/testing';
8
+ import { INestApplication } from '@nestjs/common';
9
+ import * as request from 'supertest';
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import {
13
+ NestFileStorageModule,
14
+ FileStorageService,
15
+ FileStorageEnum,
16
+ } from '@ackplus/nest-file-storage';
17
+
18
+ describe('File Storage (e2e)', () => {
19
+ let app: INestApplication;
20
+ let storage: any;
21
+ const testDir = './test-uploads';
22
+
23
+ beforeAll(async () => {
24
+ const moduleFixture: TestingModule = await Test.createTestingModule({
25
+ imports: [
26
+ NestFileStorageModule.forRoot({
27
+ storage: FileStorageEnum.LOCAL,
28
+ localConfig: {
29
+ rootPath: testDir,
30
+ baseUrl: 'http://localhost:3000/test-uploads',
31
+ },
32
+ }),
33
+ // Import your controllers and services
34
+ ],
35
+ }).compile();
36
+
37
+ app = moduleFixture.createNestApplication();
38
+ await app.init();
39
+
40
+ storage = await FileStorageService.getStorage();
41
+ });
42
+
43
+ afterAll(async () => {
44
+ // Cleanup test directory
45
+ if (fs.existsSync(testDir)) {
46
+ fs.rmSync(testDir, { recursive: true, force: true });
47
+ }
48
+ await app.close();
49
+ });
50
+
51
+ describe('File Upload', () => {
52
+ it('should upload a single file', async () => {
53
+ const response = await request(app.getHttpServer())
54
+ .post('/upload/single')
55
+ .attach('file', Buffer.from('test content'), 'test.txt')
56
+ .expect(201);
57
+
58
+ expect(response.body.fileKey).toBeDefined();
59
+ expect(response.body.message).toBe('File uploaded successfully');
60
+ });
61
+
62
+ it('should upload multiple files', async () => {
63
+ const response = await request(app.getHttpServer())
64
+ .post('/upload/multiple')
65
+ .attach('files', Buffer.from('test 1'), 'test1.txt')
66
+ .attach('files', Buffer.from('test 2'), 'test2.txt')
67
+ .expect(201);
68
+
69
+ expect(response.body.fileKeys).toHaveLength(2);
70
+ });
71
+
72
+ it('should reject invalid file types', async () => {
73
+ await request(app.getHttpServer())
74
+ .post('/upload/image')
75
+ .attach('image', Buffer.from('not an image'), 'test.txt')
76
+ .expect(400);
77
+ });
78
+ });
79
+
80
+ describe('FileStorageService', () => {
81
+ const testKey = 'test/file.txt';
82
+ const testContent = Buffer.from('Hello, World!');
83
+
84
+ it('should upload a file', async () => {
85
+ const result = await storage.putFile(testContent, testKey);
86
+
87
+ expect(result.key).toBe(testKey);
88
+ expect(result.size).toBe(testContent.length);
89
+ expect(result.url).toContain(testKey);
90
+ });
91
+
92
+ it('should retrieve a file', async () => {
93
+ await storage.putFile(testContent, testKey);
94
+ const retrieved = await storage.getFile(testKey);
95
+
96
+ expect(retrieved.toString()).toBe(testContent.toString());
97
+ });
98
+
99
+ it('should get file URL', () => {
100
+ const url = storage.getUrl(testKey);
101
+ expect(url).toContain(testKey);
102
+ expect(url).toContain('http://localhost:3000');
103
+ });
104
+
105
+ it('should delete a file', async () => {
106
+ await storage.putFile(testContent, testKey);
107
+ await storage.deleteFile(testKey);
108
+
109
+ await expect(storage.getFile(testKey)).rejects.toThrow();
110
+ });
111
+
112
+ it('should copy a file', async () => {
113
+ const sourceKey = 'test/source.txt';
114
+ const targetKey = 'test/target.txt';
115
+
116
+ await storage.putFile(testContent, sourceKey);
117
+ const result = await storage.copyFile(sourceKey, targetKey);
118
+
119
+ expect(result.key).toBe(targetKey);
120
+
121
+ const copiedContent = await storage.getFile(targetKey);
122
+ expect(copiedContent.toString()).toBe(testContent.toString());
123
+
124
+ // Cleanup
125
+ await storage.deleteFile(sourceKey);
126
+ await storage.deleteFile(targetKey);
127
+ });
128
+
129
+ it('should handle multiple files', async () => {
130
+ const files = [
131
+ { key: 'test/file1.txt', content: Buffer.from('Content 1') },
132
+ { key: 'test/file2.txt', content: Buffer.from('Content 2') },
133
+ { key: 'test/file3.txt', content: Buffer.from('Content 3') },
134
+ ];
135
+
136
+ // Upload multiple files
137
+ await Promise.all(
138
+ files.map(file => storage.putFile(file.content, file.key))
139
+ );
140
+
141
+ // Verify all files exist
142
+ const results = await Promise.all(
143
+ files.map(file => storage.getFile(file.key))
144
+ );
145
+
146
+ expect(results).toHaveLength(3);
147
+ expect(results[0].toString()).toBe('Content 1');
148
+
149
+ // Cleanup
150
+ await Promise.all(
151
+ files.map(file => storage.deleteFile(file.key))
152
+ );
153
+ });
154
+ });
155
+
156
+ describe('File Service', () => {
157
+ // Test your custom file service
158
+ it('should handle file operations', async () => {
159
+ // Your file service tests here
160
+ });
161
+ });
162
+ });
163
+
164
+ /**
165
+ * Unit Test Example
166
+ */
167
+ describe('FileService', () => {
168
+ let service: any; // Your file service
169
+
170
+ beforeEach(async () => {
171
+ const module: TestingModule = await Test.createTestingModule({
172
+ imports: [
173
+ NestFileStorageModule.forRoot({
174
+ storage: FileStorageEnum.LOCAL,
175
+ localConfig: {
176
+ rootPath: './test-uploads',
177
+ baseUrl: 'http://localhost:3000/test-uploads',
178
+ },
179
+ }),
180
+ ],
181
+ providers: [/* Your FileService */],
182
+ }).compile();
183
+
184
+ service = module.get(/* Your FileService */);
185
+ });
186
+
187
+ it('should be defined', () => {
188
+ expect(service).toBeDefined();
189
+ });
190
+
191
+ // Add more unit tests...
192
+ });
193
+
194
+ /**
195
+ * Mock Storage for Testing
196
+ */
197
+ class MockStorage {
198
+ private files = new Map<string, Buffer>();
199
+
200
+ async putFile(content: Buffer, key: string) {
201
+ this.files.set(key, content);
202
+ return {
203
+ key,
204
+ url: `http://mock/${key}`,
205
+ size: content.length,
206
+ fileName: path.basename(key),
207
+ originalName: path.basename(key),
208
+ fullPath: key,
209
+ };
210
+ }
211
+
212
+ async getFile(key: string): Promise<Buffer> {
213
+ const file = this.files.get(key);
214
+ if (!file) {
215
+ throw new Error('File not found');
216
+ }
217
+ return file;
218
+ }
219
+
220
+ async deleteFile(key: string): Promise<void> {
221
+ this.files.delete(key);
222
+ }
223
+
224
+ async copyFile(oldKey: string, newKey: string) {
225
+ const file = await this.getFile(oldKey);
226
+ return await this.putFile(file, newKey);
227
+ }
228
+
229
+ getUrl(key: string): string {
230
+ return `http://mock/${key}`;
231
+ }
232
+ }
233
+
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Example 2: AWS S3 Storage Configuration
3
+ *
4
+ * This example shows how to configure AWS S3 for file storage.
5
+ * Requires: @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
6
+ */
7
+
8
+ import { Module } from '@nestjs/common';
9
+ import { ConfigModule, ConfigService } from '@nestjs/config';
10
+ import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
11
+
12
+ @Module({
13
+ imports: [
14
+ ConfigModule.forRoot(),
15
+ // Async configuration with ConfigService
16
+ NestFileStorageModule.forRootAsync({
17
+ imports: [ConfigModule],
18
+ useFactory: async (configService: ConfigService) => ({
19
+ storage: FileStorageEnum.S3,
20
+ s3Config: {
21
+ accessKeyId: configService.get('AWS_ACCESS_KEY_ID'),
22
+ secretAccessKey: configService.get('AWS_SECRET_ACCESS_KEY'),
23
+ region: configService.get('AWS_REGION', 'us-east-1'),
24
+ bucket: configService.get('AWS_BUCKET'),
25
+ cloudFrontUrl: configService.get('AWS_CLOUDFRONT_URL'), // Optional CloudFront URL
26
+ },
27
+ }),
28
+ inject: [ConfigService],
29
+ }),
30
+ ],
31
+ })
32
+ export class AppModule {}
33
+
34
+ // Environment variables (.env file):
35
+ // AWS_ACCESS_KEY_ID=your-access-key
36
+ // AWS_SECRET_ACCESS_KEY=your-secret-key
37
+ // AWS_REGION=us-east-1
38
+ // AWS_BUCKET=your-bucket-name
39
+ // AWS_CLOUDFRONT_URL=https://d1234567890.cloudfront.net (optional)
40
+
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Example 3: Azure Blob Storage Configuration
3
+ *
4
+ * This example shows how to configure Azure Blob Storage.
5
+ * Requires: @azure/storage-blob
6
+ */
7
+
8
+ import { Module } from '@nestjs/common';
9
+ import { ConfigModule, ConfigService } from '@nestjs/config';
10
+ import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
11
+
12
+ @Module({
13
+ imports: [
14
+ ConfigModule.forRoot(),
15
+ NestFileStorageModule.forRootAsync({
16
+ imports: [ConfigModule],
17
+ useFactory: async (configService: ConfigService) => ({
18
+ storage: FileStorageEnum.AZURE,
19
+ azureConfig: {
20
+ account: configService.get('AZURE_STORAGE_ACCOUNT'),
21
+ accountKey: configService.get('AZURE_STORAGE_KEY'),
22
+ container: configService.get('AZURE_CONTAINER', 'uploads'),
23
+ },
24
+ }),
25
+ inject: [ConfigService],
26
+ }),
27
+ ],
28
+ })
29
+ export class AppModule {}
30
+
31
+ // Environment variables (.env file):
32
+ // AZURE_STORAGE_ACCOUNT=your-account-name
33
+ // AZURE_STORAGE_KEY=your-account-key
34
+ // AZURE_CONTAINER=uploads
35
+
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Example 4: File Upload Controller
3
+ *
4
+ * This example demonstrates different file upload scenarios using the FileStorageInterceptor.
5
+ */
6
+
7
+ import { Controller, Post, UseInterceptors, Body, BadRequestException } from '@nestjs/common';
8
+ import { FileStorageInterceptor } from '@ackplus/nest-file-storage';
9
+
10
+ @Controller('upload')
11
+ export class UploadController {
12
+ /**
13
+ * Single file upload
14
+ * POST /upload/single
15
+ * Form field: "file"
16
+ */
17
+ @Post('single')
18
+ @UseInterceptors(FileStorageInterceptor('file'))
19
+ uploadSingle(@Body() body: any) {
20
+ return {
21
+ message: 'File uploaded successfully',
22
+ fileKey: body.file, // File key is automatically added to body
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Multiple files upload (same field name)
28
+ * POST /upload/multiple
29
+ * Form field: "files" (multiple files)
30
+ */
31
+ @Post('multiple')
32
+ @UseInterceptors(
33
+ FileStorageInterceptor({
34
+ type: 'array',
35
+ fieldName: 'files',
36
+ maxCount: 10, // Maximum 10 files
37
+ })
38
+ )
39
+ uploadMultiple(@Body() body: any) {
40
+ return {
41
+ message: 'Files uploaded successfully',
42
+ fileKeys: body.files, // Array of file keys
43
+ count: body.files.length,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Multiple fields with different files
49
+ * POST /upload/fields
50
+ * Form fields: "avatar" (1 file), "photos" (up to 5 files)
51
+ */
52
+ @Post('fields')
53
+ @UseInterceptors(
54
+ FileStorageInterceptor({
55
+ type: 'fields',
56
+ fields: [
57
+ { name: 'avatar', maxCount: 1 },
58
+ { name: 'photos', maxCount: 5 },
59
+ ],
60
+ })
61
+ )
62
+ uploadFields(@Body() body: any) {
63
+ return {
64
+ message: 'Files uploaded successfully',
65
+ avatar: body.avatar, // Single file key
66
+ photos: body.photos, // Array of file keys
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Upload with custom file information
72
+ * Returns full file object instead of just the key
73
+ */
74
+ @Post('with-details')
75
+ @UseInterceptors(
76
+ FileStorageInterceptor('file', {
77
+ mapToRequestBody: (file) => {
78
+ // Return the full file object
79
+ return file;
80
+ },
81
+ })
82
+ )
83
+ uploadWithDetails(@Body() body: any) {
84
+ return {
85
+ message: 'File uploaded successfully',
86
+ file: body.file, // Full file object with key, url, size, etc.
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Upload with validation
92
+ */
93
+ @Post('image')
94
+ @UseInterceptors(
95
+ FileStorageInterceptor('image', {
96
+ fileName: (file) => {
97
+ // Validate file type
98
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
99
+ if (!allowedTypes.includes(file.mimetype)) {
100
+ throw new BadRequestException('Only image files are allowed');
101
+ }
102
+
103
+ // Generate filename with timestamp
104
+ const timestamp = Date.now();
105
+ const ext = file.originalname.split('.').pop();
106
+ return `image-${timestamp}.${ext}`;
107
+ },
108
+ })
109
+ )
110
+ uploadImage(@Body() body: any) {
111
+ return {
112
+ message: 'Image uploaded successfully',
113
+ imageKey: body.image,
114
+ };
115
+ }
116
+ }
117
+
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Example 5: Custom Configuration
3
+ *
4
+ * This example shows advanced configuration options including custom file naming,
5
+ * directory structure, and file transformations.
6
+ */
7
+
8
+ import { Module } from '@nestjs/common';
9
+ import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
10
+ import { v4 as uuidv4 } from 'uuid';
11
+ import * as path from 'path';
12
+
13
+ @Module({
14
+ imports: [
15
+ NestFileStorageModule.forRoot({
16
+ storage: FileStorageEnum.LOCAL,
17
+ localConfig: {
18
+ rootPath: './uploads',
19
+ baseUrl: 'http://localhost:3000/uploads',
20
+
21
+ // Custom file naming function
22
+ fileName: (file, req) => {
23
+ // Generate unique filename: uuid-originalname.ext
24
+ const uuid = uuidv4();
25
+ const ext = path.extname(file.originalname);
26
+ const name = path.basename(file.originalname, ext);
27
+ return `${uuid}-${name}${ext}`;
28
+ },
29
+
30
+ // Custom directory structure: year/month/day
31
+ fileDist: (file, req) => {
32
+ const date = new Date();
33
+ const year = date.getFullYear();
34
+ const month = String(date.getMonth() + 1).padStart(2, '0');
35
+ const day = String(date.getDate()).padStart(2, '0');
36
+
37
+ // Optional: organize by file type
38
+ const isImage = file.mimetype?.startsWith('image/');
39
+ const type = isImage ? 'images' : 'documents';
40
+
41
+ return path.join(type, String(year), month, day);
42
+ },
43
+
44
+ // Transform uploaded file object (return only necessary fields)
45
+ transformUploadedFileObject: (file) => {
46
+ return {
47
+ key: file.key,
48
+ url: file.url,
49
+ size: file.size,
50
+ mimetype: file.mimetype,
51
+ originalName: file.originalName,
52
+ };
53
+ },
54
+ },
55
+ }),
56
+ ],
57
+ })
58
+ export class AppModule {}
59
+
60
+ /**
61
+ * Result directory structure:
62
+ * uploads/
63
+ * ├── images/
64
+ * │ └── 2024/
65
+ * │ └── 01/
66
+ * │ └── 15/
67
+ * │ ├── uuid1-photo1.jpg
68
+ * │ └── uuid2-photo2.png
69
+ * └── documents/
70
+ * └── 2024/
71
+ * └── 01/
72
+ * └── 15/
73
+ * └── uuid3-document.pdf
74
+ */
75
+
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Example 6: File Service
3
+ *
4
+ * This example demonstrates how to use FileStorageService to perform
5
+ * file operations programmatically.
6
+ */
7
+
8
+ import { Injectable, NotFoundException } from '@nestjs/common';
9
+ import { FileStorageService } from '@ackplus/nest-file-storage';
10
+ import * as fs from 'fs';
11
+
12
+ @Injectable()
13
+ export class FileService {
14
+ /**
15
+ * Get file content as Buffer
16
+ */
17
+ async getFile(key: string): Promise<Buffer> {
18
+ try {
19
+ const storage = await FileStorageService.getStorage();
20
+ return await storage.getFile(key);
21
+ } catch (error) {
22
+ throw new NotFoundException(`File not found: ${key}`);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Delete a file
28
+ */
29
+ async deleteFile(key: string): Promise<void> {
30
+ const storage = await FileStorageService.getStorage();
31
+ await storage.deleteFile(key);
32
+ }
33
+
34
+ /**
35
+ * Copy a file to a new location
36
+ */
37
+ async copyFile(oldKey: string, newKey: string) {
38
+ const storage = await FileStorageService.getStorage();
39
+ return await storage.copyFile(oldKey, newKey);
40
+ }
41
+
42
+ /**
43
+ * Upload a file from local filesystem
44
+ */
45
+ async uploadFromLocal(localPath: string, storageKey: string) {
46
+ const fileBuffer = await fs.promises.readFile(localPath);
47
+ const storage = await FileStorageService.getStorage();
48
+ return await storage.putFile(fileBuffer, storageKey);
49
+ }
50
+
51
+ /**
52
+ * Get public URL for a file
53
+ */
54
+ async getFileUrl(key: string): Promise<string> {
55
+ const storage = await FileStorageService.getStorage();
56
+ return storage.getUrl(key);
57
+ }
58
+
59
+ /**
60
+ * Get signed URL (for S3)
61
+ * Useful for temporary access to private files
62
+ */
63
+ async getSignedUrl(key: string, expiresIn: number = 3600): Promise<string> {
64
+ const storage = await FileStorageService.getStorage();
65
+
66
+ // Check if storage supports signed URLs (S3)
67
+ if ('getSignedUrl' in storage) {
68
+ return await storage.getSignedUrl(key, { expiresIn });
69
+ }
70
+
71
+ // Fallback to regular URL for other storage types
72
+ return storage.getUrl(key);
73
+ }
74
+
75
+ /**
76
+ * Upload file from Buffer
77
+ */
78
+ async uploadBuffer(buffer: Buffer, key: string, mimetype?: string) {
79
+ const storage = await FileStorageService.getStorage();
80
+ const result = await storage.putFile(buffer, key);
81
+
82
+ return {
83
+ key: result.key,
84
+ url: result.url,
85
+ size: result.size,
86
+ mimetype,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Check if file exists
92
+ */
93
+ async fileExists(key: string): Promise<boolean> {
94
+ try {
95
+ const storage = await FileStorageService.getStorage();
96
+ await storage.getFile(key);
97
+ return true;
98
+ } catch (error) {
99
+ return false;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Delete multiple files
105
+ */
106
+ async deleteMultipleFiles(keys: string[]): Promise<void> {
107
+ const storage = await FileStorageService.getStorage();
108
+ await Promise.all(keys.map(key => storage.deleteFile(key)));
109
+ }
110
+
111
+ /**
112
+ * Get file path (Local storage only)
113
+ */
114
+ async getFilePath(key: string): Promise<string | undefined> {
115
+ const storage = await FileStorageService.getStorage();
116
+
117
+ if ('path' in storage) {
118
+ return storage.path(key);
119
+ }
120
+
121
+ return undefined;
122
+ }
123
+ }
124
+