@flusys/nestjs-storage 4.1.1 → 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
|
@@ -50,7 +50,7 @@ export class FileServeMiddleware {
|
|
|
50
50
|
throw new NotFoundException({
|
|
51
51
|
message: `Not a file: ${normalizedPath}`,
|
|
52
52
|
messageKey: FILE_MESSAGES.NOT_FOUND,
|
|
53
|
-
|
|
53
|
+
messageVariables: {
|
|
54
54
|
path: normalizedPath
|
|
55
55
|
}
|
|
56
56
|
});
|
|
@@ -99,7 +99,7 @@ export class FileServeMiddleware {
|
|
|
99
99
|
throw new NotFoundException({
|
|
100
100
|
message: `File not found: ${normalizedPath}`,
|
|
101
101
|
messageKey: FILE_MESSAGES.NOT_FOUND,
|
|
102
|
-
|
|
102
|
+
messageVariables: {
|
|
103
103
|
path: normalizedPath
|
|
104
104
|
}
|
|
105
105
|
});
|
|
@@ -148,7 +148,7 @@ export class FileServeMiddleware {
|
|
|
148
148
|
this.sendErrorResponse(res, new NotFoundException({
|
|
149
149
|
message: `Failed to serve file: ${normalizedPath}`,
|
|
150
150
|
messageKey: FILE_MESSAGES.NOT_FOUND,
|
|
151
|
-
|
|
151
|
+
messageVariables: {
|
|
152
152
|
path: normalizedPath
|
|
153
153
|
}
|
|
154
154
|
}));
|
|
@@ -162,12 +162,12 @@ export class FileServeMiddleware {
|
|
|
162
162
|
const response = isNotFound ? error.getResponse() : null;
|
|
163
163
|
const message = response?.message ?? 'File not found';
|
|
164
164
|
const messageKey = response?.messageKey ?? FILE_MESSAGES.NOT_FOUND;
|
|
165
|
-
const
|
|
165
|
+
const messageVariables = response?.messageVariables;
|
|
166
166
|
res.status(404).json({
|
|
167
167
|
success: false,
|
|
168
168
|
message,
|
|
169
169
|
messageKey,
|
|
170
|
-
|
|
170
|
+
messageVariables,
|
|
171
171
|
error: 'Not Found',
|
|
172
172
|
statusCode: 404
|
|
173
173
|
});
|
|
@@ -25,9 +25,9 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
|
|
28
29
|
import { Inject, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
|
29
30
|
import * as crypto from 'crypto';
|
|
30
|
-
import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
|
|
31
31
|
import { StorageConfigService } from '../services';
|
|
32
32
|
import { StorageProviderRegistry } from './storage-provider.registry';
|
|
33
33
|
export class StorageFactoryService {
|
|
@@ -38,9 +38,9 @@ export class StorageFactoryService {
|
|
|
38
38
|
const ProviderClass = StorageProviderRegistry.get(config.provider);
|
|
39
39
|
if (!ProviderClass) {
|
|
40
40
|
throw new NotFoundException({
|
|
41
|
-
message: `Storage provider '${config.provider}' not
|
|
41
|
+
message: `Storage provider '${config.provider}' not available. Available: ${StorageProviderRegistry.getAll().join(', ')}`,
|
|
42
42
|
messageKey: SYSTEM_MESSAGES.SERVICE_NOT_AVAILABLE,
|
|
43
|
-
|
|
43
|
+
messageVariables: {
|
|
44
44
|
provider: config.provider,
|
|
45
45
|
available: StorageProviderRegistry.getAll().join(', ')
|
|
46
46
|
}
|
|
@@ -56,7 +56,7 @@ export class StorageFactoryService {
|
|
|
56
56
|
throw new InternalServerErrorException({
|
|
57
57
|
message: `Failed to initialize '${config.provider}': ${errorMessage}`,
|
|
58
58
|
messageKey: SYSTEM_MESSAGES.INTERNAL_ERROR,
|
|
59
|
-
|
|
59
|
+
messageVariables: {
|
|
60
60
|
provider: config.provider,
|
|
61
61
|
error: errorMessage
|
|
62
62
|
}
|
|
@@ -28,12 +28,12 @@ function _ts_param(paramIndex, decorator) {
|
|
|
28
28
|
import { HybridCache, RequestScopedApiService } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
30
|
import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
|
|
31
|
-
import { FILE_MESSAGES, FOLDER_MESSAGES, UPLOAD_MESSAGES } from '../config';
|
|
32
31
|
import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
|
|
33
32
|
import { Brackets, In } from 'typeorm';
|
|
34
|
-
import {
|
|
33
|
+
import { FILE_MESSAGES, FOLDER_MESSAGES, UPLOAD_MESSAGES } from '../config';
|
|
35
34
|
import { FileManager, FileManagerWithCompany, Folder, FolderWithCompany } from '../entities';
|
|
36
35
|
import { FileLocationEnum } from '../enums/file-location.enum';
|
|
36
|
+
import { StorageConfigService } from './storage-config.service';
|
|
37
37
|
import { StorageDataSourceProvider } from './storage-datasource.provider';
|
|
38
38
|
import { UploadService } from './upload.service';
|
|
39
39
|
const PERMISSION_CACHE_KEY = 'user_action_permission';
|
|
@@ -144,42 +144,6 @@ export class FileManagerService extends RequestScopedApiService {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
// ─── Public Methods ─────────────────────────────────────────────────────────
|
|
147
|
-
async enrichWithProviderNames(items) {
|
|
148
|
-
const configIds = [
|
|
149
|
-
...new Set(items.map((i)=>i.storageConfigId).filter(Boolean))
|
|
150
|
-
];
|
|
151
|
-
if (!configIds.length) {
|
|
152
|
-
return items.map((item)=>({
|
|
153
|
-
...item,
|
|
154
|
-
providerName: undefined
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
const repo = await this.getStorageConfigRepository();
|
|
159
|
-
const configs = await repo.find({
|
|
160
|
-
where: {
|
|
161
|
-
id: In(configIds)
|
|
162
|
-
},
|
|
163
|
-
select: [
|
|
164
|
-
'id',
|
|
165
|
-
'name'
|
|
166
|
-
]
|
|
167
|
-
});
|
|
168
|
-
const nameMap = new Map(configs.map((c)=>[
|
|
169
|
-
c.id,
|
|
170
|
-
c.name
|
|
171
|
-
]));
|
|
172
|
-
return items.map((item)=>({
|
|
173
|
-
...item,
|
|
174
|
-
providerName: item.storageConfigId ? nameMap.get(item.storageConfigId) : undefined
|
|
175
|
-
}));
|
|
176
|
-
} catch {
|
|
177
|
-
return items.map((item)=>({
|
|
178
|
-
...item,
|
|
179
|
-
providerName: undefined
|
|
180
|
-
}));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
147
|
async getFiles(dtos, protocol, host, user) {
|
|
184
148
|
await this.ensureRepositoryInitialized();
|
|
185
149
|
const ids = dtos.map((d)=>d.id).filter(Boolean);
|
|
@@ -247,7 +211,7 @@ export class FileManagerService extends RequestScopedApiService {
|
|
|
247
211
|
throw new BadRequestException({
|
|
248
212
|
message: `Folder "${folderId}" not found or access denied`,
|
|
249
213
|
messageKey: FOLDER_MESSAGES.NOT_FOUND,
|
|
250
|
-
|
|
214
|
+
messageVariables: {
|
|
251
215
|
folderId
|
|
252
216
|
}
|
|
253
217
|
});
|
|
@@ -26,8 +26,9 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { Inject, Injectable } from '@nestjs/common';
|
|
29
|
+
import { UPLOAD_MESSAGES } from '../config';
|
|
30
|
+
import { DEFAULT_ALLOWED_FILE_TYPES, DEFAULT_MAX_FILE_SIZE, STORAGE_MODULE_OPTIONS } from '../config/storage.constants';
|
|
29
31
|
import { StorageModuleOptions } from '../interfaces';
|
|
30
|
-
import { STORAGE_MODULE_OPTIONS, DEFAULT_MAX_FILE_SIZE, DEFAULT_ALLOWED_FILE_TYPES } from '../config/storage.constants';
|
|
31
32
|
const BYTES_PER_MB = 1024 * 1024;
|
|
32
33
|
const DEFAULT_LOCAL_PATH = './uploads';
|
|
33
34
|
const DEFAULT_PORT = '3000';
|
|
@@ -71,7 +72,12 @@ export class StorageConfigService {
|
|
|
71
72
|
if (fileSize > maxSize) {
|
|
72
73
|
return {
|
|
73
74
|
valid: false,
|
|
74
|
-
message: `File size (${this.toMB(fileSize)}MB) exceeds maximum ${this.toMB(maxSize)}MB
|
|
75
|
+
message: `File size (${this.toMB(fileSize)}MB) exceeds maximum ${this.toMB(maxSize)}MB`,
|
|
76
|
+
messageKey: UPLOAD_MESSAGES.FILE_TOO_LARGE,
|
|
77
|
+
messageVariables: {
|
|
78
|
+
fileSize: this.toMB(fileSize),
|
|
79
|
+
maxSize: this.toMB(maxSize)
|
|
80
|
+
}
|
|
75
81
|
};
|
|
76
82
|
}
|
|
77
83
|
return {
|
|
@@ -82,7 +88,12 @@ export class StorageConfigService {
|
|
|
82
88
|
if (!this.isFileTypeAllowed(mimeType)) {
|
|
83
89
|
return {
|
|
84
90
|
valid: false,
|
|
85
|
-
message: `File type "${mimeType}" not allowed. Allowed: ${this.getAllowedFileTypes().join(', ')}
|
|
91
|
+
message: `File type "${mimeType}" not allowed. Allowed: ${this.getAllowedFileTypes().join(', ')}`,
|
|
92
|
+
messageKey: UPLOAD_MESSAGES.INVALID_TYPE,
|
|
93
|
+
messageVariables: {
|
|
94
|
+
mimeType,
|
|
95
|
+
allowedTypes: this.getAllowedFileTypes().join(', ')
|
|
96
|
+
}
|
|
86
97
|
};
|
|
87
98
|
}
|
|
88
99
|
return {
|
|
@@ -25,9 +25,9 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
|
|
28
29
|
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
29
30
|
import { Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
|
|
30
|
-
import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
|
|
31
31
|
import { REQUEST } from '@nestjs/core';
|
|
32
32
|
import { Request } from 'express';
|
|
33
33
|
import { StorageConfigService } from './storage-config.service';
|
|
@@ -48,11 +48,6 @@ export class StorageDataSourceProvider extends MultiTenantDataSourceService {
|
|
|
48
48
|
*/ getEnableCompanyFeatureForTenant(tenant) {
|
|
49
49
|
return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
|
|
50
50
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Get enable company feature for current request context
|
|
53
|
-
*/ getEnableCompanyFeatureForCurrentTenant() {
|
|
54
|
-
return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
|
|
55
|
-
}
|
|
56
51
|
// Entity Management
|
|
57
52
|
/**
|
|
58
53
|
* Get storage entities for migrations based on company feature flag.
|
|
@@ -26,16 +26,16 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { LogAction } from '@flusys/nestjs-shared';
|
|
29
|
-
import { UPLOAD_MESSAGES } from '../config';
|
|
30
29
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
31
30
|
import { validateCompanyOwnership } from '@flusys/nestjs-shared/utils';
|
|
32
31
|
import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
|
|
33
|
-
import {
|
|
32
|
+
import { UPLOAD_MESSAGES } from '../config';
|
|
34
33
|
import { UploadOptionsDto } from '../dtos/upload.dto';
|
|
35
34
|
import { FileLocationEnum } from '../enums/file-location.enum';
|
|
36
35
|
import { StorageFactoryService } from '../providers/storage-factory.service';
|
|
37
|
-
import { StorageProviderConfigService } from './storage-provider-config.service';
|
|
38
36
|
import { FileValidator } from '../utils/file-validator.util';
|
|
37
|
+
import { StorageConfigService } from './storage-config.service';
|
|
38
|
+
import { StorageProviderConfigService } from './storage-provider-config.service';
|
|
39
39
|
export class UploadService {
|
|
40
40
|
/**
|
|
41
41
|
* Validate file before upload - includes size, type, and content validation.
|
|
@@ -46,7 +46,8 @@ export class UploadService {
|
|
|
46
46
|
if (!sizeValidation.valid) {
|
|
47
47
|
throw new BadRequestException({
|
|
48
48
|
message: sizeValidation.message,
|
|
49
|
-
messageKey:
|
|
49
|
+
messageKey: sizeValidation.messageKey,
|
|
50
|
+
messageVariables: sizeValidation.messageVariables
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
// Validate declared file type (MIME)
|
|
@@ -54,7 +55,8 @@ export class UploadService {
|
|
|
54
55
|
if (!typeValidation.valid) {
|
|
55
56
|
throw new BadRequestException({
|
|
56
57
|
message: typeValidation.message,
|
|
57
|
-
messageKey:
|
|
58
|
+
messageKey: typeValidation.messageKey,
|
|
59
|
+
messageVariables: typeValidation.messageVariables
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
62
|
// Validate file content matches declared type (magic bytes check)
|
|
@@ -64,7 +66,11 @@ export class UploadService {
|
|
|
64
66
|
if (!contentValidation.valid) {
|
|
65
67
|
throw new BadRequestException({
|
|
66
68
|
message: contentValidation.message || 'File content validation failed',
|
|
67
|
-
messageKey: UPLOAD_MESSAGES.INVALID_TYPE
|
|
69
|
+
messageKey: UPLOAD_MESSAGES.INVALID_TYPE,
|
|
70
|
+
messageVariables: {
|
|
71
|
+
mimeType: file.mimetype,
|
|
72
|
+
allowedTypes: allowedTypes.join(', ')
|
|
73
|
+
}
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
76
|
// Sanitize filename to prevent path traversal attacks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-storage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Modular storage package with optional AWS S3, Azure Blob, and SFTP providers",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
131
|
"dependencies": {
|
|
132
|
-
"@flusys/nestjs-core": "
|
|
133
|
-
"@flusys/nestjs-shared": "
|
|
132
|
+
"@flusys/nestjs-core": "5.0.0",
|
|
133
|
+
"@flusys/nestjs-shared": "5.0.0"
|
|
134
134
|
}
|
|
135
135
|
}
|
|
@@ -3,10 +3,10 @@ import { DeleteDto, FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
|
|
|
3
3
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
4
4
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
5
5
|
import { EntityTarget, QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
|
|
6
|
-
import { StorageConfigService } from './storage-config.service';
|
|
7
6
|
import { CreateFileManagerDto, FilesResponseDto, GetFilesRequestDto, UpdateFileManagerDto } from '../dtos';
|
|
8
7
|
import { FileManager, FileManagerBase } from '../entities';
|
|
9
8
|
import { IFileManager } from '../interfaces';
|
|
9
|
+
import { StorageConfigService } from './storage-config.service';
|
|
10
10
|
import { StorageDataSourceProvider } from './storage-datasource.provider';
|
|
11
11
|
import { UploadService } from './upload.service';
|
|
12
12
|
export declare class FileManagerService extends RequestScopedApiService<CreateFileManagerDto, UpdateFileManagerDto, IFileManager, FileManagerBase, Repository<FileManagerBase>> {
|
|
@@ -32,11 +32,6 @@ export declare class FileManagerService extends RequestScopedApiService<CreateFi
|
|
|
32
32
|
isRaw: boolean;
|
|
33
33
|
}>;
|
|
34
34
|
beforeDeleteOperation(dto: DeleteDto, user: ILoggedUserInfo | null, _qr: QueryRunner): Promise<void>;
|
|
35
|
-
enrichWithProviderNames<T extends {
|
|
36
|
-
storageConfigId?: string | null;
|
|
37
|
-
}>(items: T[]): Promise<(T & {
|
|
38
|
-
providerName?: string;
|
|
39
|
-
})[]>;
|
|
40
35
|
getFiles(dtos: GetFilesRequestDto[], protocol: string, host: string, user?: ILoggedUserInfo): Promise<FilesResponseDto[]>;
|
|
41
36
|
private buildWhereWithCompany;
|
|
42
37
|
private getStorageConfigRepository;
|
|
@@ -4,6 +4,8 @@ import { StorageModuleOptions } from '../interfaces';
|
|
|
4
4
|
interface ValidationResult {
|
|
5
5
|
valid: boolean;
|
|
6
6
|
message?: string;
|
|
7
|
+
messageKey?: string;
|
|
8
|
+
messageVariables?: Record<string, unknown>;
|
|
7
9
|
}
|
|
8
10
|
export declare class StorageConfigService implements IModuleConfigService {
|
|
9
11
|
private readonly options;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
2
1
|
import { IDatabaseConfig, ITenantDatabaseConfig } from '@flusys/nestjs-core';
|
|
2
|
+
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
3
3
|
import { Request } from 'express';
|
|
4
4
|
import { DataSource } from 'typeorm';
|
|
5
5
|
import { StorageConfigService } from './storage-config.service';
|
|
@@ -14,7 +14,6 @@ export declare class StorageDataSourceProvider extends MultiTenantDataSourceServ
|
|
|
14
14
|
constructor(configService: StorageConfigService, request?: Request);
|
|
15
15
|
private static buildParentOptions;
|
|
16
16
|
getEnableCompanyFeatureForTenant(tenant?: ITenantDatabaseConfig): boolean;
|
|
17
|
-
getEnableCompanyFeatureForCurrentTenant(): boolean;
|
|
18
17
|
getStorageEntities(enableCompanyFeature?: boolean): Promise<any[]>;
|
|
19
18
|
protected createDataSourceFromConfig(config: IDatabaseConfig): Promise<DataSource>;
|
|
20
19
|
protected getSingleDataSource(): Promise<DataSource>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
|
-
import { StorageConfigService } from './storage-config.service';
|
|
3
2
|
import { UploadOptionsDto } from '../dtos/upload.dto';
|
|
4
3
|
import { IUploadedFileInfo } from '../interfaces/storage-provider.interface';
|
|
5
4
|
import { StorageFactoryService } from '../providers/storage-factory.service';
|
|
5
|
+
import { StorageConfigService } from './storage-config.service';
|
|
6
6
|
import { StorageProviderConfigService } from './storage-provider-config.service';
|
|
7
7
|
export declare class UploadService {
|
|
8
8
|
private readonly storageFactory;
|