@ackplus/nest-file-storage 1.1.23 → 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.
- package/CHANGELOG.md +72 -0
- package/MIGRATION.md +220 -0
- package/README.md +311 -557
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +4 -2
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/driver-registry.d.ts +34 -0
- package/dist/lib/driver-registry.js +118 -0
- package/dist/lib/driver-registry.js.map +1 -0
- package/dist/lib/drivers/azure.driver.d.ts +21 -0
- package/dist/lib/drivers/azure.driver.js +91 -0
- package/dist/lib/drivers/azure.driver.js.map +1 -0
- package/dist/lib/drivers/driver.interface.d.ts +40 -0
- package/dist/lib/drivers/driver.interface.js +3 -0
- package/dist/lib/drivers/driver.interface.js.map +1 -0
- package/dist/lib/drivers/driver.util.d.ts +2 -0
- package/dist/lib/drivers/driver.util.js +15 -0
- package/dist/lib/drivers/driver.util.js.map +1 -0
- package/dist/lib/drivers/index.d.ts +7 -0
- package/dist/lib/drivers/index.js +39 -0
- package/dist/lib/drivers/index.js.map +1 -0
- package/dist/lib/drivers/local.driver.d.ts +15 -0
- package/dist/lib/drivers/local.driver.js +110 -0
- package/dist/lib/drivers/local.driver.js.map +1 -0
- package/dist/lib/drivers/s3.driver.d.ts +22 -0
- package/dist/lib/drivers/s3.driver.js +103 -0
- package/dist/lib/drivers/s3.driver.js.map +1 -0
- package/dist/lib/file-storage.service.d.ts +16 -5
- package/dist/lib/file-storage.service.js +60 -22
- package/dist/lib/file-storage.service.js.map +1 -1
- package/dist/lib/index.d.ts +9 -2
- package/dist/lib/index.js +15 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/interceptor/file-storage.interceptor.d.ts +7 -10
- package/dist/lib/interceptor/file-storage.interceptor.js +117 -112
- package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -1
- package/dist/lib/multer/driver-multer-engine.d.ts +18 -0
- package/dist/lib/multer/driver-multer-engine.js +91 -0
- package/dist/lib/multer/driver-multer-engine.js.map +1 -0
- package/dist/lib/nest-file-storage.module.d.ts +3 -3
- package/dist/lib/nest-file-storage.module.js +81 -44
- package/dist/lib/nest-file-storage.module.js.map +1 -1
- package/dist/lib/registry-holder.d.ts +6 -0
- package/dist/lib/registry-holder.js +26 -0
- package/dist/lib/registry-holder.js.map +1 -0
- package/dist/lib/tenant/tenant-from.d.ts +14 -0
- package/dist/lib/tenant/tenant-from.js +71 -0
- package/dist/lib/tenant/tenant-from.js.map +1 -0
- package/dist/lib/tenant/tenant.types.d.ts +20 -0
- package/dist/lib/tenant/tenant.types.js +3 -0
- package/dist/lib/tenant/tenant.types.js.map +1 -0
- package/dist/lib/types.d.ts +45 -35
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/validation.d.ts +22 -0
- package/dist/lib/validation.js +98 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/examples/1-basic-local-storage.example.ts +11 -7
- package/examples/10-testing.example.ts +60 -196
- package/examples/11-custom-driver.example.ts +82 -0
- package/examples/12-multi-tenant.example.ts +93 -0
- package/examples/2-s3-storage.example.ts +18 -16
- package/examples/3-azure-storage.example.ts +14 -12
- package/examples/4-upload-controller.example.ts +20 -55
- package/examples/5-custom-configuration.example.ts +37 -57
- package/examples/6-file-service.example.ts +34 -91
- package/examples/7-user-avatar.example.ts +37 -92
- package/examples/8-document-management.example.ts +45 -196
- package/examples/9-dynamic-storage.example.ts +29 -147
- package/examples/README.md +25 -107
- package/package.json +17 -4
- package/dist/lib/storage/azure.storage.d.ts +0 -18
- package/dist/lib/storage/azure.storage.js +0 -210
- package/dist/lib/storage/azure.storage.js.map +0 -1
- package/dist/lib/storage/local.storage.d.ts +0 -20
- package/dist/lib/storage/local.storage.js +0 -212
- package/dist/lib/storage/local.storage.js.map +0 -1
- package/dist/lib/storage/s3.storage.d.ts +0 -19
- package/dist/lib/storage/s3.storage.js +0 -241
- package/dist/lib/storage/s3.storage.js.map +0 -1
- package/dist/lib/storage.factory.d.ts +0 -8
- package/dist/lib/storage.factory.js +0 -46
- package/dist/lib/storage.factory.js.map +0 -1
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Example 8: Document Management System
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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(
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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
|
|
72
|
-
|
|
73
|
-
// Save document info to database
|
|
40
|
+
const file = body.document;
|
|
74
41
|
const document = await this.documentService.create({
|
|
75
|
-
name:
|
|
76
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
|
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
|
-
|
|
57
|
+
body.documents.map((file: any) =>
|
|
109
58
|
this.documentService.create({
|
|
110
|
-
name: file.originalName,
|
|
111
|
-
|
|
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
|
|
131
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
86
|
+
async signedUrl(@Param('id') id: number) {
|
|
184
87
|
const document = await this.documentService.findById(id);
|
|
185
|
-
if (!document)
|
|
186
|
-
|
|
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
|
|
93
|
+
async remove(@Param('id') id: number) {
|
|
211
94
|
const document = await this.documentService.findById(id);
|
|
212
|
-
if (!document)
|
|
213
|
-
|
|
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
|
|
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
|
-
|
|
246
|
-
const
|
|
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
|
-
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
driver: (req) => (req.user?.plan === 'premium' ? 's3' : 'local'),
|
|
104
27
|
})
|
|
105
28
|
)
|
|
106
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
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.
|
package/examples/README.md
CHANGED
|
@@ -1,122 +1,40 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runnable-style snippets for `@ackplus/nest-file-storage` (v2). Copy what you need into your app.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
7
|
+
## Configuration
|
|
8
8
|
|
|
9
|
-
1. **[Basic
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
- Configure AWS S3
|
|
16
|
-
- Async configuration with ConfigService
|
|
17
|
-
- Environment variables setup
|
|
14
|
+
## Uploading
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
20
|
+
## Programmatic & advanced
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
- Custom file naming
|
|
35
|
-
- Custom directory structure
|
|
36
|
-
- File transformations
|
|
37
|
-
- Organized file storage
|
|
27
|
+
## Testing
|
|
38
28
|
|
|
39
|
-
|
|
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
|
-
**
|
|
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).
|