@ackplus/nest-file-storage 1.1.22 → 2.0.0

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 (86) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/MIGRATION.md +220 -0
  3. package/README.md +353 -547
  4. package/dist/index.d.ts +1 -4
  5. package/dist/index.js +1 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/constants.d.ts +3 -1
  8. package/dist/lib/constants.js +4 -2
  9. package/dist/lib/constants.js.map +1 -1
  10. package/dist/lib/driver-registry.d.ts +34 -0
  11. package/dist/lib/driver-registry.js +118 -0
  12. package/dist/lib/driver-registry.js.map +1 -0
  13. package/dist/lib/drivers/azure.driver.d.ts +21 -0
  14. package/dist/lib/drivers/azure.driver.js +91 -0
  15. package/dist/lib/drivers/azure.driver.js.map +1 -0
  16. package/dist/lib/drivers/driver.interface.d.ts +40 -0
  17. package/dist/lib/drivers/driver.interface.js +3 -0
  18. package/dist/lib/drivers/driver.interface.js.map +1 -0
  19. package/dist/lib/drivers/driver.util.d.ts +2 -0
  20. package/dist/lib/drivers/driver.util.js +15 -0
  21. package/dist/lib/drivers/driver.util.js.map +1 -0
  22. package/dist/lib/drivers/index.d.ts +7 -0
  23. package/dist/lib/drivers/index.js +39 -0
  24. package/dist/lib/drivers/index.js.map +1 -0
  25. package/dist/lib/drivers/local.driver.d.ts +15 -0
  26. package/dist/lib/drivers/local.driver.js +110 -0
  27. package/dist/lib/drivers/local.driver.js.map +1 -0
  28. package/dist/lib/drivers/s3.driver.d.ts +22 -0
  29. package/dist/lib/drivers/s3.driver.js +103 -0
  30. package/dist/lib/drivers/s3.driver.js.map +1 -0
  31. package/dist/lib/file-storage.service.d.ts +16 -5
  32. package/dist/lib/file-storage.service.js +60 -22
  33. package/dist/lib/file-storage.service.js.map +1 -1
  34. package/dist/lib/index.d.ts +9 -2
  35. package/dist/lib/index.js +15 -2
  36. package/dist/lib/index.js.map +1 -1
  37. package/dist/lib/interceptor/file-storage.interceptor.d.ts +7 -10
  38. package/dist/lib/interceptor/file-storage.interceptor.js +119 -112
  39. package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -1
  40. package/dist/lib/multer/driver-multer-engine.d.ts +18 -0
  41. package/dist/lib/multer/driver-multer-engine.js +91 -0
  42. package/dist/lib/multer/driver-multer-engine.js.map +1 -0
  43. package/dist/lib/nest-file-storage.module.d.ts +3 -3
  44. package/dist/lib/nest-file-storage.module.js +81 -44
  45. package/dist/lib/nest-file-storage.module.js.map +1 -1
  46. package/dist/lib/registry-holder.d.ts +6 -0
  47. package/dist/lib/registry-holder.js +26 -0
  48. package/dist/lib/registry-holder.js.map +1 -0
  49. package/dist/lib/tenant/tenant-from.d.ts +14 -0
  50. package/dist/lib/tenant/tenant-from.js +71 -0
  51. package/dist/lib/tenant/tenant-from.js.map +1 -0
  52. package/dist/lib/tenant/tenant.types.d.ts +20 -0
  53. package/dist/lib/tenant/tenant.types.js +3 -0
  54. package/dist/lib/tenant/tenant.types.js.map +1 -0
  55. package/dist/lib/types.d.ts +45 -35
  56. package/dist/lib/types.js.map +1 -1
  57. package/dist/lib/validation.d.ts +22 -0
  58. package/dist/lib/validation.js +98 -0
  59. package/dist/lib/validation.js.map +1 -0
  60. package/dist/tsconfig.build.tsbuildinfo +1 -1
  61. package/examples/1-basic-local-storage.example.ts +11 -7
  62. package/examples/10-testing.example.ts +60 -196
  63. package/examples/11-custom-driver.example.ts +82 -0
  64. package/examples/12-multi-tenant.example.ts +93 -0
  65. package/examples/2-s3-storage.example.ts +18 -16
  66. package/examples/3-azure-storage.example.ts +14 -12
  67. package/examples/4-upload-controller.example.ts +20 -55
  68. package/examples/5-custom-configuration.example.ts +37 -57
  69. package/examples/6-file-service.example.ts +34 -91
  70. package/examples/7-user-avatar.example.ts +37 -92
  71. package/examples/8-document-management.example.ts +45 -196
  72. package/examples/9-dynamic-storage.example.ts +29 -147
  73. package/examples/README.md +25 -107
  74. package/package.json +17 -4
  75. package/dist/lib/storage/azure.storage.d.ts +0 -18
  76. package/dist/lib/storage/azure.storage.js +0 -210
  77. package/dist/lib/storage/azure.storage.js.map +0 -1
  78. package/dist/lib/storage/local.storage.d.ts +0 -20
  79. package/dist/lib/storage/local.storage.js +0 -212
  80. package/dist/lib/storage/local.storage.js.map +0 -1
  81. package/dist/lib/storage/s3.storage.d.ts +0 -19
  82. package/dist/lib/storage/s3.storage.js +0 -241
  83. package/dist/lib/storage/s3.storage.js.map +0 -1
  84. package/dist/lib/storage.factory.d.ts +0 -8
  85. package/dist/lib/storage.factory.js +0 -46
  86. package/dist/lib/storage.factory.js.map +0 -1
