@flusys/nestjs-storage 1.0.0-beta → 1.0.0-rc
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 +131 -13
- package/cjs/config/storage-config.service.js +5 -0
- package/cjs/config/storage.constants.js +0 -8
- package/cjs/controllers/file-manager.controller.js +44 -1
- package/cjs/controllers/folder.controller.js +44 -1
- package/cjs/controllers/storage-config.controller.js +44 -1
- package/cjs/controllers/upload.controller.js +6 -12
- package/cjs/dtos/file-manager.dto.js +8 -5
- package/cjs/dtos/storage-config.dto.js +41 -1
- package/cjs/dtos/upload.dto.js +7 -0
- package/cjs/entities/storage-config-base.entity.js +31 -2
- package/cjs/interfaces/index.js +0 -1
- package/cjs/middlewares/file-serve.middleware.js +6 -0
- package/cjs/providers/local-provider.js +52 -2
- package/cjs/providers/storage-factory.service.js +2 -2
- package/cjs/services/file-manager.service.js +37 -24
- package/cjs/services/folder.service.js +5 -8
- package/cjs/services/storage-provider-config.service.js +18 -35
- package/cjs/services/upload.service.js +39 -27
- package/cjs/utils/file-validator.util.js +470 -0
- package/cjs/utils/image-compressor.util.js +1 -3
- package/config/storage-config.service.d.ts +5 -2
- package/config/storage.constants.d.ts +0 -2
- package/controllers/upload.controller.d.ts +2 -6
- package/dtos/file-manager.dto.d.ts +2 -4
- package/dtos/folder.dto.d.ts +2 -4
- package/dtos/storage-config.dto.d.ts +9 -6
- package/entities/storage-config-base.entity.d.ts +2 -0
- package/fesm/config/storage-config.service.js +5 -0
- package/fesm/config/storage.constants.js +0 -2
- package/fesm/controllers/file-manager.controller.js +45 -2
- package/fesm/controllers/folder.controller.js +45 -2
- package/fesm/controllers/storage-config.controller.js +45 -2
- package/fesm/controllers/upload.controller.js +7 -13
- package/fesm/dtos/file-manager.dto.js +8 -5
- package/fesm/dtos/storage-config.dto.js +45 -11
- package/fesm/dtos/upload.dto.js +8 -1
- package/fesm/entities/index.js +1 -5
- package/fesm/entities/storage-config-base.entity.js +33 -7
- package/fesm/interfaces/index.js +0 -1
- package/fesm/interfaces/storage-config.interface.js +1 -3
- package/fesm/middlewares/file-serve.middleware.js +7 -1
- package/fesm/providers/local-provider.js +52 -2
- package/fesm/providers/storage-factory.service.js +2 -2
- package/fesm/services/file-manager.service.js +38 -25
- package/fesm/services/folder.service.js +5 -8
- package/fesm/services/storage-provider-config.service.js +18 -35
- package/fesm/services/upload.service.js +40 -28
- package/fesm/utils/file-validator.util.js +463 -0
- package/fesm/utils/image-compressor.util.js +1 -3
- package/interfaces/file-manager.interface.d.ts +7 -4
- package/interfaces/index.d.ts +0 -1
- package/interfaces/storage-config.interface.d.ts +2 -20
- package/package.json +6 -6
- package/providers/local-provider.d.ts +2 -0
- package/services/file-manager.service.d.ts +2 -2
- package/utils/file-validator.util.d.ts +16 -0
- package/cjs/interfaces/file-upload-response.interface.js +0 -4
- package/fesm/interfaces/file-upload-response.interface.js +0 -1
- package/interfaces/file-upload-response.interface.d.ts +0 -6
|
@@ -25,13 +25,56 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { createApiController } from '@flusys/nestjs-shared/classes';
|
|
28
|
+
import { createApiController, FOLDER_PERMISSIONS } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { CreateFolderDto, FolderResponseDto, UpdateFolderDto } from '../dtos';
|
|
30
30
|
import { Controller, Inject } from '@nestjs/common';
|
|
31
31
|
import { ApiTags } from '@nestjs/swagger';
|
|
32
32
|
import { FolderService } from '../services/folder.service';
|
|
33
33
|
export class FolderController extends createApiController(CreateFolderDto, UpdateFolderDto, FolderResponseDto, {
|
|
34
|
-
security:
|
|
34
|
+
security: {
|
|
35
|
+
insert: {
|
|
36
|
+
level: 'permission',
|
|
37
|
+
permissions: [
|
|
38
|
+
FOLDER_PERMISSIONS.CREATE
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
insertMany: {
|
|
42
|
+
level: 'permission',
|
|
43
|
+
permissions: [
|
|
44
|
+
FOLDER_PERMISSIONS.CREATE
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
getById: {
|
|
48
|
+
level: 'permission',
|
|
49
|
+
permissions: [
|
|
50
|
+
FOLDER_PERMISSIONS.READ
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
getAll: {
|
|
54
|
+
level: 'permission',
|
|
55
|
+
permissions: [
|
|
56
|
+
FOLDER_PERMISSIONS.READ
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
update: {
|
|
60
|
+
level: 'permission',
|
|
61
|
+
permissions: [
|
|
62
|
+
FOLDER_PERMISSIONS.UPDATE
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
updateMany: {
|
|
66
|
+
level: 'permission',
|
|
67
|
+
permissions: [
|
|
68
|
+
FOLDER_PERMISSIONS.UPDATE
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
delete: {
|
|
72
|
+
level: 'permission',
|
|
73
|
+
permissions: [
|
|
74
|
+
FOLDER_PERMISSIONS.DELETE
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
35
78
|
}) {
|
|
36
79
|
constructor(folderService){
|
|
37
80
|
super(folderService), _define_property(this, "folderService", void 0), this.folderService = folderService;
|
|
@@ -25,13 +25,56 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { createApiController } from '@flusys/nestjs-shared/classes';
|
|
28
|
+
import { createApiController, STORAGE_CONFIG_PERMISSIONS } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { CreateStorageConfigDto, StorageConfigResponseDto, UpdateStorageConfigDto } from '../dtos';
|
|
30
30
|
import { Controller, Inject } from '@nestjs/common';
|
|
31
31
|
import { ApiTags } from '@nestjs/swagger';
|
|
32
32
|
import { StorageProviderConfigService } from '../services/storage-provider-config.service';
|
|
33
33
|
export class StorageConfigController extends createApiController(CreateStorageConfigDto, UpdateStorageConfigDto, StorageConfigResponseDto, {
|
|
34
|
-
security:
|
|
34
|
+
security: {
|
|
35
|
+
insert: {
|
|
36
|
+
level: 'permission',
|
|
37
|
+
permissions: [
|
|
38
|
+
STORAGE_CONFIG_PERMISSIONS.CREATE
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
insertMany: {
|
|
42
|
+
level: 'permission',
|
|
43
|
+
permissions: [
|
|
44
|
+
STORAGE_CONFIG_PERMISSIONS.CREATE
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
getById: {
|
|
48
|
+
level: 'permission',
|
|
49
|
+
permissions: [
|
|
50
|
+
STORAGE_CONFIG_PERMISSIONS.READ
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
getAll: {
|
|
54
|
+
level: 'permission',
|
|
55
|
+
permissions: [
|
|
56
|
+
STORAGE_CONFIG_PERMISSIONS.READ
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
update: {
|
|
60
|
+
level: 'permission',
|
|
61
|
+
permissions: [
|
|
62
|
+
STORAGE_CONFIG_PERMISSIONS.UPDATE
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
updateMany: {
|
|
66
|
+
level: 'permission',
|
|
67
|
+
permissions: [
|
|
68
|
+
STORAGE_CONFIG_PERMISSIONS.UPDATE
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
delete: {
|
|
72
|
+
level: 'permission',
|
|
73
|
+
permissions: [
|
|
74
|
+
STORAGE_CONFIG_PERMISSIONS.DELETE
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
35
78
|
}) {
|
|
36
79
|
constructor(storageConfigService){
|
|
37
80
|
super(storageConfigService), _define_property(this, "storageConfigService", void 0), this.storageConfigService = storageConfigService;
|
|
@@ -26,15 +26,13 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
29
|
-
import { ApiResponseDto, CurrentUser } from '@flusys/nestjs-shared/decorators';
|
|
29
|
+
import { ApiResponseDto, CurrentUser, RequirePermission } from '@flusys/nestjs-shared/decorators';
|
|
30
30
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
31
31
|
import { DeleteMultipleFileDto, DeleteSingleFileDto, FileUploadResponsePayloadDto, UploadOptionsDto } from '../dtos';
|
|
32
32
|
import { Body, Controller, Inject, Post, Query, UploadedFile, UploadedFiles, UseGuards, UseInterceptors } from '@nestjs/common';
|
|
33
33
|
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
|
34
34
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
|
35
35
|
import { UploadService } from '../services/upload.service';
|
|
36
|
-
import { StorageConfigService } from '../config';
|
|
37
|
-
import { StorageFactoryService } from '../providers/storage-factory.service';
|
|
38
36
|
export class UploadController {
|
|
39
37
|
async uploadSingleFile(file, options, user) {
|
|
40
38
|
const result = await this.uploadService.uploadSingleFile(file, options, user);
|
|
@@ -83,17 +81,14 @@ export class UploadController {
|
|
|
83
81
|
data
|
|
84
82
|
};
|
|
85
83
|
}
|
|
86
|
-
constructor(uploadService
|
|
84
|
+
constructor(uploadService){
|
|
87
85
|
_define_property(this, "uploadService", void 0);
|
|
88
|
-
_define_property(this, "storageConfigService", void 0);
|
|
89
|
-
_define_property(this, "storageFactoryService", void 0);
|
|
90
86
|
this.uploadService = uploadService;
|
|
91
|
-
this.storageConfigService = storageConfigService;
|
|
92
|
-
this.storageFactoryService = storageFactoryService;
|
|
93
87
|
}
|
|
94
88
|
}
|
|
95
89
|
_ts_decorate([
|
|
96
90
|
Post('single-file'),
|
|
91
|
+
RequirePermission('file.upload'),
|
|
97
92
|
ApiResponseDto(FileUploadResponsePayloadDto, false),
|
|
98
93
|
ApiOperation({
|
|
99
94
|
summary: 'Upload a single file'
|
|
@@ -135,6 +130,7 @@ _ts_decorate([
|
|
|
135
130
|
], UploadController.prototype, "uploadSingleFile", null);
|
|
136
131
|
_ts_decorate([
|
|
137
132
|
Post('multiple-file'),
|
|
133
|
+
RequirePermission('file.upload'),
|
|
138
134
|
ApiOperation({
|
|
139
135
|
summary: 'Upload multiple files (up to 50)'
|
|
140
136
|
}),
|
|
@@ -179,6 +175,7 @@ _ts_decorate([
|
|
|
179
175
|
], UploadController.prototype, "uploadMultipleFiles", null);
|
|
180
176
|
_ts_decorate([
|
|
181
177
|
Post('delete-single-file'),
|
|
178
|
+
RequirePermission('file.delete'),
|
|
182
179
|
ApiOperation({
|
|
183
180
|
summary: 'Delete a single file'
|
|
184
181
|
}),
|
|
@@ -196,6 +193,7 @@ _ts_decorate([
|
|
|
196
193
|
], UploadController.prototype, "deleteSingleFile", null);
|
|
197
194
|
_ts_decorate([
|
|
198
195
|
Post('delete-multiple-file'),
|
|
196
|
+
RequirePermission('file.delete'),
|
|
199
197
|
ApiOperation({
|
|
200
198
|
summary: 'Delete multiple files'
|
|
201
199
|
}),
|
|
@@ -217,12 +215,8 @@ UploadController = _ts_decorate([
|
|
|
217
215
|
Controller('storage/upload'),
|
|
218
216
|
UseGuards(JwtAuthGuard),
|
|
219
217
|
_ts_param(0, Inject(UploadService)),
|
|
220
|
-
_ts_param(1, Inject(StorageConfigService)),
|
|
221
|
-
_ts_param(2, Inject(StorageFactoryService)),
|
|
222
218
|
_ts_metadata("design:type", Function),
|
|
223
219
|
_ts_metadata("design:paramtypes", [
|
|
224
|
-
typeof UploadService === "undefined" ? Object : UploadService
|
|
225
|
-
typeof StorageConfigService === "undefined" ? Object : StorageConfigService,
|
|
226
|
-
typeof StorageFactoryService === "undefined" ? Object : StorageFactoryService
|
|
220
|
+
typeof UploadService === "undefined" ? Object : UploadService
|
|
227
221
|
])
|
|
228
222
|
], UploadController);
|
|
@@ -37,8 +37,8 @@ export class CreateFileManagerDto {
|
|
|
37
37
|
}
|
|
38
38
|
_ts_decorate([
|
|
39
39
|
ApiProperty({
|
|
40
|
-
description: '
|
|
41
|
-
example: '
|
|
40
|
+
description: 'Original file name',
|
|
41
|
+
example: 'vacation-photo.jpg'
|
|
42
42
|
}),
|
|
43
43
|
IsNotEmpty(),
|
|
44
44
|
IsString(),
|
|
@@ -46,8 +46,8 @@ _ts_decorate([
|
|
|
46
46
|
], CreateFileManagerDto.prototype, "name", void 0);
|
|
47
47
|
_ts_decorate([
|
|
48
48
|
ApiProperty({
|
|
49
|
-
description: '
|
|
50
|
-
example: '
|
|
49
|
+
description: 'Storage key or path of the file',
|
|
50
|
+
example: 'uploads/abc123-vacation-photo.jpg'
|
|
51
51
|
}),
|
|
52
52
|
IsNotEmpty(),
|
|
53
53
|
IsString(),
|
|
@@ -115,10 +115,11 @@ export class UpdateFileManagerDto extends PartialType(CreateFileManagerDto) {
|
|
|
115
115
|
}
|
|
116
116
|
_ts_decorate([
|
|
117
117
|
ApiProperty({
|
|
118
|
-
description: 'Unique identifier of the
|
|
118
|
+
description: 'Unique identifier of the file',
|
|
119
119
|
example: 'f2e9c8d0-7a2a-11eb-9439-0242ac130002'
|
|
120
120
|
}),
|
|
121
121
|
IsNotEmpty(),
|
|
122
|
+
IsString(),
|
|
122
123
|
_ts_metadata("design:type", String)
|
|
123
124
|
], UpdateFileManagerDto.prototype, "id", void 0);
|
|
124
125
|
export class FileManagerResponseDto extends UpdateFileManagerDto {
|
|
@@ -142,6 +143,8 @@ _ts_decorate([
|
|
|
142
143
|
ApiProperty({
|
|
143
144
|
example: 'f2e9c8d0-7a2a-11eb-9439-0242ac130002'
|
|
144
145
|
}),
|
|
146
|
+
IsNotEmpty(),
|
|
147
|
+
IsString(),
|
|
145
148
|
_ts_metadata("design:type", String)
|
|
146
149
|
], GetFilesRequestDto.prototype, "id", void 0);
|
|
147
150
|
export class FilesResponseDto {
|
|
@@ -23,14 +23,14 @@ function _ts_metadata(k, v) {
|
|
|
23
23
|
import { IdentityResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
24
24
|
import { FileLocationEnum } from '../enums';
|
|
25
25
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
26
|
-
import { IsEnum, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
|
|
27
|
-
|
|
28
|
-
* DTO for creating storage configuration
|
|
29
|
-
*/ export class CreateStorageConfigDto {
|
|
26
|
+
import { IsBoolean, IsEnum, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
|
|
27
|
+
export class CreateStorageConfigDto {
|
|
30
28
|
constructor(){
|
|
31
29
|
_define_property(this, "name", void 0);
|
|
32
30
|
_define_property(this, "storage", void 0);
|
|
33
31
|
_define_property(this, "config", void 0);
|
|
32
|
+
_define_property(this, "isActive", void 0);
|
|
33
|
+
_define_property(this, "isDefault", void 0);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
_ts_decorate([
|
|
@@ -78,14 +78,32 @@ _ts_decorate([
|
|
|
78
78
|
IsNotEmpty(),
|
|
79
79
|
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
80
80
|
], CreateStorageConfigDto.prototype, "config", void 0);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
_ts_decorate([
|
|
82
|
+
ApiPropertyOptional({
|
|
83
|
+
example: true,
|
|
84
|
+
description: 'Whether storage configuration is active'
|
|
85
|
+
}),
|
|
86
|
+
IsBoolean(),
|
|
87
|
+
IsOptional(),
|
|
88
|
+
_ts_metadata("design:type", Boolean)
|
|
89
|
+
], CreateStorageConfigDto.prototype, "isActive", void 0);
|
|
90
|
+
_ts_decorate([
|
|
91
|
+
ApiPropertyOptional({
|
|
92
|
+
example: false,
|
|
93
|
+
description: 'Set as default storage configuration'
|
|
94
|
+
}),
|
|
95
|
+
IsBoolean(),
|
|
96
|
+
IsOptional(),
|
|
97
|
+
_ts_metadata("design:type", Boolean)
|
|
98
|
+
], CreateStorageConfigDto.prototype, "isDefault", void 0);
|
|
99
|
+
export class UpdateStorageConfigDto {
|
|
84
100
|
constructor(){
|
|
85
101
|
_define_property(this, "id", void 0);
|
|
86
102
|
_define_property(this, "name", void 0);
|
|
87
103
|
_define_property(this, "storage", void 0);
|
|
88
104
|
_define_property(this, "config", void 0);
|
|
105
|
+
_define_property(this, "isActive", void 0);
|
|
106
|
+
_define_property(this, "isDefault", void 0);
|
|
89
107
|
}
|
|
90
108
|
}
|
|
91
109
|
_ts_decorate([
|
|
@@ -140,10 +158,26 @@ _ts_decorate([
|
|
|
140
158
|
IsOptional(),
|
|
141
159
|
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
142
160
|
], UpdateStorageConfigDto.prototype, "config", void 0);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
_ts_decorate([
|
|
162
|
+
ApiPropertyOptional({
|
|
163
|
+
example: true,
|
|
164
|
+
description: 'Whether storage configuration is active'
|
|
165
|
+
}),
|
|
166
|
+
IsBoolean(),
|
|
167
|
+
IsOptional(),
|
|
168
|
+
_ts_metadata("design:type", Boolean)
|
|
169
|
+
], UpdateStorageConfigDto.prototype, "isActive", void 0);
|
|
170
|
+
_ts_decorate([
|
|
171
|
+
ApiPropertyOptional({
|
|
172
|
+
example: false,
|
|
173
|
+
description: 'Set as default storage configuration'
|
|
174
|
+
}),
|
|
175
|
+
IsBoolean(),
|
|
176
|
+
IsOptional(),
|
|
177
|
+
_ts_metadata("design:type", Boolean)
|
|
178
|
+
], UpdateStorageConfigDto.prototype, "isDefault", void 0);
|
|
179
|
+
export class StorageConfigResponseDto extends IdentityResponseDto {
|
|
146
180
|
constructor(...args){
|
|
147
|
-
super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0), _define_property(this, "config", void 0);
|
|
181
|
+
super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0), _define_property(this, "config", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0);
|
|
148
182
|
}
|
|
149
183
|
}
|
package/fesm/dtos/upload.dto.js
CHANGED
|
@@ -21,7 +21,7 @@ function _ts_metadata(k, v) {
|
|
|
21
21
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
22
|
}
|
|
23
23
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
24
|
-
import { IsBoolean, IsEnum, IsInt, IsOptional, IsUUID, Matches, Max, Min } from 'class-validator';
|
|
24
|
+
import { IsArray, IsBoolean, IsEnum, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID, Matches, Max, Min } from 'class-validator';
|
|
25
25
|
import { Type } from 'class-transformer';
|
|
26
26
|
// Image format enum
|
|
27
27
|
export var ImageFormat = /*#__PURE__*/ function(ImageFormat) {
|
|
@@ -42,6 +42,8 @@ _ts_decorate([
|
|
|
42
42
|
description: 'Key of the file to delete',
|
|
43
43
|
example: '/uploads/some path'
|
|
44
44
|
}),
|
|
45
|
+
IsNotEmpty(),
|
|
46
|
+
IsString(),
|
|
45
47
|
_ts_metadata("design:type", String)
|
|
46
48
|
], DeleteSingleFileDto.prototype, "key", void 0);
|
|
47
49
|
_ts_decorate([
|
|
@@ -70,6 +72,11 @@ _ts_decorate([
|
|
|
70
72
|
String
|
|
71
73
|
]
|
|
72
74
|
}),
|
|
75
|
+
IsNotEmpty(),
|
|
76
|
+
IsArray(),
|
|
77
|
+
IsString({
|
|
78
|
+
each: true
|
|
79
|
+
}),
|
|
73
80
|
_ts_metadata("design:type", Array)
|
|
74
81
|
], DeleteMultipleFileDto.prototype, "keys", void 0);
|
|
75
82
|
_ts_decorate([
|
package/fesm/entities/index.js
CHANGED
|
@@ -28,14 +28,10 @@ export const StorageCompanyEntities = [
|
|
|
28
28
|
FolderWithCompany,
|
|
29
29
|
StorageConfigWithCompany
|
|
30
30
|
];
|
|
31
|
-
// All entities
|
|
32
31
|
export const StorageAllEntities = [
|
|
33
32
|
...StorageCoreEntities,
|
|
34
33
|
...StorageCompanyEntities
|
|
35
34
|
];
|
|
36
|
-
|
|
37
|
-
* Get Storage entities based on configuration
|
|
38
|
-
* @param enableCompanyFeature - Whether company feature is enabled
|
|
39
|
-
*/ export function getStorageEntitiesByConfig(enableCompanyFeature) {
|
|
35
|
+
export function getStorageEntitiesByConfig(enableCompanyFeature) {
|
|
40
36
|
return enableCompanyFeature ? StorageCompanyEntities : StorageCoreEntities;
|
|
41
37
|
}
|
|
@@ -22,14 +22,10 @@ function _ts_metadata(k, v) {
|
|
|
22
22
|
}
|
|
23
23
|
import { Identity } from '@flusys/nestjs-shared/entities';
|
|
24
24
|
import { FileLocationEnum } from '../enums';
|
|
25
|
-
import { Column } from 'typeorm';
|
|
26
|
-
|
|
27
|
-
* Base StorageConfig Entity
|
|
28
|
-
* Contains common fields shared by both StorageConfig and StorageConfigWithCompany
|
|
29
|
-
*/ export class StorageConfigBase extends Identity {
|
|
25
|
+
import { Column, Index } from 'typeorm';
|
|
26
|
+
export class StorageConfigBase extends Identity {
|
|
30
27
|
constructor(...args){
|
|
31
|
-
super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0),
|
|
32
|
-
_define_property(this, "config", void 0);
|
|
28
|
+
super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0), _define_property(this, "config", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0);
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
_ts_decorate([
|
|
@@ -55,3 +51,33 @@ _ts_decorate([
|
|
|
55
51
|
}),
|
|
56
52
|
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
57
53
|
], StorageConfigBase.prototype, "config", void 0);
|
|
54
|
+
_ts_decorate([
|
|
55
|
+
Column({
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
default: true,
|
|
58
|
+
name: 'is_active'
|
|
59
|
+
}),
|
|
60
|
+
_ts_metadata("design:type", Boolean)
|
|
61
|
+
], StorageConfigBase.prototype, "isActive", void 0);
|
|
62
|
+
_ts_decorate([
|
|
63
|
+
Column({
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
default: false,
|
|
66
|
+
name: 'is_default'
|
|
67
|
+
}),
|
|
68
|
+
_ts_metadata("design:type", Boolean)
|
|
69
|
+
], StorageConfigBase.prototype, "isDefault", void 0);
|
|
70
|
+
StorageConfigBase = _ts_decorate([
|
|
71
|
+
Index([
|
|
72
|
+
'name'
|
|
73
|
+
]),
|
|
74
|
+
Index([
|
|
75
|
+
'storage'
|
|
76
|
+
]),
|
|
77
|
+
Index([
|
|
78
|
+
'isActive'
|
|
79
|
+
]),
|
|
80
|
+
Index([
|
|
81
|
+
'isDefault'
|
|
82
|
+
])
|
|
83
|
+
], StorageConfigBase);
|
package/fesm/interfaces/index.js
CHANGED
|
@@ -20,7 +20,12 @@ 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
|
-
|
|
23
|
+
function _ts_param(paramIndex, decorator) {
|
|
24
|
+
return function(target, key) {
|
|
25
|
+
decorator(target, key, paramIndex);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
import { Injectable, Logger, NotFoundException, Inject } from '@nestjs/common';
|
|
24
29
|
import { createReadStream, existsSync, statSync } from 'fs';
|
|
25
30
|
import { join, resolve } from 'path';
|
|
26
31
|
import * as mime from 'mime-types';
|
|
@@ -146,6 +151,7 @@ export class FileServeMiddleware {
|
|
|
146
151
|
}
|
|
147
152
|
FileServeMiddleware = _ts_decorate([
|
|
148
153
|
Injectable(),
|
|
154
|
+
_ts_param(0, Inject(UploadService)),
|
|
149
155
|
_ts_metadata("design:type", Function),
|
|
150
156
|
_ts_metadata("design:paramtypes", [
|
|
151
157
|
typeof UploadService === "undefined" ? Object : UploadService
|
|
@@ -32,6 +32,29 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
32
32
|
* Uses Node.js built-in fs module - no external dependencies
|
|
33
33
|
*/ export class LocalProvider {
|
|
34
34
|
/**
|
|
35
|
+
* SECURITY: Validates that a target path does not escape the base directory
|
|
36
|
+
* Prevents path traversal attacks using ../ sequences
|
|
37
|
+
* @throws Error if path traversal is detected
|
|
38
|
+
*/ validatePathWithinBase(targetPath) {
|
|
39
|
+
const normalizedBasePath = path.resolve(this.basePath);
|
|
40
|
+
const normalizedTargetPath = path.resolve(targetPath);
|
|
41
|
+
if (!normalizedTargetPath.startsWith(normalizedBasePath + path.sep) && normalizedTargetPath !== normalizedBasePath) {
|
|
42
|
+
this.logger.warn(`Path traversal attempt detected: ${targetPath}`);
|
|
43
|
+
throw new Error('Invalid path: Path traversal attempt detected');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* SECURITY: Validates file key format to prevent malicious input
|
|
48
|
+
* @throws Error if key contains suspicious patterns
|
|
49
|
+
*/ validateKeyFormat(key) {
|
|
50
|
+
if (!key || typeof key !== 'string' || key.trim().length === 0) {
|
|
51
|
+
throw new Error('Invalid file key: empty or invalid');
|
|
52
|
+
}
|
|
53
|
+
if (key.includes('\0')) {
|
|
54
|
+
throw new Error('Invalid file key: contains null bytes');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
35
58
|
* Initialize Local File System provider with configuration
|
|
36
59
|
* @param config.basePath - Base path for file storage (default: './uploads')
|
|
37
60
|
* @param config.baseUrl - Optional base URL for generating file URLs
|
|
@@ -64,7 +87,11 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
64
87
|
}
|
|
65
88
|
// Build file path
|
|
66
89
|
const folderPath = options.folderPath ? path.join(this.basePath, options.folderPath) : this.basePath;
|
|
90
|
+
// SECURITY: Validate path does not escape base directory
|
|
91
|
+
this.validatePathWithinBase(folderPath);
|
|
67
92
|
const filePath = path.join(folderPath, fileName);
|
|
93
|
+
// SECURITY: Double-check final file path
|
|
94
|
+
this.validatePathWithinBase(filePath);
|
|
68
95
|
// Ensure directory exists
|
|
69
96
|
await fs.mkdir(folderPath, {
|
|
70
97
|
recursive: true
|
|
@@ -87,14 +114,20 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
87
114
|
}
|
|
88
115
|
async deleteFile(key) {
|
|
89
116
|
try {
|
|
117
|
+
// SECURITY: Validate key format first
|
|
118
|
+
this.validateKeyFormat(key);
|
|
90
119
|
// Key now includes the basePath, resolve from cwd
|
|
91
120
|
let filePath = path.resolve(key);
|
|
121
|
+
// SECURITY: Validate resolved path is within base directory
|
|
122
|
+
this.validatePathWithinBase(filePath);
|
|
92
123
|
// Check if file exists at the resolved path
|
|
93
124
|
try {
|
|
94
125
|
await fs.access(filePath);
|
|
95
126
|
} catch {
|
|
96
127
|
// Fallback: try with basePath prefix (for old keys without basePath)
|
|
97
128
|
const fallbackPath = path.join(this.basePath, key);
|
|
129
|
+
// SECURITY: Validate fallback path as well
|
|
130
|
+
this.validatePathWithinBase(fallbackPath);
|
|
98
131
|
try {
|
|
99
132
|
await fs.access(fallbackPath);
|
|
100
133
|
filePath = fallbackPath;
|
|
@@ -107,7 +140,11 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
107
140
|
}
|
|
108
141
|
await fs.unlink(filePath);
|
|
109
142
|
this.logger.log(`Deleted file from local storage: ${key}`);
|
|
110
|
-
} catch (
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Re-throw security errors
|
|
145
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
111
148
|
this.logger.warn(`Failed to delete file from local storage: ${key}`);
|
|
112
149
|
}
|
|
113
150
|
}
|
|
@@ -138,14 +175,23 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
138
175
|
* Get the absolute path for a file key
|
|
139
176
|
* Key now includes basePath, so resolve from cwd
|
|
140
177
|
*/ getAbsolutePath(key) {
|
|
141
|
-
|
|
178
|
+
// SECURITY: Validate key format
|
|
179
|
+
this.validateKeyFormat(key);
|
|
180
|
+
const filePath = path.resolve(key);
|
|
181
|
+
// SECURITY: Validate path is within base directory
|
|
182
|
+
this.validatePathWithinBase(filePath);
|
|
183
|
+
return filePath;
|
|
142
184
|
}
|
|
143
185
|
/**
|
|
144
186
|
* Check if a file exists
|
|
145
187
|
*/ async fileExists(key) {
|
|
146
188
|
try {
|
|
189
|
+
// SECURITY: Validate key format
|
|
190
|
+
this.validateKeyFormat(key);
|
|
147
191
|
// Key now includes basePath, resolve from cwd
|
|
148
192
|
const filePath = path.resolve(key);
|
|
193
|
+
// SECURITY: Validate path is within base directory
|
|
194
|
+
this.validatePathWithinBase(filePath);
|
|
149
195
|
await fs.access(filePath);
|
|
150
196
|
return true;
|
|
151
197
|
} catch {
|
|
@@ -155,8 +201,12 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
155
201
|
/**
|
|
156
202
|
* Get file stats
|
|
157
203
|
*/ async getFileStats(key) {
|
|
204
|
+
// SECURITY: Validate key format
|
|
205
|
+
this.validateKeyFormat(key);
|
|
158
206
|
// Key now includes basePath, resolve from cwd
|
|
159
207
|
const filePath = path.resolve(key);
|
|
208
|
+
// SECURITY: Validate path is within base directory
|
|
209
|
+
this.validatePathWithinBase(filePath);
|
|
160
210
|
const stats = await fs.stat(filePath);
|
|
161
211
|
return {
|
|
162
212
|
size: stats.size,
|
|
@@ -79,8 +79,8 @@ export class StorageFactoryService {
|
|
|
79
79
|
} catch (error) {
|
|
80
80
|
this.logger.error(`Failed to create provider ${config.provider}:`, error);
|
|
81
81
|
// Preserve original error message for better debugging
|
|
82
|
-
const
|
|
83
|
-
throw new Error(`Failed to initialize storage provider '${config.provider}': ${
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
83
|
+
throw new Error(`Failed to initialize storage provider '${config.provider}': ${errorMessage}`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
/**
|