@flusys/nestjs-storage 4.1.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +71 -483
  2. package/cjs/config/message-keys.js +2 -52
  3. package/cjs/controllers/storage-config.controller.js +2 -2
  4. package/cjs/docs/storage-swagger.config.js +5 -3
  5. package/cjs/entities/storage-config.entity.js +2 -1
  6. package/cjs/middlewares/file-serve.middleware.js +5 -5
  7. package/cjs/providers/azure-provider.optional.js +1 -1
  8. package/cjs/providers/s3-provider.optional.js +1 -1
  9. package/cjs/providers/sftp-provider.optional.js +1 -1
  10. package/cjs/providers/storage-factory.service.js +4 -4
  11. package/cjs/services/file-manager.service.js +3 -39
  12. package/cjs/services/storage-config.service.js +14 -3
  13. package/cjs/services/storage-datasource.provider.js +1 -6
  14. package/cjs/services/upload.service.js +12 -6
  15. package/config/message-keys.d.ts +0 -96
  16. package/fesm/config/message-keys.js +2 -47
  17. package/fesm/controllers/storage-config.controller.js +2 -2
  18. package/fesm/docs/storage-swagger.config.js +5 -3
  19. package/fesm/entities/storage-config.entity.js +2 -1
  20. package/fesm/middlewares/file-serve.middleware.js +5 -5
  21. package/fesm/providers/azure-provider.optional.js +1 -1
  22. package/fesm/providers/s3-provider.optional.js +1 -1
  23. package/fesm/providers/sftp-provider.optional.js +1 -1
  24. package/fesm/providers/storage-factory.service.js +4 -4
  25. package/fesm/services/file-manager.service.js +3 -39
  26. package/fesm/services/storage-config.service.js +14 -3
  27. package/fesm/services/storage-datasource.provider.js +1 -6
  28. package/fesm/services/upload.service.js +12 -6
  29. package/package.json +3 -3
  30. package/services/file-manager.service.d.ts +1 -6
  31. package/services/storage-config.service.d.ts +2 -0
  32. package/services/storage-datasource.provider.d.ts +1 -2
  33. package/services/upload.service.d.ts +1 -1
@@ -18,7 +18,8 @@ const COMPANY_SCHEMA_EXCLUSIONS = [
18
18
  ]
19
19
  }
20
20
  ];
21
- function buildDescription(enableCompanyFeature) {
21
+ function buildDescription(enableCompanyFeature, isMultiTenant) {
22
+ const multiTenantNote = isMultiTenant ? `\n> **Multi-Tenant Mode**: Include \`x-tenant-id\` header to target a specific tenant database.\n` : '';
22
23
  const companyIsolation = enableCompanyFeature ? '\n- **Company isolation**' : '';
23
24
  const companyPermissions = enableCompanyFeature ? '\n- **Company-level permissions**' : '';
24
25
  const multiTenantSection = enableCompanyFeature ? `
@@ -46,7 +47,7 @@ POST /storage/folder/insert with JSON body containing folder name
46
47
  ` : '';
47
48
  return `
48
49
  # Storage Management API
49
-
50
+ ${multiTenantNote}
50
51
  Complete file storage and management system${enableCompanyFeature ? ' with multi-tenant support' : ''}.
51
52
 
52
53
  ## Features
@@ -167,9 +168,10 @@ All endpoints are rate-limited to prevent abuse. Default limits:
167
168
  * Use this with setupSwaggerDocs() from @flusys/nestjs-core
168
169
  */ export function storageSwaggerConfig(bootstrapConfig) {
169
170
  const enableCompanyFeature = bootstrapConfig?.enableCompanyFeature ?? true;
171
+ const isMultiTenant = bootstrapConfig?.databaseMode === 'multi-tenant';
170
172
  return {
171
173
  title: 'Storage API',
172
- description: buildDescription(enableCompanyFeature),
174
+ description: buildDescription(enableCompanyFeature, isMultiTenant),
173
175
  version: '1.0',
174
176
  path: 'api/docs/storage',
175
177
  bearerAuth: true,
@@ -20,6 +20,7 @@ function _ts_decorate(decorators, target, key, desc) {
20
20
  function _ts_metadata(k, v) {
21
21
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
22
  }
23
+ import { getJsonColumnType } from '@flusys/nestjs-shared';
23
24
  import { Identity } from '@flusys/nestjs-shared/entities';
24
25
  import { Column, Entity, Index } from 'typeorm';
25
26
  export class StorageConfig extends Identity {
@@ -45,7 +46,7 @@ _ts_decorate([
45
46
  ], StorageConfig.prototype, "storage", void 0);
46
47
  _ts_decorate([
47
48
  Column({
48
- type: 'json',
49
+ type: getJsonColumnType(),
49
50
  nullable: false
50
51
  }),
51
52
  _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
@@ -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
- messageParams: {
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
- messageParams: {
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
- messageParams: {
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 messageParams = response?.messageParams;
165
+ const messageVariables = response?.messageVariables;
166
166
  res.status(404).json({
167
167
  success: false,
168
168
  message,
169
169
  messageKey,
170
- messageParams,
170
+ messageVariables,
171
171
  error: 'Not Found',
172
172
  statusCode: 404
173
173
  });
@@ -31,7 +31,7 @@ export class AzureProvider {
31
31
  throw new InternalServerErrorException({
32
32
  message: 'Azure Storage SDK not found',
33
33
  messageKey: SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
34
- messageParams: {
34
+ messageVariables: {
35
35
  sdk: '@azure/storage-blob'
36
36
  }
37
37
  });
@@ -36,7 +36,7 @@ export class S3Provider {
36
36
  throw new InternalServerErrorException({
37
37
  message: 'AWS SDK not found',
38
38
  messageKey: SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
39
- messageParams: {
39
+ messageVariables: {
40
40
  sdk: '@aws-sdk/client-s3'
41
41
  }
42
42
  });
@@ -59,7 +59,7 @@ import { v4 as uuidv4 } from 'uuid';
59
59
  throw new InternalServerErrorException({
60
60
  message: 'SFTP client not found',
61
61
  messageKey: SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
62
- messageParams: {
62
+ messageVariables: {
63
63
  sdk: 'ssh2-sftp-client'
64
64
  }
65
65
  });
@@ -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 registered. Available: ${StorageProviderRegistry.getAll().join(', ')}`,
41
+ message: `Storage provider '${config.provider}' not available. Available: ${StorageProviderRegistry.getAll().join(', ')}`,
42
42
  messageKey: SYSTEM_MESSAGES.SERVICE_NOT_AVAILABLE,
43
- messageParams: {
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
- messageParams: {
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 { StorageConfigService } from './storage-config.service';
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
- messageParams: {
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 { StorageConfigService } from './storage-config.service';
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: UPLOAD_MESSAGES.FILE_TOO_LARGE
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: UPLOAD_MESSAGES.INVALID_TYPE
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": "4.1.1",
3
+ "version": "5.0.1",
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": "4.1.1",
133
- "@flusys/nestjs-shared": "4.1.1"
132
+ "@flusys/nestjs-core": "5.0.1",
133
+ "@flusys/nestjs-shared": "5.0.1"
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;