@@ -1,22 +1,26 @@
1
1
  /**
2
2
  * Example 1: Basic Local Storage Configuration
3
- *
4
- * This example shows how to set up local file storage with basic configuration.
3
+ *
4
+ * The simplest setup register a single local driver and make it the default.
5
5
  */
6
6
 
7
7
  import { Module } from '@nestjs/common';
8
- import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
8
+ import { NestFileStorageModule, localDriver } from '@ackplus/nest-file-storage';
9
9
 
10
10
  @Module({
11
11
  imports: [
12
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
13
+ default: 'local',
14
+ drivers: {
15
+ local: localDriver({
16
+ rootPath: './uploads', // directory where files are written
17
+ baseUrl: 'http://localhost:3000/uploads', // URL prefix used by getUrl()
18
+ }),
17
19
  },
18
20
  }),
19
21
  ],
20
22
  })
21
23
  export class AppModule {}
22
24
 
25
+ // Note: `baseUrl` only builds the URL string — it does not serve files over HTTP.
26
+ // Add static serving (e.g. @nestjs/serve-static) or stream files via your own controller.
@@ -1,233 +1,97 @@
1
1
  /**
2
2
  * Example 10: Testing File Storage
3
- *
4
- * This example demonstrates how to write tests for file storage functionality.
3
+ *
4
+ * Two approaches: (a) a real local driver against a temp directory, and
5
+ * (b) an in-memory mock driver registered with defineDriver — no disk, no network.
5
6
  */
6
7
 
7
8
  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,
9
+ import {
10
+ NestFileStorageModule,
14
11
  FileStorageService,
15
- FileStorageEnum,
12
+ localDriver,
13
+ defineDriver,
14
+ StorageDriver,
15
+ UploadedFile,
16
16
  } from '@ackplus/nest-file-storage';
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
17
19
 
