@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.
- package/LICENSE +22 -0
- package/README.md +658 -6
- package/{src → dist}/index.d.ts +0 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/{src → dist}/lib/constants.d.ts +0 -1
- package/{src → dist}/lib/constants.js +1 -0
- package/dist/lib/constants.js.map +1 -0
- package/{src → dist}/lib/file-storage.service.d.ts +0 -1
- package/{src → dist}/lib/file-storage.service.js +3 -3
- package/dist/lib/file-storage.service.js.map +1 -0
- package/{src → dist}/lib/index.d.ts +0 -1
- package/dist/lib/index.js +22 -0
- package/dist/lib/index.js.map +1 -0
- package/{src → dist}/lib/interceptor/file-storage.interceptor.d.ts +0 -4
- package/{src → dist}/lib/interceptor/file-storage.interceptor.js +5 -17
- package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -0
- package/{src → dist}/lib/nest-file-storage.module.d.ts +0 -1
- package/{src → dist}/lib/nest-file-storage.module.js +9 -3
- package/dist/lib/nest-file-storage.module.js.map +1 -0
- package/{src → dist}/lib/storage/azure.storage.d.ts +0 -1
- package/{src → dist}/lib/storage/azure.storage.js +49 -10
- package/dist/lib/storage/azure.storage.js.map +1 -0
- package/{src → dist}/lib/storage/local.storage.d.ts +0 -15
- package/{src → dist}/lib/storage/local.storage.js +44 -32
- package/dist/lib/storage/local.storage.js.map +1 -0
- package/{src → dist}/lib/storage/s3.storage.d.ts +0 -1
- package/{src → dist}/lib/storage/s3.storage.js +43 -8
- package/dist/lib/storage/s3.storage.js.map +1 -0
- package/{src → dist}/lib/storage.factory.d.ts +0 -1
- package/dist/lib/storage.factory.js +46 -0
- package/dist/lib/storage.factory.js.map +1 -0
- package/{src → dist}/lib/types.d.ts +0 -14
- package/{src → dist}/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/examples/1-basic-local-storage.example.ts +22 -0
- package/examples/10-testing.example.ts +233 -0
- package/examples/2-s3-storage.example.ts +40 -0
- package/examples/3-azure-storage.example.ts +35 -0
- package/examples/4-upload-controller.example.ts +117 -0
- package/examples/5-custom-configuration.example.ts +75 -0
- package/examples/6-file-service.example.ts +124 -0
- package/examples/7-user-avatar.example.ts +139 -0
- package/examples/8-document-management.example.ts +265 -0
- package/examples/9-dynamic-storage.example.ts +175 -0
- package/examples/README.md +122 -0
- package/package.json +86 -6
- package/src/index.d.ts.map +0 -1
- package/src/index.js +0 -7
- package/src/lib/constants.d.ts.map +0 -1
- package/src/lib/file-storage.service.d.ts.map +0 -1
- package/src/lib/index.d.ts.map +0 -1
- package/src/lib/index.js +0 -8
- package/src/lib/interceptor/file-storage.interceptor.d.ts.map +0 -1
- package/src/lib/nest-file-storage.module.d.ts.map +0 -1
- package/src/lib/storage/azure.storage.d.ts.map +0 -1
- package/src/lib/storage/local.storage.d.ts.map +0 -1
- package/src/lib/storage/s3.storage.d.ts.map +0 -1
- package/src/lib/storage.factory.d.ts.map +0 -1
- package/src/lib/storage.factory.js +0 -81
- 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
|
+
|