@flusys/nestjs-storage 4.1.0 → 5.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/README.md +71 -483
- package/cjs/config/message-keys.js +2 -52
- package/cjs/controllers/storage-config.controller.js +2 -2
- package/cjs/docs/storage-swagger.config.js +5 -3
- package/cjs/middlewares/file-serve.middleware.js +5 -5
- package/cjs/providers/azure-provider.optional.js +1 -1
- package/cjs/providers/s3-provider.optional.js +1 -1
- package/cjs/providers/sftp-provider.optional.js +1 -1
- package/cjs/providers/storage-factory.service.js +4 -4
- package/cjs/services/file-manager.service.js +3 -39
- package/cjs/services/storage-config.service.js +14 -3
- package/cjs/services/storage-datasource.provider.js +1 -6
- package/cjs/services/upload.service.js +12 -6
- package/config/message-keys.d.ts +0 -96
- package/fesm/config/message-keys.js +2 -47
- package/fesm/controllers/storage-config.controller.js +2 -2
- package/fesm/docs/storage-swagger.config.js +5 -3
- package/fesm/middlewares/file-serve.middleware.js +5 -5
- package/fesm/providers/azure-provider.optional.js +1 -1
- package/fesm/providers/s3-provider.optional.js +1 -1
- package/fesm/providers/sftp-provider.optional.js +1 -1
- package/fesm/providers/storage-factory.service.js +4 -4
- package/fesm/services/file-manager.service.js +3 -39
- package/fesm/services/storage-config.service.js +14 -3
- package/fesm/services/storage-datasource.provider.js +1 -6
- package/fesm/services/upload.service.js +12 -6
- package/package.json +3 -3
- package/services/file-manager.service.d.ts +1 -6
- package/services/storage-config.service.d.ts +2 -0
- package/services/storage-datasource.provider.d.ts +1 -2
- package/services/upload.service.d.ts +1 -1
package/README.md
CHANGED
|
@@ -1,78 +1,9 @@
|
|
|
1
1
|
# @flusys/nestjs-storage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Multi-provider file storage for NestJS — Local disk, AWS S3, Azure Blob, and SFTP with folder management, image compression, and multi-tenant support.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@flusys/nestjs-storage)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nestjs.com/)
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
-
[](https://nodejs.org/)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Table of Contents
|
|
14
|
-
|
|
15
|
-
- [Overview](#overview)
|
|
16
|
-
- [Features](#features)
|
|
17
|
-
- [Compatibility](#compatibility)
|
|
18
|
-
- [Installation](#installation)
|
|
19
|
-
- [Quick Start](#quick-start)
|
|
20
|
-
- [Module Registration](#module-registration)
|
|
21
|
-
- [forRoot (Sync)](#forroot-sync)
|
|
22
|
-
- [forRootAsync (Factory)](#forrootasync-factory)
|
|
23
|
-
- [Configuration Reference](#configuration-reference)
|
|
24
|
-
- [Feature Toggles](#feature-toggles)
|
|
25
|
-
- [Storage Providers](#storage-providers)
|
|
26
|
-
- [Local](#local-provider)
|
|
27
|
-
- [AWS S3](#aws-s3)
|
|
28
|
-
- [Azure Blob Storage](#azure-blob-storage)
|
|
29
|
-
- [SFTP](#sftp)
|
|
30
|
-
- [Custom Provider](#custom-provider)
|
|
31
|
-
- [API Endpoints](#api-endpoints)
|
|
32
|
-
- [Entities](#entities)
|
|
33
|
-
- [File Serving (Local)](#file-serving-local)
|
|
34
|
-
- [Image Compression](#image-compression)
|
|
35
|
-
- [File Validation](#file-validation)
|
|
36
|
-
- [Exported Services](#exported-services)
|
|
37
|
-
- [Programmatic Usage](#programmatic-usage)
|
|
38
|
-
- [Troubleshooting](#troubleshooting)
|
|
39
|
-
- [License](#license)
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Overview
|
|
44
|
-
|
|
45
|
-
`@flusys/nestjs-storage` provides a unified API for managing files across multiple storage backends. Provider configurations are stored in the database — switch providers without code changes. Cloud providers (S3, Azure, SFTP) are dynamically imported so you only pay the install cost for what you use.
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Features
|
|
50
|
-
|
|
51
|
-
- **4 storage backends** — Local disk, AWS S3, Azure Blob Storage, SFTP
|
|
52
|
-
- **Pluggable providers** — Register custom providers via `StorageProviderRegistry`
|
|
53
|
-
- **Folder hierarchy** — Organize files in folders with parent-child relationships
|
|
54
|
-
- **Image compression** — Automatic optimization via `sharp` (JPEG, PNG, WebP, AVIF, TIFF, GIF)
|
|
55
|
-
- **Presigned URLs** — Time-limited signed URLs for S3 and Azure with TTL caching
|
|
56
|
-
- **File validation** — Magic-byte validation prevents file-type spoofing
|
|
57
|
-
- **Path traversal protection** — All file paths sanitized before disk access
|
|
58
|
-
- **Company scoping** — Optional `companyId` filtering on all queries
|
|
59
|
-
- **Multi-tenant** — Per-tenant DataSource isolation
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Compatibility
|
|
64
|
-
|
|
65
|
-
| Package | Version |
|
|
66
|
-
|---------|---------|
|
|
67
|
-
| `@flusys/nestjs-core` | `^4.0.0` |
|
|
68
|
-
| `@flusys/nestjs-shared` | `^4.0.0` |
|
|
69
|
-
| `sharp` | `^0.33.0` |
|
|
70
|
-
| `uuid` | `^9.0.0` |
|
|
71
|
-
| `mime-types` | `^2.0.0` |
|
|
72
|
-
| `@aws-sdk/client-s3` | `^3.0.0` *(optional)* |
|
|
73
|
-
| `@azure/storage-blob` | `^12.0.0` *(optional)* |
|
|
74
|
-
| `ssh2-sftp-client` | `^9.0.0` *(optional)* |
|
|
75
|
-
| Node.js | `>= 18.x` |
|
|
76
7
|
|
|
77
8
|
---
|
|
78
9
|
|
|
@@ -82,28 +13,21 @@
|
|
|
82
13
|
npm install @flusys/nestjs-storage @flusys/nestjs-shared @flusys/nestjs-core sharp uuid mime-types
|
|
83
14
|
```
|
|
84
15
|
|
|
85
|
-
Optional cloud
|
|
16
|
+
Optional cloud SDKs — install only what you need:
|
|
86
17
|
|
|
87
18
|
```bash
|
|
88
|
-
#
|
|
89
|
-
npm install @
|
|
90
|
-
|
|
91
|
-
# Azure Blob Storage
|
|
92
|
-
npm install @azure/storage-blob
|
|
93
|
-
|
|
94
|
-
# SFTP
|
|
95
|
-
npm install ssh2-sftp-client
|
|
96
|
-
npm install -D @types/ssh2-sftp-client
|
|
19
|
+
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner # S3
|
|
20
|
+
npm install @azure/storage-blob # Azure
|
|
21
|
+
npm install ssh2-sftp-client # SFTP
|
|
97
22
|
```
|
|
98
23
|
|
|
99
|
-
|
|
24
|
+
## 1. Register the Module
|
|
100
25
|
|
|
101
|
-
|
|
26
|
+
### Synchronous
|
|
102
27
|
|
|
103
|
-
|
|
28
|
+
#### Mode 1: Single Database
|
|
104
29
|
|
|
105
30
|
```typescript
|
|
106
|
-
import { Module } from '@nestjs/common';
|
|
107
31
|
import { StorageModule } from '@flusys/nestjs-storage';
|
|
108
32
|
|
|
109
33
|
@Module({
|
|
@@ -117,17 +41,17 @@ import { StorageModule } from '@flusys/nestjs-storage';
|
|
|
117
41
|
},
|
|
118
42
|
config: {
|
|
119
43
|
defaultDatabaseConfig: {
|
|
120
|
-
type: '
|
|
44
|
+
type: 'mysql',
|
|
121
45
|
host: process.env.DB_HOST,
|
|
122
|
-
port: Number(process.env.DB_PORT ??
|
|
46
|
+
port: Number(process.env.DB_PORT ?? 3306),
|
|
123
47
|
username: process.env.DB_USER,
|
|
124
48
|
password: process.env.DB_PASSWORD,
|
|
125
49
|
database: process.env.DB_NAME,
|
|
126
50
|
},
|
|
127
|
-
maxFileSize: 10 * 1024 * 1024,
|
|
128
|
-
allowedFileTypes: ['image/*', 'application/pdf'],
|
|
51
|
+
maxFileSize: 10 * 1024 * 1024, // 10 MB (default: 100 MB)
|
|
52
|
+
allowedFileTypes: ['image/*', 'application/pdf'], // default: ['*/*']
|
|
129
53
|
localStoragePath: './uploads',
|
|
130
|
-
appUrl: process.env.APP_URL
|
|
54
|
+
appUrl: process.env.APP_URL,
|
|
131
55
|
},
|
|
132
56
|
}),
|
|
133
57
|
],
|
|
@@ -135,49 +59,43 @@ import { StorageModule } from '@flusys/nestjs-storage';
|
|
|
135
59
|
export class AppModule {}
|
|
136
60
|
```
|
|
137
61
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
import { FileServeMiddleware } from '@flusys/nestjs-storage';
|
|
142
|
-
|
|
143
|
-
@Module({})
|
|
144
|
-
export class AppModule implements NestModule {
|
|
145
|
-
configure(consumer: MiddlewareConsumer) {
|
|
146
|
-
// Serves GET /storage/upload/file/* from local disk
|
|
147
|
-
consumer.apply(FileServeMiddleware).forRoutes('storage/upload/file');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Module Registration
|
|
155
|
-
|
|
156
|
-
### forRoot (Sync)
|
|
62
|
+
#### Mode 2: Multi-Tenant
|
|
157
63
|
|
|
158
64
|
```typescript
|
|
159
65
|
StorageModule.forRoot({
|
|
160
|
-
global
|
|
161
|
-
includeController
|
|
66
|
+
global: true,
|
|
67
|
+
includeController: true,
|
|
162
68
|
bootstrapAppConfig: {
|
|
163
|
-
databaseMode: '
|
|
164
|
-
enableCompanyFeature:
|
|
165
|
-
}
|
|
69
|
+
databaseMode: 'multi-tenant',
|
|
70
|
+
enableCompanyFeature: true,
|
|
71
|
+
},
|
|
166
72
|
config: {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
73
|
+
tenantDefaultDatabaseConfig: {
|
|
74
|
+
type: 'mysql',
|
|
75
|
+
host: process.env.TENANT_DB_HOST,
|
|
76
|
+
port: Number(process.env.TENANT_DB_PORT ?? 3306),
|
|
77
|
+
username: process.env.TENANT_DB_USER,
|
|
78
|
+
password: process.env.TENANT_DB_PASSWORD,
|
|
79
|
+
database: process.env.TENANT_DB_NAME,
|
|
80
|
+
},
|
|
81
|
+
tenants: [
|
|
82
|
+
{ id: 'tenant-a', database: 'tenant_a_db' },
|
|
83
|
+
{ id: 'tenant-b', database: 'tenant_b_db' },
|
|
84
|
+
],
|
|
85
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
86
|
+
localStoragePath: './uploads',
|
|
87
|
+
appUrl: process.env.APP_URL,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
174
90
|
```
|
|
175
91
|
|
|
176
|
-
###
|
|
92
|
+
### Asynchronous (with ConfigService)
|
|
177
93
|
|
|
178
94
|
```typescript
|
|
179
|
-
import { ConfigService } from '@nestjs/config';
|
|
95
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
96
|
+
import { StorageModule, ITenantDatabaseConfig } from '@flusys/nestjs-storage';
|
|
180
97
|
|
|
98
|
+
// Single database
|
|
181
99
|
StorageModule.forRootAsync({
|
|
182
100
|
global: true,
|
|
183
101
|
includeController: true,
|
|
@@ -188,7 +106,7 @@ StorageModule.forRootAsync({
|
|
|
188
106
|
imports: [ConfigModule],
|
|
189
107
|
useFactory: (configService: ConfigService) => ({
|
|
190
108
|
defaultDatabaseConfig: {
|
|
191
|
-
type: '
|
|
109
|
+
type: 'mysql',
|
|
192
110
|
host: configService.get('DB_HOST'),
|
|
193
111
|
port: configService.get<number>('DB_PORT'),
|
|
194
112
|
username: configService.get('DB_USER'),
|
|
@@ -200,378 +118,48 @@ StorageModule.forRootAsync({
|
|
|
200
118
|
localStoragePath: configService.get('UPLOAD_PATH', './uploads'),
|
|
201
119
|
}),
|
|
202
120
|
inject: [ConfigService],
|
|
203
|
-
})
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Configuration Reference
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
interface IStorageModuleConfig extends IDataSourceServiceOptions {
|
|
212
|
-
/** Maximum file size in bytes (default: 100MB) */
|
|
213
|
-
maxFileSize?: number;
|
|
214
|
-
|
|
215
|
-
/** Allowed MIME types. Use '*/*' for all. (default: ['*/*']) */
|
|
216
|
-
allowedFileTypes?: string[];
|
|
217
|
-
|
|
218
|
-
/** Local storage base directory (default: './uploads') */
|
|
219
|
-
localStoragePath?: string;
|
|
220
|
-
|
|
221
|
-
/** Application base URL for building file URLs (local provider) */
|
|
222
|
-
appUrl?: string;
|
|
223
|
-
}
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
---
|
|
121
|
+
});
|
|
227
122
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## Storage Providers
|
|
239
|
-
|
|
240
|
-
### Local Provider
|
|
241
|
-
|
|
242
|
-
Built-in, no extra SDK required. Files stored on the server filesystem.
|
|
243
|
-
|
|
244
|
-
Create a `StorageConfig` record:
|
|
245
|
-
|
|
246
|
-
```json
|
|
247
|
-
POST /storage/storage-config/insert
|
|
248
|
-
{
|
|
249
|
-
"name": "Local Storage",
|
|
250
|
-
"storage": "local",
|
|
251
|
-
"config": {},
|
|
252
|
-
"isActive": true,
|
|
253
|
-
"isDefault": true
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
File URLs format: `{appUrl}/storage/upload/file/{path}`
|
|
258
|
-
|
|
259
|
-
### AWS S3
|
|
260
|
-
|
|
261
|
-
**1. Install the SDK:**
|
|
262
|
-
```bash
|
|
263
|
-
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**2. Register the provider at startup (before module init):**
|
|
267
|
-
```typescript
|
|
268
|
-
import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
|
|
269
|
-
import { S3Provider } from '@flusys/nestjs-storage/providers/s3-provider.optional';
|
|
270
|
-
|
|
271
|
-
StorageProviderRegistry.register(FileLocationEnum.AWS, S3Provider);
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**3. Create a StorageConfig:**
|
|
275
|
-
```json
|
|
276
|
-
POST /storage/storage-config/insert
|
|
277
|
-
{
|
|
278
|
-
"name": "Production S3",
|
|
279
|
-
"storage": "aws",
|
|
280
|
-
"config": {
|
|
281
|
-
"region": "us-east-1",
|
|
282
|
-
"bucket": "my-app-files",
|
|
283
|
-
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
284
|
-
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
285
|
-
},
|
|
286
|
-
"isDefault": true
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### Azure Blob Storage
|
|
291
|
-
|
|
292
|
-
**1. Install the SDK:**
|
|
293
|
-
```bash
|
|
294
|
-
npm install @azure/storage-blob
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**2. Register:**
|
|
298
|
-
```typescript
|
|
299
|
-
import { AzureProvider } from '@flusys/nestjs-storage/providers/azure-provider.optional';
|
|
300
|
-
StorageProviderRegistry.register(FileLocationEnum.AZURE, AzureProvider);
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
**3. Create a StorageConfig:**
|
|
304
|
-
```json
|
|
305
|
-
POST /storage/storage-config/insert
|
|
306
|
-
{
|
|
307
|
-
"name": "Azure Blob",
|
|
308
|
-
"storage": "azure",
|
|
309
|
-
"config": {
|
|
310
|
-
"connectionString": "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net",
|
|
311
|
-
"containerName": "my-files"
|
|
123
|
+
// Multi-tenant
|
|
124
|
+
StorageModule.forRootAsync({
|
|
125
|
+
global: true,
|
|
126
|
+
includeController: true,
|
|
127
|
+
bootstrapAppConfig: {
|
|
128
|
+
databaseMode: 'multi-tenant',
|
|
129
|
+
enableCompanyFeature: true,
|
|
312
130
|
},
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
**3. Create a StorageConfig:**
|
|
331
|
-
```json
|
|
332
|
-
POST /storage/storage-config/insert
|
|
333
|
-
{
|
|
334
|
-
"name": "Legacy SFTP",
|
|
335
|
-
"storage": "sftp",
|
|
336
|
-
"config": {
|
|
337
|
-
"host": "sftp.example.com",
|
|
338
|
-
"port": 22,
|
|
339
|
-
"username": "sftpuser",
|
|
340
|
-
"password": "secret",
|
|
341
|
-
"remotePath": "/uploads"
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Custom Provider
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
import { IStorageProvider, StorageProviderRegistry } from '@flusys/nestjs-storage';
|
|
350
|
-
|
|
351
|
-
class MyCloudProvider implements IStorageProvider {
|
|
352
|
-
async upload(file: IFileUploadOptions): Promise<IUploadedFile> { /* ... */ }
|
|
353
|
-
async delete(path: string): Promise<void> { /* ... */ }
|
|
354
|
-
async getUrl(path: string, ttl?: number): Promise<string> { /* ... */ }
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
StorageProviderRegistry.register('my-cloud', MyCloudProvider);
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## API Endpoints
|
|
363
|
-
|
|
364
|
-
All endpoints use **POST** and require JWT authentication.
|
|
365
|
-
|
|
366
|
-
### Storage Config — `POST /storage/storage-config/*`
|
|
367
|
-
|
|
368
|
-
| Endpoint | Permission | Description |
|
|
369
|
-
|----------|-----------|-------------|
|
|
370
|
-
| `POST /storage/storage-config/insert` | `storage-config.create` | Add provider config |
|
|
371
|
-
| `POST /storage/storage-config/get-all` | `storage-config.read` | List configs |
|
|
372
|
-
| `POST /storage/storage-config/get/:id` | `storage-config.read` | Get config by ID |
|
|
373
|
-
| `POST /storage/storage-config/update` | `storage-config.update` | Update config |
|
|
374
|
-
| `POST /storage/storage-config/delete` | `storage-config.delete` | Delete config |
|
|
375
|
-
| `POST /storage/storage-config/set-default` | `storage-config.update` | Set as default provider |
|
|
376
|
-
|
|
377
|
-
### Folders — `POST /storage/folder/*`
|
|
378
|
-
|
|
379
|
-
| Endpoint | Permission | Description |
|
|
380
|
-
|----------|-----------|-------------|
|
|
381
|
-
| `POST /storage/folder/insert` | `folder.create` | Create folder |
|
|
382
|
-
| `POST /storage/folder/get-all` | `folder.read` | List folders |
|
|
383
|
-
| `POST /storage/folder/get/:id` | `folder.read` | Get folder by ID |
|
|
384
|
-
| `POST /storage/folder/get-tree` | `folder.read` | Get hierarchical folder tree |
|
|
385
|
-
| `POST /storage/folder/update` | `folder.update` | Update folder |
|
|
386
|
-
| `POST /storage/folder/delete` | `folder.delete` | Delete folder (must be empty) |
|
|
387
|
-
|
|
388
|
-
### Files — `POST /storage/file/*`
|
|
389
|
-
|
|
390
|
-
| Endpoint | Permission | Description |
|
|
391
|
-
|----------|-----------|-------------|
|
|
392
|
-
| `POST /storage/file/upload` | `file.create` | Upload a file (multipart/form-data) |
|
|
393
|
-
| `POST /storage/file/get-all` | `file.read` | List files with pagination |
|
|
394
|
-
| `POST /storage/file/get/:id` | `file.read` | Get file metadata by ID |
|
|
395
|
-
| `POST /storage/file/get-url/:id` | `file.read` | Get presigned or direct file URL |
|
|
396
|
-
| `POST /storage/file/update` | `file.update` | Update file metadata |
|
|
397
|
-
| `POST /storage/file/delete` | `file.delete` | Delete file (from storage + DB) |
|
|
398
|
-
| `POST /storage/file/move` | `file.update` | Move file to a different folder |
|
|
399
|
-
|
|
400
|
-
### File Serving (Local)
|
|
401
|
-
|
|
402
|
-
```
|
|
403
|
-
GET /storage/upload/file/{path}
|
|
131
|
+
imports: [ConfigModule],
|
|
132
|
+
useFactory: (configService: ConfigService) => ({
|
|
133
|
+
tenantDefaultDatabaseConfig: {
|
|
134
|
+
type: 'mysql',
|
|
135
|
+
host: configService.get('TENANT_DB_HOST'),
|
|
136
|
+
port: configService.get<number>('TENANT_DB_PORT'),
|
|
137
|
+
username: configService.get('TENANT_DB_USER'),
|
|
138
|
+
password: configService.get('TENANT_DB_PASSWORD'),
|
|
139
|
+
database: configService.get('TENANT_DB_NAME'),
|
|
140
|
+
},
|
|
141
|
+
tenants: configService.get<ITenantDatabaseConfig[]>('TENANTS'),
|
|
142
|
+
maxFileSize: configService.get<number>('MAX_FILE_SIZE', 10 * 1024 * 1024),
|
|
143
|
+
appUrl: configService.get('APP_URL'),
|
|
144
|
+
localStoragePath: configService.get('UPLOAD_PATH', './uploads'),
|
|
145
|
+
}),
|
|
146
|
+
inject: [ConfigService],
|
|
147
|
+
});
|
|
404
148
|
```
|
|
405
149
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
## Entities
|
|
411
|
-
|
|
412
|
-
### Core Entities (always registered)
|
|
413
|
-
|
|
414
|
-
| Entity | Table | Description |
|
|
415
|
-
|--------|-------|-------------|
|
|
416
|
-
| `StorageConfig` | `storage_config` | Provider configuration (credentials, bucket, etc.) |
|
|
417
|
-
| `Folder` | `storage_folder` | Folder hierarchy with `parentId` |
|
|
418
|
-
| `FileManager` | `storage_file` | File metadata (name, path, size, MIME type, provider) |
|
|
419
|
-
|
|
420
|
-
### Company Feature Entities (`enableCompanyFeature: true`)
|
|
421
|
-
|
|
422
|
-
| Entity | Table | Description |
|
|
423
|
-
|--------|-------|-------------|
|
|
424
|
-
| `StorageConfigWithCompany` | `storage_config` | Same + `companyId` |
|
|
425
|
-
| `FolderWithCompany` | `storage_folder` | Same + `companyId` |
|
|
426
|
-
| `FileManagerWithCompany` | `storage_file` | Same + `companyId` |
|
|
150
|
+
## 2. Register Entities in TypeORM
|
|
427
151
|
|
|
428
152
|
```typescript
|
|
429
|
-
import {
|
|
153
|
+
import { getStorageEntitiesByConfig } from '@flusys/nestjs-storage/entities';
|
|
430
154
|
|
|
431
155
|
TypeOrmModule.forRoot({
|
|
432
156
|
entities: [
|
|
433
|
-
...
|
|
157
|
+
...getStorageEntitiesByConfig(true), // match enableCompanyFeature in bootstrapAppConfig
|
|
434
158
|
],
|
|
435
|
-
})
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
## File Serving (Local)
|
|
441
|
-
|
|
442
|
-
Register `FileServeMiddleware` to serve uploaded local files over HTTP:
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
import { FileServeMiddleware } from '@flusys/nestjs-storage';
|
|
446
|
-
|
|
447
|
-
// In AppModule
|
|
448
|
-
configure(consumer: MiddlewareConsumer) {
|
|
449
|
-
consumer.apply(FileServeMiddleware).forRoutes('storage/upload/file');
|
|
450
|
-
}
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
Files are served from the `localStoragePath` directory. The middleware validates paths to prevent directory traversal attacks.
|
|
454
|
-
|
|
455
|
-
---
|
|
456
|
-
|
|
457
|
-
## Image Compression
|
|
458
|
-
|
|
459
|
-
Automatic compression is applied on upload for supported image types:
|
|
460
|
-
|
|
461
|
-
| Format | Compression applied |
|
|
462
|
-
|--------|---------------------|
|
|
463
|
-
| JPEG | Quality 80%, progressive |
|
|
464
|
-
| PNG | Compression level 6 |
|
|
465
|
-
| WebP | Quality 80% |
|
|
466
|
-
| AVIF | Quality 75% |
|
|
467
|
-
| TIFF | Deflate compression |
|
|
468
|
-
| GIF | Passthrough |
|
|
469
|
-
|
|
470
|
-
Custom compression options:
|
|
471
|
-
|
|
472
|
-
```json
|
|
473
|
-
POST /storage/file/upload
|
|
474
|
-
Content-Type: multipart/form-data
|
|
475
|
-
|
|
476
|
-
{
|
|
477
|
-
"file": <binary>,
|
|
478
|
-
"folderId": "uuid",
|
|
479
|
-
"compress": true,
|
|
480
|
-
"maxWidth": 1920,
|
|
481
|
-
"maxHeight": 1080,
|
|
482
|
-
"quality": 75
|
|
483
|
-
}
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## File Validation
|
|
489
|
-
|
|
490
|
-
Files are validated using **magic bytes** (file header inspection), not just the file extension or MIME type header. This prevents users from uploading malicious files with forged extensions.
|
|
491
|
-
|
|
492
|
-
Allowed types are configured via `allowedFileTypes`:
|
|
493
|
-
```typescript
|
|
494
|
-
allowedFileTypes: ['image/*', 'application/pdf', 'text/plain']
|
|
159
|
+
});
|
|
495
160
|
```
|
|
496
161
|
|
|
497
|
-
Wildcard patterns like `image/*` match any subtype (e.g., `image/jpeg`, `image/png`, `image/webp`).
|
|
498
|
-
|
|
499
|
-
---
|
|
500
|
-
|
|
501
|
-
## Exported Services
|
|
502
|
-
|
|
503
|
-
| Service | Description |
|
|
504
|
-
|---------|-------------|
|
|
505
|
-
| `FileManagerService` | File upload, retrieval, deletion, move |
|
|
506
|
-
| `FolderService` | Folder CRUD and tree queries |
|
|
507
|
-
| `StorageConfigService` | Provider config CRUD |
|
|
508
|
-
| `StorageDataSourceProvider` | Dynamic DataSource per request |
|
|
509
|
-
|
|
510
|
-
---
|
|
511
|
-
|
|
512
|
-
## Programmatic Usage
|
|
513
|
-
|
|
514
|
-
```typescript
|
|
515
|
-
import { FileManagerService } from '@flusys/nestjs-storage';
|
|
516
|
-
|
|
517
|
-
@Injectable()
|
|
518
|
-
export class ProfileService {
|
|
519
|
-
constructor(
|
|
520
|
-
@Inject(FileManagerService) private readonly fileManager: FileManagerService,
|
|
521
|
-
) {}
|
|
522
|
-
|
|
523
|
-
async uploadAvatar(userId: string, fileBuffer: Buffer, filename: string): Promise<string> {
|
|
524
|
-
const result = await this.fileManager.uploadFile({
|
|
525
|
-
buffer: fileBuffer,
|
|
526
|
-
originalName: filename,
|
|
527
|
-
mimeType: 'image/jpeg',
|
|
528
|
-
folderId: 'avatars-folder-id',
|
|
529
|
-
});
|
|
530
|
-
return result.id; // Store file ID on user profile
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
async getAvatarUrl(fileId: string): Promise<string> {
|
|
534
|
-
return this.fileManager.getFileUrl(fileId);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
---
|
|
540
|
-
|
|
541
|
-
## Troubleshooting
|
|
542
|
-
|
|
543
|
-
**`Provider not registered` error**
|
|
544
|
-
|
|
545
|
-
You created a StorageConfig with `storage: 'aws'` but forgot to register the S3 provider. Call `StorageProviderRegistry.register()` before the module initializes.
|
|
546
|
-
|
|
547
|
-
---
|
|
548
|
-
|
|
549
|
-
**Local files return 404**
|
|
550
|
-
|
|
551
|
-
Ensure `FileServeMiddleware` is registered and that `localStoragePath` points to an existing directory. The path must be accessible by the Node.js process.
|
|
552
|
-
|
|
553
|
-
---
|
|
554
|
-
|
|
555
|
-
**`File type not allowed` for a valid file**
|
|
556
|
-
|
|
557
|
-
Magic-byte validation is strict. If your file is being rejected incorrectly, check `allowedFileTypes` in your config and ensure the actual file header matches the declared MIME type.
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
**Presigned URL expired immediately**
|
|
562
|
-
|
|
563
|
-
S3 and Azure presigned URLs have a TTL. The default TTL is cached per file. Pass a custom TTL in the get-url request body:
|
|
564
|
-
```json
|
|
565
|
-
POST /storage/file/get-url/:id
|
|
566
|
-
{ "ttl": 3600 }
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
---
|
|
570
162
|
|
|
571
163
|
## License
|
|
572
164
|
|
|
573
|
-
MIT © FLUSYS
|
|
574
|
-
|
|
575
|
-
---
|
|
576
|
-
|
|
577
|
-
> Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
|
|
165
|
+
MIT © FLUSYS
|