18
- describe('File Storage (e2e)', () => {
19
- let app: INestApplication;
20
- let storage: any;
20
+ // (a) Local driver against a temp directory ---------------------------------
21
+
22
+ describe('FileStorageService (local)', () => {
23
+ let fileStorage: FileStorageService;
21
24
  const testDir = './test-uploads';
22
25
 
23
26
  beforeAll(async () => {
24
- const moduleFixture: TestingModule = await Test.createTestingModule({
27
+ const moduleRef: TestingModule = await Test.createTestingModule({
25
28
  imports: [
26
29
  NestFileStorageModule.forRoot({
27
- storage: FileStorageEnum.LOCAL,
28
- localConfig: {
29
- rootPath: testDir,
30
- baseUrl: 'http://localhost:3000/test-uploads',
31
- },
30
+ default: 'local',
31
+ drivers: { local: localDriver({ rootPath: testDir, baseUrl: 'http://localhost/test' }) },
32
32
  }),
33
- // Import your controllers and services
34
33
  ],
35
34
  }).compile();
36
-
37
- app = moduleFixture.createNestApplication();
38
- await app.init();
39
-
40
- storage = await FileStorageService.getStorage();
35
+ fileStorage = moduleRef.get(FileStorageService);
41
36
  });
42
37
 
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();
38
+ afterAll(() => {
39
+ if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true });
49
40
  });
50
41
 
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
- });
42
+ it('puts, reads, copies, and deletes', async () => {
43
+ const content = Buffer.from('hello');
44
+ const put = await fileStorage.putFile(content, 'a/b.txt');
45
+ expect(put.key).toBe('a/b.txt');
46
+ expect(put.size).toBe(content.length);
61
47
 
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);
48
+ expect((await fileStorage.getFile('a/b.txt')).toString()).toBe('hello');
68
49
 
69
- expect(response.body.fileKeys).toHaveLength(2);
70
- });
50
+ const copy = await fileStorage.copyFile('a/b.txt', 'a/c.txt');
51
+ expect(copy.key).toBe('a/c.txt');
71
52
 
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
- });
53
+ await fileStorage.deleteFile('a/b.txt');
54
+ await expect(fileStorage.getFile('a/b.txt')).rejects.toThrow();
78
55
  });
56
+ });
79
57
 
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');
58
+ // (b) In-memory mock driver -------------------------------------------------
148
59
 
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
- });
60
+ class MemoryDriver implements StorageDriver {
61
+ private files = new Map<string, Buffer>();
62
+ async putFile(content: Buffer, key: string): Promise<UploadedFile> {
63
+ this.files.set(key, content);
64
+ const name = path.basename(key);
65
+ return { key, url: `mem://${key}`, originalName: name, fileName: name, size: content.length, fullPath: key };
66
+ }
67
+ async getFile(key: string): Promise<Buffer> {
68
+ const f = this.files.get(key);
69
+ if (!f) throw new Error('not found');
70
+ return f;
71
+ }
72
+ async deleteFile(key: string): Promise<void> { this.files.delete(key); }
73
+ async copyFile(src: string, dest: string): Promise<UploadedFile> { return this.putFile(await this.getFile(src), dest); }
74
+ getUrl(key: string): string { return `mem://${key}`; }
75
+ }
163
76
 
