@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,26 +1,13 @@
1
1
  /**
2
2
  * Example 8: Document Management System
3
- *
4
- * This example demonstrates a complete document management system with
5
- * upload, download, listing, and deletion features.
3
+ *
4
+ * Upload, download, list, copy, and delete documents using the injected service.
6
5
  */
7
6
 
8
- import {
9
- Controller,
10
- Get,
11
- Post,
12
- Delete,
13
- Param,
14
- UseInterceptors,
15
- Body,
16
- Res,
17
- NotFoundException,
18
- StreamableFile,
19
- } from '@nestjs/common';
7
+ import { Controller, Get, Post, Delete, Param, UseInterceptors, Body, Res, NotFoundException, StreamableFile } from '@nestjs/common';
20
8
  import { Response } from 'express';
21
9
  import { FileStorageInterceptor, FileStorageService } from '@ackplus/nest-file-storage';
22
10
 
23
- // Document entity interface
24
11
  interface Document {
25
12
  id: number;
26
13
  name: string;
@@ -31,235 +18,97 @@ interface Document {
31
18
  uploadedAt: Date;
32
19
  }
33
20
 
34
- // Document service (example)
35
21
  class DocumentService {
36
- async create(data: Partial<Document>): Promise<Document> {
37
- // Save to database
38
- return {} as Document;
39
- }
40
-
41
- async findAll(): Promise<Document[]> {
42
- // Get all documents from database
43
- return [] as Document[];
44
- }
45
-
46
- async findById(id: number): Promise<Document> {
47
- // Get document from database
48
- return {} as Document;
49
- }
50
-
51
- async delete(id: number): Promise<void> {
52
- // Delete from database
53
- }
22
+ async create(_data: Partial<Document>): Promise<Document> { return {} as Document; }
23
+ async findAll(): Promise<Document[]> { return []; }
24
+ async findById(_id: number): Promise<Document> { return {} as Document; }
25
+ async delete(_id: number): Promise<void> {}
54
26
  }
55
27
 
56
28
  @Controller('documents')
57
29
  export class DocumentController {
58
- constructor(private readonly documentService: DocumentService) {}
30
+ constructor(
31
+ private readonly documentService: DocumentService,
32
+ private readonly fileStorage: FileStorageService,
33
+ ) {}
59
34
 
60
- /**
61
- * Upload single document
62
- */
63
35
  @Post('upload')
64
36
  @UseInterceptors(
65
- FileStorageInterceptor('document', {
66
- fileDist: () => 'documents',
67
- mapToRequestBody: (file) => file, // Return full file object
68
- })
37
+ FileStorageInterceptor('document', { fileDist: () => 'documents', mapToRequestBody: (file) => file })
69
38
  )
70
39
  async uploadDocument(@Body() body: any) {
71
- const uploadedFile = body.document;
72
-
73
- // Save document info to database
40
+ const file = body.document;
74
41
  const document = await this.documentService.create({
75
- name: uploadedFile.originalName,
76
- key: uploadedFile.key,
77
- url: uploadedFile.url,
78
- size: uploadedFile.size,
79
- mimetype: uploadedFile.mimetype,
80
- uploadedAt: new Date(),
42
+ name: file.originalName, key: file.key, url: file.url,
43
+ size: file.size, mimetype: file.mimetype, uploadedAt: new Date(),
81
44
  });
82
-
83
- return {
84
- message: 'Document uploaded successfully',
85
- document,
86
- };
45
+ return { message: 'Document uploaded successfully', document };
87
46
  }
88
47
 
89
- /**
90
- * Upload multiple documents
91
- */
92
48
  @Post('upload/multiple')
93
49
  @UseInterceptors(
94
- FileStorageInterceptor({
95
- type: 'array',
96
- fieldName: 'documents',
97
- maxCount: 10,
98
- }, {
99
- fileDist: () => 'documents',
100
- mapToRequestBody: (files) => files,
101
- })
50
+ FileStorageInterceptor(
51
+ { type: 'array', fieldName: 'documents', maxCount: 10 },
52
+ { fileDist: () => 'documents', mapToRequestBody: (files) => files }
53
+ )
102
54
  )
103
- async uploadMultipleDocuments(@Body() body: any) {
104
- const uploadedFiles = body.documents;
105
-
106
- // Save all documents to database
55
+ async uploadMany(@Body() body: any) {
107
56
  const documents = await Promise.all(
108
- uploadedFiles.map(file =>
57
+ body.documents.map((file: any) =>
109
58
  this.documentService.create({
110
- name: file.originalName,
111
- key: file.key,
112
- url: file.url,
113
- size: file.size,
114
- mimetype: file.mimetype,
115
- uploadedAt: new Date(),
59
+ name: file.originalName, key: file.key, url: file.url,
60
+ size: file.size, mimetype: file.mimetype, uploadedAt: new Date(),
116
61
  })
117
62
  )
118
63
  );
119
-
120
- return {
121
- message: `${documents.length} documents uploaded successfully`,
122
- documents,
123
- };
64
+ return { message: `${documents.length} documents uploaded successfully`, documents };
124
65
  }
125
66
 
126
- /**
127
- * Get all documents
128
- */
129
67
  @Get()
130
- async getAllDocuments() {
131
- const documents = await this.documentService.findAll();
132
- return { documents };
68
+ async getAll() {
69
+ return { documents: await this.documentService.findAll() };
133
70
  }
134
71
 
135
- /**
136
- * Download document
137
- */
138
72
  @Get(':id/download')
139
- async downloadDocument(
140
- @Param('id') id: number,
141
- @Res({ passthrough: true }) res: Response,
142
- ): Promise<StreamableFile> {
143
- // Get document from database
73
+ async download(@Param('id') id: number, @Res({ passthrough: true }) res: Response): Promise<StreamableFile> {
144
74
  const document = await this.documentService.findById(id);
145
- if (!document) {
146
- throw new NotFoundException('Document not found');
147
- }
75
+ if (!document) throw new NotFoundException('Document not found');
148
76
 
149
- // Get file from storage
150
- const storage = await FileStorageService.getStorage();
151
- const fileBuffer = await storage.getFile(document.key);
152
-
153
- // Set response headers
77
+ const buffer = await this.fileStorage.getFile(document.key);
154
78
  res.set({
155
79
  'Content-Type': document.mimetype || 'application/octet-stream',
156
80
  'Content-Disposition': `attachment; filename="${document.name}"`,
157
- 'Content-Length': document.size,
158
81
  });
159
-
160
- return new StreamableFile(fileBuffer);
82
+ return new StreamableFile(buffer);
161
83
  }
162
84
 
163
- /**
164
- * Get document URL (for preview or direct access)
165
- */
166
- @Get(':id/url')
167
- async getDocumentUrl(@Param('id') id: number) {
168
- const document = await this.documentService.findById(id);
169
- if (!document) {
170
- throw new NotFoundException('Document not found');
171
- }
172
-
173
- return {
174
- url: document.url,
175
- expiresIn: null, // Permanent URL
176
- };
177
- }
178
-
179
- /**
180
- * Get signed URL (for S3, temporary access)
181
- */
182
85
  @Get(':id/signed-url')
183
- async getSignedUrl(@Param('id') id: number) {
86
+ async signedUrl(@Param('id') id: number) {
184
87
  const document = await this.documentService.findById(id);
185
- if (!document) {
186
- throw new NotFoundException('Document not found');
187
- }
188
-
189
- const storage = await FileStorageService.getStorage();
190
-
191
- let url: string;
192
- if ('getSignedUrl' in storage) {
193
- // Generate signed URL (valid for 1 hour)
194
- url = await storage.getSignedUrl(document.key, { expiresIn: 3600 });
195
- } else {
196
- // Fallback to regular URL
197
- url = storage.getUrl(document.key);
198
- }
199
-
200
- return {
201
- url,
202
- expiresIn: 3600, // seconds
203
- };
88
+ if (!document) throw new NotFoundException('Document not found');
89
+ return { url: await this.fileStorage.getSignedUrl(document.key, { expiresIn: 3600 }), expiresIn: 3600 };
204
90
  }
205
91
 
206
- /**
207
- * Delete document
208
- */
209
92
  @Delete(':id')
210
- async deleteDocument(@Param('id') id: number) {
93
+ async remove(@Param('id') id: number) {
211
94
  const document = await this.documentService.findById(id);
212
- if (!document) {
213
- throw new NotFoundException('Document not found');
214
- }
215
-
216
- // Delete from storage
217
- const storage = await FileStorageService.getStorage();
218
- await storage.deleteFile(document.key);
219
-
220
- // Delete from database
95
+ if (!document) throw new NotFoundException('Document not found');
96
+ await this.fileStorage.deleteFile(document.key);
221
97
  await this.documentService.delete(id);
222
-
223
- return {
224
- message: 'Document deleted successfully',
225
- };
98
+ return { message: 'Document deleted successfully' };
226
99
  }
227
100
 
228
- /**
229
- * Copy document
230
- */
231
101
  @Post(':id/copy')
232
- async copyDocument(@Param('id') id: number) {
102
+ async copy(@Param('id') id: number) {
233
103
  const document = await this.documentService.findById(id);
234
- if (!document) {
235
- throw new NotFoundException('Document not found');
236
- }
237
-
238
- // Generate new key for the copy
239
- const timestamp = Date.now();
240
- const newKey = document.key.replace(
241
- /(\.[^.]+)$/,
242
- `-copy-${timestamp}$1`
243
- );
104
+ if (!document) throw new NotFoundException('Document not found');
244
105
 
245
- // Copy file in storage
246
- const storage = await FileStorageService.getStorage();
247
- const copiedFile = await storage.copyFile(document.key, newKey);
248
-
249
- // Save copy to database
106
+ const newKey = document.key.replace(/(\.[^.]+)$/, `-copy-${Date.now()}$1`);
107
+ const copied = await this.fileStorage.copyFile(document.key, newKey);
250
108
  const copiedDocument = await this.documentService.create({
251
- name: `${document.name} (Copy)`,
252
- key: copiedFile.key,
253
- url: copiedFile.url,
254
- size: copiedFile.size,
255
- mimetype: document.mimetype,
256
- uploadedAt: new Date(),
109
+ name: `${document.name} (Copy)`, key: copied.key, url: copied.url,
110
+ size: copied.size, mimetype: document.mimetype, uploadedAt: new Date(),
257
111
  });
258
-
259
- return {
260
- message: 'Document copied successfully',
261
- document: copiedDocument,
262
- };
112
+ return { message: 'Document copied successfully', document: copiedDocument };
263
113
  }
264
114
  }
265
-
@@ -1,175 +1,57 @@
1
1
  /**
2
2
  * Example 9: Dynamic Storage Selection
3
- *
4
- * This example demonstrates how to dynamically choose storage provider
5
- * based on request, user preferences, or business logic.
3
+ *
4
+ * Choose the storage driver per request (by query, user plan, file type, …). Register the
5
+ * candidate drivers in the module, then select one by name on the route or in the service.
6
6
  */
7
7
 
8
- import {
9
- Controller,
10
- Post,
11
- UseInterceptors,
12
- Body,
13
- Query,
14
- } from '@nestjs/common';
15
- import {
16
- FileStorageInterceptor,
17
- FileStorageEnum,
18
- } from '@ackplus/nest-file-storage';
8
+ import { Controller, Post, UseInterceptors, Body, Injectable } from '@nestjs/common';
9
+ import { FileStorageInterceptor, FileStorageService } from '@ackplus/nest-file-storage';
10
+
11
+ // Assumes the module registered drivers named 'local' and 's3' (see examples 1–2).
19
12
 
20
13
  @Controller('upload')
21
14
  export class DynamicStorageController {
22
- /**
23
- * Upload to specific storage based on query parameter
24
- * Example: POST /upload/dynamic?storage=s3
25
- */
26
- @Post('dynamic')
27
- @UseInterceptors(
28
- FileStorageInterceptor('file', {
29
- // Storage type will be determined at runtime
30
- })
31
- )
32
- uploadDynamic(
33
- @Body() body: any,
34
- @Query('storage') storage: string,
35
- ) {
36
- return {
37
- message: 'File uploaded successfully',
38
- storage,
39
- fileKey: body.file,
40
- };
41
- }
42
-
43
- /**
44
- * Upload large files to S3, small files to local storage
45
- */
46
- @Post('smart')
47
- @UseInterceptors(
48
- FileStorageInterceptor('file', {
49
- fileName: (file, req) => {
50
- const timestamp = Date.now();
51
- const ext = file.originalname.split('.').pop();
52
- return `${timestamp}-${file.originalname}`;
53
- },
54
- // Determine storage based on file size
55
- storageType: FileStorageEnum.LOCAL, // Can be overridden
56
- })
57
- )
58
- uploadSmart(@Body() body: any) {
59
- return {
60
- message: 'File uploaded successfully',
61
- fileKey: body.file,
62
- };
63
- }
64
-
65
- /**
66
- * Upload to S3 explicitly (override default storage)
67
- */
15
+ /** Force a specific registered driver for this route. */
68
16
  @Post('to-s3')
69
- @UseInterceptors(
70
- FileStorageInterceptor('file', {
71
- storageType: FileStorageEnum.S3,
72
- })
73
- )
17
+ @UseInterceptors(FileStorageInterceptor('file', { driver: 's3' }))
74
18
  uploadToS3(@Body() body: any) {
75
- return {
76
- message: 'File uploaded to S3',
77
- fileKey: body.file,
78
- };
79
- }
80
-
81
- /**
82
- * Upload to Azure explicitly
83
- */
84
- @Post('to-azure')
85
- @UseInterceptors(
86
- FileStorageInterceptor('file', {
87
- storageType: FileStorageEnum.AZURE,
88
- })
89
- )
90
- uploadToAzure(@Body() body: any) {
91
- return {
92
- message: 'File uploaded to Azure',
93
- fileKey: body.file,
94
- };
19
+ return { message: 'Uploaded to S3', fileKey: body.file };
95
20
  }
96
21
 
97
- /**
98
- * Upload to local storage explicitly
99
- */
100
- @Post('to-local')
22
+ /** Pick the driver dynamically from the request (e.g. premium users -> s3). */
23
+ @Post('smart')
101
24
  @UseInterceptors(
102
25
  FileStorageInterceptor('file', {
103
- storageType: FileStorageEnum.LOCAL,
26
+ driver: (req) => (req.user?.plan === 'premium' ? 's3' : 'local'),
104
27
  })
105
28
  )
106
- uploadToLocal(@Body() body: any) {
107
- return {
108
- message: 'File uploaded to local storage',
109
- fileKey: body.file,
110
- };
29
+ uploadSmart(@Body() body: any) {
30
+ return { message: 'Uploaded', fileKey: body.file };
111
31
  }
112
32
  }
113
33
 
114
34
  /**
115
- * Advanced: Custom logic for storage selection
35
+ * Programmatic selection in a service — resolve any registered driver by name.
116
36
  */
117
- import { Injectable } from '@nestjs/common';
118
- import { FileStorageService } from '@ackplus/nest-file-storage';
119
-
120
37
  @Injectable()
121
38
  export class SmartStorageService {
122
- /**
123
- * Select storage based on file size
124
- */
125
- async uploadFile(buffer: Buffer, fileName: string, fileSize: number) {
126
- // Use local storage for small files (< 10MB)
127
- // Use S3 for large files
128
- const storageType = fileSize < 10 * 1024 * 1024
129
- ? FileStorageEnum.LOCAL
130
- : FileStorageEnum.S3;
131
-
132
- const storage = await FileStorageService.getStorage(storageType);
133
- return await storage.putFile(buffer, fileName);
134
- }
135
-
136
- /**
137
- * Select storage based on file type
138
- */
139
- async uploadByType(buffer: Buffer, fileName: string, mimetype: string) {
140
- let storageType: FileStorageEnum;
39
+ constructor(private readonly fileStorage: FileStorageService) {}
141
40
 
142
- if (mimetype.startsWith('image/')) {
143
- // Store images on S3 with CloudFront CDN
144
- storageType = FileStorageEnum.S3;
145
- } else if (mimetype.startsWith('video/')) {
146
- // Store videos on Azure
147
- storageType = FileStorageEnum.AZURE;
148
- } else {
149
- // Store other files locally
150
- storageType = FileStorageEnum.LOCAL;
151
- }
152
-
153
- const storage = await FileStorageService.getStorage(storageType);
154
- return await storage.putFile(buffer, fileName);
41
+ /** Small files -> local, large files -> s3. */
42
+ async uploadBySize(buffer: Buffer, key: string) {
43
+ const driverName = buffer.length < 10 * 1024 * 1024 ? 'local' : 's3';
44
+ const driver = await this.fileStorage.getDriver(driverName);
45
+ return driver.putFile(buffer, key);
155
46
  }
156
47
 
157
- /**
158
- * Select storage based on user plan
159
- */
160
- async uploadForUser(
161
- buffer: Buffer,
162
- fileName: string,
163
- userPlan: 'free' | 'premium'
164
- ) {
165
- // Free users: local storage
166
- // Premium users: S3 with better performance
167
- const storageType = userPlan === 'premium'
168
- ? FileStorageEnum.S3
169
- : FileStorageEnum.LOCAL;
170
-
171
- const storage = await FileStorageService.getStorage(storageType);
172
- return await storage.putFile(buffer, fileName);
48
+ /** Route by content type. */
49
+ async uploadByType(buffer: Buffer, key: string, mimetype: string) {
50
+ const driverName = mimetype.startsWith('video/') ? 'azure' : 's3';
51
+ const driver = await this.fileStorage.getDriver(driverName);
52
+ return driver.putFile(buffer, key, { contentType: mimetype });
173
53
  }
174
54
  }
175
55
 
56
+ // Tip: for per-tenant routing, prefer the module's `tenant` config (see example 12) — it caches
57
+ // the resolved driver per tenant instead of selecting on every request.
@@ -1,122 +1,40 @@
1
1
  # Examples
2
2
 
3
- This directory contains comprehensive examples demonstrating various features and use cases of `@ackplus/nest-file-storage`.
3
+ Runnable-style snippets for `@ackplus/nest-file-storage` (v2). Copy what you need into your app.
4
4
 
5
- ## 📚 Available Examples
5
+ > New to the library? Start with the [docs](https://ack-solutions.github.io/nest-file-storage/) or the package [README](../README.md). Upgrading from v1? See [MIGRATION.md](../MIGRATION.md).
6
6
 
7
- ### Getting Started
7
+ ## Configuration
8
8
 
9
- 1. **[Basic Local Storage](./1-basic-local-storage.example.ts)**
10
- - Setting up local file storage
11
- - Basic configuration
12
- - Perfect for development
9
+ 1. **[Basic local storage](./1-basic-local-storage.example.ts)** — one `localDriver`, made the default.
10
+ 2. **[AWS S3](./2-s3-storage.example.ts)** `s3Driver` via `forRootAsync` + `ConfigService`.
11
+ 3. **[Azure Blob](./3-azure-storage.example.ts)** `azureDriver` via `forRootAsync` + `ConfigService`.
12
+ 5. **[Custom key generation](./5-custom-configuration.example.ts)** default `fileName` / `fileDist` on the driver.
13
13
 
14
- 2. **[AWS S3 Storage](./2-s3-storage.example.ts)**
15
- - Configure AWS S3
16
- - Async configuration with ConfigService
17
- - Environment variables setup
14
+ ## Uploading
18
15
 
19
- 3. **[Azure Blob Storage](./3-azure-storage.example.ts)**
20
- - Configure Azure Blob Storage
21
- - Connection string setup
22
- - Container management
16
+ 4. **[Upload controller](./4-upload-controller.example.ts)** — single / array / fields, and declarative validation.
17
+ 7. **[User avatar](./7-user-avatar.example.ts)** validation, old-file cleanup, DB update.
18
+ 8. **[Document management](./8-document-management.example.ts)** upload, download, copy, delete.
23
19
 
24
- ### Core Features
20
+ ## Programmatic & advanced
25
21
 
26
- 4. **[File Upload Controller](./4-upload-controller.example.ts)**
27
- - Single file upload
28
- - Multiple files upload
29
- - Multiple fields upload
30
- - File validation
31
- - Custom file mapping
22
+ 6. **[File service](./6-file-service.example.ts)** — the injectable `FileStorageService`.
23
+ 9. **[Dynamic storage](./9-dynamic-storage.example.ts)** pick a driver per route/request by name.
24
+ 11. **[Custom driver](./11-custom-driver.example.ts)** implement `StorageDriver` + `defineDriver`.
25
+ 12. **[Multi-tenant](./12-multi-tenant.example.ts)** route storage per tenant (shared + prefix or dedicated).
32
26
 
33
- 5. **[Custom Configuration](./5-custom-configuration.example.ts)**
34
- - Custom file naming
35
- - Custom directory structure
36
- - File transformations
37
- - Organized file storage
27
+ ## Testing
38
28
 
39
- 6. **[File Service](./6-file-service.example.ts)**
40
- - File operations (get, delete, copy)
41
- - Upload from local filesystem
42
- - Get public URLs
43
- - Generate signed URLs (S3)
44
- - Batch operations
45
-
46
- ### Real-World Use Cases
47
-
48
- 7. **[User Avatar Upload](./7-user-avatar.example.ts)**
49
- - Complete avatar upload feature
50
- - File validation
51
- - Old file cleanup
52
- - Database integration
53
-
54
- 8. **[Document Management System](./8-document-management.example.ts)**
55
- - Upload/download documents
56
- - Document listing
57
- - File copying
58
- - Signed URLs for secure access
59
-
60
- 9. **[Dynamic Storage Selection](./9-dynamic-storage.example.ts)**
61
- - Switch storage per request
62
- - Storage selection by file size
63
- - Storage selection by file type
64
- - Storage selection by user plan
65
-
66
- 10. **[Testing](./10-testing.example.ts)**
67
- - E2E testing examples
68
- - Unit testing
69
- - Mock storage implementation
70
- - Test setup and cleanup
71
-
72
- ## 🚀 How to Use These Examples
73
-
74
- ### 1. Copy Example Code
75
-
76
- Simply copy the relevant example code to your project:
77
-
78
- ```bash
79
- # Copy specific example
80
- cp examples/1-basic-local-storage.example.ts src/config/storage.config.ts
81
- ```
82
-
83
- ### 2. Modify for Your Needs
84
-
85
- Each example is well-documented with comments explaining:
86
- - What the code does
87
- - Configuration options
88
- - Environment variables needed
89
- - Expected behavior
90
-
91
- ### 3. Run Your Application
92
-
93
- ```bash
94
- npm run start:dev
95
- ```
96
-
97
- ## 💡 Tips
98
-
99
- - **Start Simple**: Begin with Example 1 (Local Storage) for development
100
- - **Environment Variables**: Use Example 2 or 3 for production with proper secrets management
101
- - **Validation**: See Example 4 for file type and size validation
102
- - **Custom Logic**: Examples 5-9 show advanced customization options
103
- - **Testing**: Example 10 provides complete testing setup
104
-
105
- ## 📖 More Resources
106
-
107
- - **[Main Documentation](../README.md)** - Complete feature guide
108
- - **[GitHub Issues](https://github.com/ack-solutions/nest-file-storage/issues)** - Report issues or request features
109
- - **[NPM Package](https://www.npmjs.com/package/@ackplus/nest-file-storage)** - Published package
110
-
111
- ## 🤝 Contributing
112
-
113
- Found a bug in an example? Want to add a new example? PRs are welcome!
114
-
115
- 1. Add your example file: `[number]-[name].example.ts`
116
- 2. Add it to this README
117
- 3. Submit a PR
29
+ 10. **[Testing](./10-testing.example.ts)** — a local driver against a temp dir, and an in-memory mock driver.
118
30
 
119
31
  ---
120
32
 
121
- **Need help?** Open an issue on [GitHub](https://github.com/ack-solutions/nest-file-storage/issues)
33
+ **Key v2 changes you'll see in these examples**
34
+
35
+ - Config is `{ default, drivers: { name: someDriver(...) } }` — not `{ storage, localConfig }`.
36
+ - The service is **injected** (`constructor(private fileStorage: FileStorageService)`), not static.
37
+ - Validation is **declarative** (`validation: { maxSize, allowedMimeTypes, ... }`), not thrown inside `fileName`.
38
+ - Custom storage is a first-class `StorageDriver` registered with `defineDriver` — and works everywhere.
122
39
 
40
+ Found an issue or want a new example? PRs welcome — open one on [GitHub](https://github.com/ack-solutions/nest-file-storage).