164
- /**
165
- * Unit Test Example
166
- */
167
- describe('FileService', () => {
168
- let service: any; // Your file service
77
+ describe('FileStorageService (mock driver)', () => {
78
+ let fileStorage: FileStorageService;
169
79
 
170
- beforeEach(async () => {
171
- const module: TestingModule = await Test.createTestingModule({
80
+ beforeAll(async () => {
81
+ const moduleRef = await Test.createTestingModule({
172
82
  imports: [
173
83
  NestFileStorageModule.forRoot({
174
- storage: FileStorageEnum.LOCAL,
175
- localConfig: {
176
- rootPath: './test-uploads',
177
- baseUrl: 'http://localhost:3000/test-uploads',
178
- },
84
+ default: 'memory',
85
+ drivers: { memory: defineDriver(MemoryDriver) },
179
86
  }),
180
87
  ],
181
- providers: [/* Your FileService */],
182
88
  }).compile();
183
-
184
- service = module.get(/* Your FileService */);
89
+ fileStorage = moduleRef.get(FileStorageService);
185
90
  });
186
91
 
187
- it('should be defined', () => {
188
- expect(service).toBeDefined();
92
+ it('stores in memory', async () => {
93
+ await fileStorage.putFile(Buffer.from('x'), 'k');
94
+ expect((await fileStorage.getFile('k')).toString()).toBe('x');
95
+ expect(await fileStorage.getUrl('k')).toBe('mem://k');
189
96
  });
190
-
191
- // Add more unit tests...
192
97
  });
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,82 @@
1
+ /**
2
+ * Example 11: Custom Storage Driver
3
+ *
4
+ * Implement the StorageDriver interface and register it with defineDriver. A custom driver works
5
+ * everywhere a built-in does — in the interceptor, the service, and tenant resolution.
6
+ *
7
+ * This example sketches a Google Cloud Storage driver; swap in any backend.
8
+ */
9
+
10
+ import { Module, Injectable } from '@nestjs/common';
11
+ import {
12
+ NestFileStorageModule,
13
+ defineDriver,
14
+ FileStorageService,
15
+ StorageDriver,
16
+ UploadedFile,
17
+ PutFileMeta,
18
+ } from '@ackplus/nest-file-storage';
19
+ // import { Storage } from '@google-cloud/storage';
20
+
21
+ class GcsDriver implements StorageDriver {
22
+ // private storage = new Storage();
23
+ constructor(private readonly opts: { bucket: string }) {}
24
+
25
+ // private file(key: string) { return this.storage.bucket(this.opts.bucket).file(key); }
26
+
27
+ async putFile(content: Buffer, key: string, meta?: PutFileMeta): Promise<UploadedFile> {
28
+ // await this.file(key).save(content, { contentType: meta?.contentType });
29
+ void content;
30
+ void meta;
31
+ const name = key.split('/').pop()!;
32
+ return { key, url: this.getUrl(key), originalName: name, fileName: name, size: content.length, fullPath: key };
33
+ }
34
+
35
+ async getFile(key: string): Promise<Buffer> {
36
+ // const [buf] = await this.file(key).download();
37
+ // return buf;
38
+ throw new Error(`download ${key} not implemented in this sketch`);
39
+ }
40
+
41
+ async deleteFile(key: string): Promise<void> {
42
+ // await this.file(key).delete();
43
+ void key;
44
+ }
45
+
46
+ async copyFile(src: string, dest: string): Promise<UploadedFile> {
47
+ // await this.file(src).copy(this.file(dest));
48
+ void src;
49
+ const name = dest.split('/').pop()!;
50
+ return { key: dest, url: this.getUrl(dest), originalName: name, fileName: name, size: 0, fullPath: dest };
51
+ }
52
+
53
+ getUrl(key: string): string {
54
+ return `https://storage.googleapis.com/${this.opts.bucket}/${key}`;
55
+ }
56
+ }
57
+
58
+ @Module({
59
+ imports: [
60
+ NestFileStorageModule.forRoot({
61
+ default: 'gcs',
62
+ drivers: {
63
+ // defineDriver(Class, opts) is sugar for () => new Class(opts).
64
+ gcs: defineDriver(GcsDriver, { bucket: 'my-bucket' }),
65
+ // For async setup, pass a plain factory instead:
66
+ // gcs: async () => new GcsDriver(await loadGcsConfig()),
67
+ },
68
+ }),
69
+ ],
70
+ })
71
+ export class AppModule {}
72
+
73
+ @Injectable()
74
+ export class ReportService {
75
+ constructor(private readonly fileStorage: FileStorageService) {}
76
+
77
+ // Use the custom driver by name — identical to a built-in.
78
+ async save(buffer: Buffer, key: string) {
79
+ const gcs = await this.fileStorage.getDriver('gcs');
80
+ return gcs.putFile(buffer, key);
81
+ }
82
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Example 12: Multi-Tenant Storage
3
+ *
4
+ * Route each upload to the right tenant's storage. "globex" gets a dedicated bucket; every other
5
+ * tenant shares one driver with a per-tenant key prefix. The tenant's driver is cached, so the DB
6
+ * lookup + client construction happen once per tenant — not per request.
7
+ */
8
+
9
+ import { Controller, Post, Body, UseInterceptors, Injectable, Module } from '@nestjs/common';
10
+ import {
11
+ NestFileStorageModule,
12
+ FileStorageInterceptor,
13
+ FileStorageService,
14
+ localDriver,
15
+ s3Driver,
16
+ tenantFrom,
17
+ } from '@ackplus/nest-file-storage';
18
+
19
+ // Your per-tenant storage config source (e.g. a database table).
20
+ @Injectable()
21
+ export class TenantStorageService {
22
+ async find(tenantId: string): Promise<{ dedicated: boolean; bucket?: string; region?: string; key?: string; secret?: string } | null> {
23
+ void tenantId;
24
+ return null; // look up the tenant's storage config
25
+ }
26
+ }
27
+
28
+ @Module({
29
+ providers: [TenantStorageService],
30
+ imports: [
31
+ NestFileStorageModule.forRootAsync({
32
+ inject: [TenantStorageService],
33
+ useFactory: (tenants: TenantStorageService) => ({
34
+ default: 'local',
35
+ drivers: {
36
+ local: localDriver({ rootPath: './uploads', baseUrl: 'http://localhost:3000/uploads' }),
37
+ },
38
+ tenant: {
39
+ // 1) Identify the tenant — try JWT, then subdomain, then a header.
40
+ resolve: tenantFrom.first(
41
+ tenantFrom.jwt('tenantId'),
42
+ tenantFrom.subdomain(),
43
+ tenantFrom.header('x-tenant-id'),
44
+ ),
45
+ // 2) Resolve a tenant -> storage. Cached by tenant id.
46
+ driver: async (tenantId) => {
47
+ const cfg = await tenants.find(tenantId);
48
+ if (cfg?.dedicated) {
49
+ return {
50
+ factory: s3Driver({
51
+ bucket: cfg.bucket!, region: cfg.region!,
52
+ accessKeyId: cfg.key!, secretAccessKey: cfg.secret!,
53
+ }),
54
+ };
55
+ }
56
+ return { use: 'local', prefix: `tenants/${tenantId}` };
57
+ },
58
+ cache: { ttlMs: 10 * 60_000, max: 500 },
59
+ fallback: 'default', // no tenant -> default driver ('error' -> 400)
60
+ },
61
+ }),
62
+ }),
63
+ ],
64
+ })
65
+ export class AppModule {}
66
+
67
+ @Controller('files')
68
+ export class TenantUploadController {
69
+ constructor(private readonly fileStorage: FileStorageService) {}
70
+
71
+ // No tenant-specific code — the interceptor routes to the tenant's storage automatically.
72
+ @Post('upload')
73
+ @UseInterceptors(FileStorageInterceptor('file', { mapToRequestBody: (file) => file }))
74
+ upload(@Body() body: any) {
75
+ return { message: 'Uploaded to the tenant storage', file: body.file };
76
+ }
77
+ }
78
+
79
+ // Programmatic access outside a request (jobs, URL generation):
80
+ @Injectable()
81
+ export class TenantReportService {
82
+ constructor(private readonly fileStorage: FileStorageService) {}
83
+
84
+ async urlFor(tenantId: string, key: string) {
85
+ const { driver } = await this.fileStorage.getTenantDriver(tenantId);
86
+ return driver.getUrl(key);
87
+ }
88
+
89
+ // When a tenant changes its storage settings, drop the cached driver:
90
+ invalidate(tenantId: string) {
91
+ this.fileStorage.getRegistry().invalidateTenant(tenantId);
92
+ }
93
+ }
@@ -1,31 +1,33 @@
1
1
  /**
2
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
3
+ *
4
+ * Configure AWS S3 (or an S3-compatible store like MinIO / R2 / Spaces).
5
+ * Requires: @aws-sdk/client-s3 @aws-sdk/s3-request-presigner (loaded lazily).
6
6
  */
7
7
 
8
8
  import { Module } from '@nestjs/common';
9
9
  import { ConfigModule, ConfigService } from '@nestjs/config';
10
- import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
10
+ import { NestFileStorageModule, s3Driver } from '@ackplus/nest-file-storage';
11
11
 
12
12
  @Module({
13
13
  imports: [
14
14
  ConfigModule.forRoot(),
15
- // Async configuration with ConfigService
16
15
  NestFileStorageModule.forRootAsync({
17
16
  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
17
+ inject: [ConfigService],
18
+ useFactory: (config: ConfigService) => ({
19
+ default: 's3',
20
+ drivers: {
21
+ s3: s3Driver({
22
+ accessKeyId: config.getOrThrow('AWS_ACCESS_KEY_ID'),
23
+ secretAccessKey: config.getOrThrow('AWS_SECRET_ACCESS_KEY'),
24
+ region: config.get('AWS_REGION', 'us-east-1'),
25
+ bucket: config.getOrThrow('AWS_BUCKET'),
26
+ cloudFrontUrl: config.get('AWS_CLOUDFRONT_URL'), // optional CDN for getUrl()
27
+ endpoint: config.get('S3_ENDPOINT'), // optional, for S3-compatible stores
28
+ }),
26
29
  },
27
30
  }),
28
- inject: [ConfigService],
29
31
  }),
30
32
  ],
31
33
  })
@@ -36,5 +38,5 @@ export class AppModule {}
36
38
  // AWS_SECRET_ACCESS_KEY=your-secret-key
37
39
  // AWS_REGION=us-east-1
38
40
  // AWS_BUCKET=your-bucket-name
39
- // AWS_CLOUDFRONT_URL=https://d1234567890.cloudfront.net (optional)
40
-
41
+ // AWS_CLOUDFRONT_URL=https://d1234567890.cloudfront.net (optional)
42
+ // S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com (optional, S3-compatible)
@@ -1,28 +1,30 @@
1
1
  /**
2
2
  * Example 3: Azure Blob Storage Configuration
3
- *
4
- * This example shows how to configure Azure Blob Storage.
5
- * Requires: @azure/storage-blob
3
+ *
4
+ * Requires: @azure/storage-blob (loaded lazily).
6
5
  */
7
6
 
8
7
  import { Module } from '@nestjs/common';
9
8
  import { ConfigModule, ConfigService } from '@nestjs/config';
10
- import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
9
+ import { NestFileStorageModule, azureDriver } from '@ackplus/nest-file-storage';
11
10
 
12
11
  @Module({
13
12
  imports: [
14
13
  ConfigModule.forRoot(),
15
14
  NestFileStorageModule.forRootAsync({
16
15
  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'),
16
+ inject: [ConfigService],
17
+ useFactory: (config: ConfigService) => ({
18
+ default: 'azure',
19
+ drivers: {
20
+ azure: azureDriver({
21
+ account: config.getOrThrow('AZURE_STORAGE_ACCOUNT'),
22
+ accountKey: config.getOrThrow('AZURE_STORAGE_KEY'),
23
+ container: config.get('AZURE_CONTAINER', 'uploads'),
24
+ cdnUrl: config.get('AZURE_CDN_URL'), // optional CDN for getSignedUrl()
25
+ }),
23
26
  },
24
27
  }),
25
- inject: [ConfigService],
26
28
  }),
27
29
  ],
28
30
  })
@@ -32,4 +34,4 @@ export class AppModule {}
32
34
  // AZURE_STORAGE_ACCOUNT=your-account-name
33
35
  // AZURE_STORAGE_KEY=your-account-key
34
36
  // AZURE_CONTAINER=uploads
35
-
37
+ // AZURE_CDN_URL=https://cdn.example.com (optional)