@flusys/nestjs-storage 7.0.2 → 8.0.0-beta.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 (202) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/file-manager.controller.d.ts +4 -4
  3. package/controllers/folder.controller.d.ts +2 -2
  4. package/controllers/index.d.ts +4 -4
  5. package/controllers/storage-config.controller.d.ts +2 -2
  6. package/controllers/upload.controller.d.ts +3 -3
  7. package/docs/index.d.ts +1 -1
  8. package/docs/storage-swagger.config.d.ts +2 -2
  9. package/dtos/index.d.ts +4 -4
  10. package/entities/file-manager-with-company.entity.d.ts +2 -2
  11. package/entities/file-manager.entity.d.ts +1 -1
  12. package/entities/folder-with-company.entity.d.ts +1 -1
  13. package/entities/index.d.ts +15 -15
  14. package/entities/storage-config-with-company.entity.d.ts +1 -1
  15. package/enums/index.d.ts +1 -1
  16. package/fesm/493.js +2768 -0
  17. package/fesm/537.js +1157 -0
  18. package/fesm/562.js +786 -0
  19. package/fesm/{providers/azure-provider.optional.js → chunks/190.js} +33 -30
  20. package/fesm/chunks/252.js +12 -0
  21. package/fesm/chunks/327.js +12 -0
  22. package/fesm/chunks/385.js +12 -0
  23. package/fesm/{providers/s3-provider.optional.js → chunks/591.js} +37 -34
  24. package/fesm/chunks/746.js +12 -0
  25. package/fesm/chunks/798.js +12 -0
  26. package/fesm/{providers/sftp-provider.optional.js → chunks/918.js} +35 -31
  27. package/fesm/config/index.js +119 -3
  28. package/fesm/controllers/index.js +16 -4
  29. package/fesm/docs/index.js +206 -1
  30. package/fesm/dtos/index.js +879 -4
  31. package/fesm/entities/index.js +463 -18
  32. package/fesm/enums/index.js +36 -1
  33. package/fesm/index.js +398 -12
  34. package/fesm/interfaces/index.js +19 -5
  35. package/fesm/modules/index.js +16 -1
  36. package/fesm/providers/index.js +364 -3
  37. package/fesm/runtime.js +252 -0
  38. package/fesm/services/index.js +16 -6
  39. package/fesm/utils/index.js +149 -1
  40. package/index.d.ts +11 -11
  41. package/interfaces/file-manager.interface.d.ts +1 -1
  42. package/interfaces/folder.interface.d.ts +1 -1
  43. package/interfaces/index.d.ts +5 -5
  44. package/interfaces/storage-config.interface.d.ts +1 -1
  45. package/interfaces/storage-module-options.interface.d.ts +3 -3
  46. package/interfaces/storage-provider.interface.d.ts +1 -1
  47. package/middlewares/file-serve.middleware.d.ts +4 -3
  48. package/middlewares/index.d.ts +1 -1
  49. package/modules/index.d.ts +1 -1
  50. package/modules/storage.module.d.ts +2 -2
  51. package/package.json +30 -41
  52. package/providers/azure-provider.optional.d.ts +2 -2
  53. package/providers/index.d.ts +3 -3
  54. package/providers/local-provider.d.ts +2 -2
  55. package/providers/s3-provider.optional.d.ts +2 -2
  56. package/providers/sftp-provider.optional.d.ts +2 -2
  57. package/providers/storage-factory.service.d.ts +3 -3
  58. package/providers/storage-provider.registry.d.ts +2 -2
  59. package/services/file-manager.service.d.ts +9 -8
  60. package/services/folder.service.d.ts +6 -6
  61. package/services/index.d.ts +6 -6
  62. package/services/storage-config.service.d.ts +3 -3
  63. package/services/storage-datasource.provider.d.ts +3 -3
  64. package/services/storage-provider-config.service.d.ts +7 -7
  65. package/services/upload.service.d.ts +6 -6
  66. package/utils/image-compressor.util.d.ts +1 -1
  67. package/utils/index.d.ts +1 -1
  68. package/cjs/config/event-actions.js +0 -34
  69. package/cjs/config/index.js +0 -20
  70. package/cjs/config/message-keys.js +0 -46
  71. package/cjs/config/storage.constants.js +0 -28
  72. package/cjs/controllers/file-manager.controller.js +0 -152
  73. package/cjs/controllers/file-manager.controller.spec.js +0 -135
  74. package/cjs/controllers/folder.controller.js +0 -102
  75. package/cjs/controllers/folder.controller.spec.js +0 -86
  76. package/cjs/controllers/index.js +0 -21
  77. package/cjs/controllers/storage-config.controller.js +0 -102
  78. package/cjs/controllers/storage-config.controller.spec.js +0 -85
  79. package/cjs/controllers/upload.controller.js +0 -234
  80. package/cjs/controllers/upload.controller.spec.js +0 -132
  81. package/cjs/docs/index.js +0 -11
  82. package/cjs/docs/storage-swagger.config.js +0 -188
  83. package/cjs/docs/storage-swagger.config.spec.js +0 -72
  84. package/cjs/dtos/file-manager.dto.js +0 -327
  85. package/cjs/dtos/folder.dto.js +0 -105
  86. package/cjs/dtos/index.js +0 -21
  87. package/cjs/dtos/storage-config.dto.js +0 -170
  88. package/cjs/dtos/upload.dto.js +0 -272
  89. package/cjs/entities/file-manager-with-company.entity.js +0 -61
  90. package/cjs/entities/file-manager.entity.js +0 -122
  91. package/cjs/entities/folder-with-company.entity.js +0 -55
  92. package/cjs/entities/folder.entity.js +0 -64
  93. package/cjs/entities/index.js +0 -63
  94. package/cjs/entities/index.spec.js +0 -18
  95. package/cjs/entities/storage-config-with-company.entity.js +0 -51
  96. package/cjs/entities/storage-config.entity.js +0 -96
  97. package/cjs/enums/file-location.enum.js +0 -17
  98. package/cjs/enums/index.js +0 -18
  99. package/cjs/index.js +0 -29
  100. package/cjs/interfaces/file-manager.interface.js +0 -4
  101. package/cjs/interfaces/folder.interface.js +0 -4
  102. package/cjs/interfaces/index.js +0 -22
  103. package/cjs/interfaces/storage-config.interface.js +0 -4
  104. package/cjs/interfaces/storage-module-options.interface.js +0 -4
  105. package/cjs/interfaces/storage-provider.interface.js +0 -4
  106. package/cjs/middlewares/file-serve.middleware.js +0 -241
  107. package/cjs/middlewares/file-serve.middleware.spec.js +0 -211
  108. package/cjs/middlewares/index.js +0 -18
  109. package/cjs/modules/index.js +0 -18
  110. package/cjs/modules/storage.module.js +0 -165
  111. package/cjs/modules/storage.module.spec.js +0 -184
  112. package/cjs/providers/azure-provider.optional.js +0 -155
  113. package/cjs/providers/azure-provider.optional.spec.js +0 -228
  114. package/cjs/providers/index.js +0 -26
  115. package/cjs/providers/local-provider.js +0 -230
  116. package/cjs/providers/local-provider.spec.js +0 -239
  117. package/cjs/providers/s3-provider.optional.js +0 -175
  118. package/cjs/providers/s3-provider.optional.spec.js +0 -243
  119. package/cjs/providers/sftp-provider.optional.js +0 -203
  120. package/cjs/providers/sftp-provider.optional.spec.js +0 -217
  121. package/cjs/providers/storage-factory.service.js +0 -175
  122. package/cjs/providers/storage-factory.service.spec.js +0 -253
  123. package/cjs/providers/storage-provider.registry.js +0 -41
  124. package/cjs/providers/storage-provider.registry.spec.js +0 -67
  125. package/cjs/services/file-manager.service.js +0 -387
  126. package/cjs/services/file-manager.service.spec.js +0 -580
  127. package/cjs/services/folder.service.js +0 -101
  128. package/cjs/services/folder.service.spec.js +0 -167
  129. package/cjs/services/index.js +0 -23
  130. package/cjs/services/storage-config.service.js +0 -129
  131. package/cjs/services/storage-config.service.spec.js +0 -257
  132. package/cjs/services/storage-datasource.provider.js +0 -137
  133. package/cjs/services/storage-datasource.provider.spec.js +0 -217
  134. package/cjs/services/storage-provider-config.service.js +0 -171
  135. package/cjs/services/storage-provider-config.service.spec.js +0 -265
  136. package/cjs/services/upload.service.js +0 -376
  137. package/cjs/services/upload.service.spec.js +0 -426
  138. package/cjs/utils/file-validator.util.js +0 -440
  139. package/cjs/utils/file-validator.util.spec.js +0 -259
  140. package/cjs/utils/image-compressor.util.js +0 -162
  141. package/cjs/utils/image-compressor.util.spec.js +0 -166
  142. package/cjs/utils/index.js +0 -18
  143. package/fesm/config/event-actions.js +0 -13
  144. package/fesm/config/message-keys.js +0 -22
  145. package/fesm/config/storage.constants.js +0 -7
  146. package/fesm/controllers/file-manager.controller.js +0 -142
  147. package/fesm/controllers/file-manager.controller.spec.js +0 -131
  148. package/fesm/controllers/folder.controller.js +0 -92
  149. package/fesm/controllers/folder.controller.spec.js +0 -82
  150. package/fesm/controllers/storage-config.controller.js +0 -92
  151. package/fesm/controllers/storage-config.controller.spec.js +0 -81
  152. package/fesm/controllers/upload.controller.js +0 -224
  153. package/fesm/controllers/upload.controller.spec.js +0 -128
  154. package/fesm/docs/storage-swagger.config.js +0 -181
  155. package/fesm/docs/storage-swagger.config.spec.js +0 -68
  156. package/fesm/dtos/file-manager.dto.js +0 -300
  157. package/fesm/dtos/folder.dto.js +0 -84
  158. package/fesm/dtos/storage-config.dto.js +0 -149
  159. package/fesm/dtos/upload.dto.js +0 -245
  160. package/fesm/entities/file-manager-with-company.entity.js +0 -51
  161. package/fesm/entities/file-manager.entity.js +0 -112
  162. package/fesm/entities/folder-with-company.entity.js +0 -45
  163. package/fesm/entities/folder.entity.js +0 -54
  164. package/fesm/entities/index.spec.js +0 -14
  165. package/fesm/entities/storage-config-with-company.entity.js +0 -41
  166. package/fesm/entities/storage-config.entity.js +0 -86
  167. package/fesm/enums/file-location.enum.js +0 -7
  168. package/fesm/interfaces/file-manager.interface.js +0 -1
  169. package/fesm/interfaces/folder.interface.js +0 -1
  170. package/fesm/interfaces/storage-config.interface.js +0 -1
  171. package/fesm/interfaces/storage-module-options.interface.js +0 -3
  172. package/fesm/interfaces/storage-provider.interface.js +0 -4
  173. package/fesm/middlewares/file-serve.middleware.js +0 -190
  174. package/fesm/middlewares/file-serve.middleware.spec.js +0 -207
  175. package/fesm/middlewares/index.js +0 -1
  176. package/fesm/modules/storage.module.js +0 -156
  177. package/fesm/modules/storage.module.spec.js +0 -180
  178. package/fesm/providers/azure-provider.optional.spec.js +0 -224
  179. package/fesm/providers/local-provider.js +0 -182
  180. package/fesm/providers/local-provider.spec.js +0 -194
  181. package/fesm/providers/s3-provider.optional.spec.js +0 -239
  182. package/fesm/providers/sftp-provider.optional.spec.js +0 -213
  183. package/fesm/providers/storage-factory.service.js +0 -124
  184. package/fesm/providers/storage-factory.service.spec.js +0 -249
  185. package/fesm/providers/storage-provider.registry.js +0 -34
  186. package/fesm/providers/storage-provider.registry.spec.js +0 -63
  187. package/fesm/services/file-manager.service.js +0 -336
  188. package/fesm/services/file-manager.service.spec.js +0 -576
  189. package/fesm/services/folder.service.js +0 -91
  190. package/fesm/services/folder.service.spec.js +0 -163
  191. package/fesm/services/storage-config.service.js +0 -119
  192. package/fesm/services/storage-config.service.spec.js +0 -253
  193. package/fesm/services/storage-datasource.provider.js +0 -86
  194. package/fesm/services/storage-datasource.provider.spec.js +0 -172
  195. package/fesm/services/storage-provider-config.service.js +0 -161
  196. package/fesm/services/storage-provider-config.service.spec.js +0 -261
  197. package/fesm/services/upload.service.js +0 -366
  198. package/fesm/services/upload.service.spec.js +0 -422
  199. package/fesm/utils/file-validator.util.js +0 -433
  200. package/fesm/utils/file-validator.util.spec.js +0 -255
  201. package/fesm/utils/image-compressor.util.js +0 -114
  202. package/fesm/utils/image-compressor.util.spec.js +0 -162
@@ -1,576 +0,0 @@
1
- import { Test } from '@nestjs/testing';
2
- import { SharedPermissionCacheService, UtilsService } from '@flusys/nestjs-shared/modules';
3
- import { createMockRepository } from '@test-utils/mocks/repository.mock';
4
- import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
5
- import { FileManagerService } from './file-manager.service';
6
- import { StorageConfigService } from './storage-config.service';
7
- import { StorageDataSourceProvider } from './storage-datasource.provider';
8
- import { UploadService } from './upload.service';
9
- import { FileManager, FileManagerWithCompany, Folder, FolderWithCompany, StorageConfig, StorageConfigWithCompany } from '../entities';
10
- import { FileLocationEnum } from '../enums/file-location.enum';
11
- function buildFile(overrides = {}) {
12
- return {
13
- id: 'file-uuid-1',
14
- name: 'photo.jpg',
15
- contentType: 'image/jpeg',
16
- size: '1024',
17
- key: 'uploads/photo.jpg',
18
- url: null,
19
- location: FileLocationEnum.LOCAL,
20
- storageConfigId: null,
21
- expiresAt: null,
22
- isPrivate: false,
23
- folder: null,
24
- createdAt: new Date(),
25
- updatedAt: new Date(),
26
- deletedAt: null,
27
- createdById: null,
28
- updatedById: null,
29
- deletedById: null,
30
- ...overrides
31
- };
32
- }
33
- describe('FileManagerService', ()=>{
34
- let service;
35
- let module;
36
- let mockFileRepo;
37
- let mockFolderRepo;
38
- let mockStorageConfigRepo;
39
- let mockStorageConfig;
40
- let mockUploadService;
41
- let mockCacheManager;
42
- let mockSharedPermissionCacheService;
43
- const mockUtilsService = {
44
- clearCache: jest.fn(),
45
- getCacheKey: jest.fn().mockReturnValue('cache-key'),
46
- trackCacheKey: jest.fn()
47
- };
48
- // Call history is shared across tests in this file; reset it so each cache assertion is exact.
49
- beforeEach(()=>{
50
- mockUtilsService.clearCache.mockClear();
51
- });
52
- beforeEach(async ()=>{
53
- mockFileRepo = createMockRepository();
54
- mockFileRepo.findBy = jest.fn();
55
- mockFolderRepo = createMockRepository();
56
- mockStorageConfigRepo = createMockRepository();
57
- const mockDataSourceProvider = {
58
- getRepository: jest.fn((entity)=>{
59
- if (entity === Folder || entity === FolderWithCompany) {
60
- return Promise.resolve(mockFolderRepo);
61
- }
62
- if (entity === StorageConfig || entity === StorageConfigWithCompany) {
63
- return Promise.resolve(mockStorageConfigRepo);
64
- }
65
- return Promise.resolve(mockFileRepo);
66
- }),
67
- getDataSource: jest.fn()
68
- };
69
- mockStorageConfig = {
70
- isCompanyFeatureEnabled: jest.fn().mockReturnValue(false),
71
- getAppUrl: jest.fn().mockReturnValue('http://localhost:2002')
72
- };
73
- mockUploadService = {
74
- deleteMultipleFile: jest.fn().mockResolvedValue(true),
75
- makeFileUrl: jest.fn().mockResolvedValue('https://signed.example.com/photo.jpg')
76
- };
77
- mockCacheManager = {
78
- get: jest.fn(),
79
- set: jest.fn()
80
- };
81
- mockSharedPermissionCacheService = {
82
- hasPermission: jest.fn().mockResolvedValue(false)
83
- };
84
- module = await Test.createTestingModule({
85
- providers: [
86
- FileManagerService,
87
- {
88
- provide: 'CACHE_INSTANCE',
89
- useValue: mockCacheManager
90
- },
91
- {
92
- provide: UtilsService,
93
- useValue: mockUtilsService
94
- },
95
- {
96
- provide: UploadService,
97
- useValue: mockUploadService
98
- },
99
- {
100
- provide: StorageConfigService,
101
- useValue: mockStorageConfig
102
- },
103
- {
104
- provide: StorageDataSourceProvider,
105
- useValue: mockDataSourceProvider
106
- },
107
- {
108
- provide: SharedPermissionCacheService,
109
- useValue: mockSharedPermissionCacheService
110
- }
111
- ]
112
- }).compile();
113
- service = await module.resolve(FileManagerService);
114
- await service.ensureDataSourceRepository();
115
- });
116
- describe('entity selection', ()=>{
117
- it('uses FileManager when the company feature is disabled', async ()=>{
118
- mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(false);
119
- const built = await module.resolve(FileManagerService);
120
- expect(built['_entity']).toBe(FileManager);
121
- });
122
- it('uses FileManagerWithCompany when the company feature is enabled', async ()=>{
123
- mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
124
- const built = await module.resolve(FileManagerService);
125
- expect(built['_entity']).toBe(FileManagerWithCompany);
126
- });
127
- });
128
- describe('convertSingleDtoToEntity', ()=>{
129
- it('creates a new entity with location LOCAL by default when no folder/storageConfig given', async ()=>{
130
- const entity = await service.convertSingleDtoToEntity({
131
- name: 'a.jpg',
132
- key: 'k',
133
- size: '10',
134
- contentType: 'image/jpeg',
135
- isPrivate: false
136
- }, buildMockUser());
137
- expect(entity.location).toBe(FileLocationEnum.LOCAL);
138
- expect(entity.folder).toBeNull();
139
- });
140
- it('preserves an explicitly provided location when no storageConfigId is given', async ()=>{
141
- const entity = await service.convertSingleDtoToEntity({
142
- name: 'a.jpg',
143
- key: 'k',
144
- size: '10',
145
- contentType: 'image/jpeg',
146
- isPrivate: false,
147
- location: FileLocationEnum.AWS
148
- }, buildMockUser());
149
- expect(entity.location).toBe(FileLocationEnum.AWS);
150
- });
151
- it('resolves the storage location from the referenced storage config', async ()=>{
152
- mockStorageConfigRepo.findOne.mockResolvedValue({
153
- id: 'cfg-1',
154
- storage: 'aws'
155
- });
156
- const entity = await service.convertSingleDtoToEntity({
157
- name: 'a.jpg',
158
- key: 'k',
159
- size: '10',
160
- contentType: 'image/jpeg',
161
- isPrivate: false,
162
- storageConfigId: 'cfg-1'
163
- }, buildMockUser());
164
- expect(entity.location).toBe('aws');
165
- });
166
- it('falls back to dto.location when the storage config lookup throws', async ()=>{
167
- mockStorageConfigRepo.findOne.mockRejectedValue(new Error('db down'));
168
- const entity = await service.convertSingleDtoToEntity({
169
- name: 'a.jpg',
170
- key: 'k',
171
- size: '10',
172
- contentType: 'image/jpeg',
173
- isPrivate: false,
174
- storageConfigId: 'cfg-1',
175
- location: FileLocationEnum.AZURE
176
- }, buildMockUser());
177
- expect(entity.location).toBe(FileLocationEnum.AZURE);
178
- });
179
- it('attaches the validated folder when folderId is provided', async ()=>{
180
- const folder = {
181
- id: 'folder-1',
182
- name: 'Docs'
183
- };
184
- mockFolderRepo.findOne.mockResolvedValue(folder);
185
- const entity = await service.convertSingleDtoToEntity({
186
- name: 'a.jpg',
187
- key: 'k',
188
- size: '10',
189
- contentType: 'image/jpeg',
190
- isPrivate: false,
191
- folderId: 'folder-1'
192
- }, buildMockUser());
193
- expect(entity.folder).toEqual(folder);
194
- });
195
- it('throws BadRequestException when the referenced folder does not exist or access is denied', async ()=>{
196
- mockFolderRepo.findOne.mockResolvedValue(null);
197
- await expect(service.convertSingleDtoToEntity({
198
- name: 'a.jpg',
199
- key: 'k',
200
- size: '10',
201
- contentType: 'image/jpeg',
202
- isPrivate: false,
203
- folderId: 'missing-folder'
204
- }, buildMockUser())).rejects.toThrow();
205
- });
206
- it('scopes folder lookup by companyId when company feature is enabled', async ()=>{
207
- mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
208
- mockFolderRepo.findOne.mockResolvedValue({
209
- id: 'folder-1'
210
- });
211
- await service.convertSingleDtoToEntity({
212
- name: 'a.jpg',
213
- key: 'k',
214
- size: '10',
215
- contentType: 'image/jpeg',
216
- isPrivate: false,
217
- folderId: 'folder-1'
218
- }, buildMockUser({
219
- companyId: 'company-9'
220
- }));
221
- expect(mockFolderRepo.findOne).toHaveBeenCalledWith({
222
- where: {
223
- id: 'folder-1',
224
- companyId: 'company-9'
225
- }
226
- });
227
- });
228
- it('attaches companyId to the entity when company feature is enabled', async ()=>{
229
- mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
230
- const entity = await service.convertSingleDtoToEntity({
231
- name: 'a.jpg',
232
- key: 'k',
233
- size: '10',
234
- contentType: 'image/jpeg',
235
- isPrivate: false
236
- }, buildMockUser({
237
- companyId: 'company-9'
238
- }));
239
- expect(entity.companyId).toBe('company-9');
240
- });
241
- it('loads and merges the existing entity when updating (dto has an id)', async ()=>{
242
- const existing = buildFile({
243
- id: 'file-1',
244
- name: 'old.jpg'
245
- });
246
- mockFileRepo.findOne.mockResolvedValue(existing);
247
- const entity = await service.convertSingleDtoToEntity({
248
- id: 'file-1',
249
- name: 'new.jpg'
250
- }, buildMockUser());
251
- expect(entity.name).toBe('new.jpg');
252
- });
253
- it('throws NotFoundException when updating a file that does not exist', async ()=>{
254
- mockFileRepo.findOne.mockResolvedValue(null);
255
- await expect(service.convertSingleDtoToEntity({
256
- id: 'missing-id',
257
- name: 'x'
258
- }, buildMockUser())).rejects.toThrow();
259
- });
260
- });
261
- describe('getSelectQuery', ()=>{
262
- it('selects the default field set and left-joins the folder relation', async ()=>{
263
- const query = {
264
- select: jest.fn().mockReturnThis(),
265
- leftJoinAndSelect: jest.fn().mockReturnThis()
266
- };
267
- await service.getSelectQuery(query, buildMockUser());
268
- expect(query.leftJoinAndSelect).toHaveBeenCalledWith('file_manager.folder', 'folder');
269
- const selectArg = query.select.mock.calls[0][0];
270
- expect(selectArg).toEqual(expect.arrayContaining([
271
- 'file_manager.id',
272
- 'file_manager.name',
273
- 'file_manager.key',
274
- 'folder.id',
275
- 'folder.name'
276
- ]));
277
- });
278
- it('appends companyId to the field set when company feature is enabled', async ()=>{
279
- mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
280
- const query = {
281
- select: jest.fn().mockReturnThis(),
282
- leftJoinAndSelect: jest.fn().mockReturnThis()
283
- };
284
- await service.getSelectQuery(query, buildMockUser());
285
- const selectArg = query.select.mock.calls[0][0];
286
- expect(selectArg).toContain('file_manager.companyId');
287
- });
288
- it('uses a custom select list when explicitly provided', async ()=>{
289
- const query = {
290
- select: jest.fn().mockReturnThis(),
291
- leftJoinAndSelect: jest.fn().mockReturnThis()
292
- };
293
- await service.getSelectQuery(query, buildMockUser(), [
294
- 'id',
295
- 'name'
296
- ]);
297
- const selectArg = query.select.mock.calls[0][0];
298
- expect(selectArg).toEqual(expect.arrayContaining([
299
- 'file_manager.id',
300
- 'file_manager.name'
301
- ]));
302
- });
303
- });
304
- describe('getFilterQuery', ()=>{
305
- it('applies a LIKE-based bracket filter for contentType with wildcard support', async ()=>{
306
- const query = {
307
- where: jest.fn().mockReturnThis(),
308
- andWhere: jest.fn().mockReturnThis()
309
- };
310
- await service.getFilterQuery(query, {
311
- contentType: 'image/*,application/pdf'
312
- }, buildMockUser());
313
- expect(query.where).toHaveBeenCalled();
314
- });
315
- it('applies a plain equality filter for non-contentType fields', async ()=>{
316
- const query = {
317
- where: jest.fn().mockReturnThis(),
318
- andWhere: jest.fn().mockReturnThis()
319
- };
320
- await service.getFilterQuery(query, {
321
- isPrivate: true
322
- }, buildMockUser());
323
- expect(query.andWhere).toHaveBeenCalledWith('file_manager.isPrivate = :value', {
324
- value: true
325
- });
326
- });
327
- });
328
- describe('getExtraManipulateQuery', ()=>{
329
- it('applies the company filter when company feature is enabled', async ()=>{
330
- mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
331
- const query = {
332
- andWhere: jest.fn().mockReturnThis()
333
- };
334
- await service.getExtraManipulateQuery(query, {}, buildMockUser({
335
- companyId: 'company-3'
336
- }));
337
- expect(query.andWhere).toHaveBeenCalledWith('file_manager.companyId = :companyId', {
338
- companyId: 'company-3'
339
- });
340
- });
341
- it('hides private files from anonymous (unauthenticated) requests', async ()=>{
342
- const query = {
343
- andWhere: jest.fn().mockReturnThis()
344
- };
345
- await service.getExtraManipulateQuery(query, {}, null);
346
- expect(query.andWhere).toHaveBeenCalledWith('file_manager.isPrivate = :isPrivate', {
347
- isPrivate: false
348
- });
349
- });
350
- it('hides private files from an authenticated user lacking the viewPrivate permission', async ()=>{
351
- mockSharedPermissionCacheService.hasPermission.mockResolvedValue(false);
352
- const query = {
353
- andWhere: jest.fn().mockReturnThis()
354
- };
355
- await service.getExtraManipulateQuery(query, {}, buildMockUser());
356
- expect(query.andWhere).toHaveBeenCalledWith('file_manager.isPrivate = :isPrivate', {
357
- isPrivate: false
358
- });
359
- });
360
- it('does not restrict private files for a user holding the viewPrivate permission', async ()=>{
361
- mockSharedPermissionCacheService.hasPermission.mockResolvedValue(true);
362
- const query = {
363
- andWhere: jest.fn().mockReturnThis()
364
- };
365
- await service.getExtraManipulateQuery(query, {}, buildMockUser());
366
- expect(query.andWhere).not.toHaveBeenCalledWith('file_manager.isPrivate = :isPrivate', expect.anything());
367
- });
368
- });
369
- describe('beforeDeleteOperation', ()=>{
370
- it('does nothing for a soft delete (type=delete)', async ()=>{
371
- await service.beforeDeleteOperation({
372
- id: [
373
- 'file-1'
374
- ],
375
- type: 'delete'
376
- }, buildMockUser(), {});
377
- expect(mockUploadService.deleteMultipleFile).not.toHaveBeenCalled();
378
- });
379
- it('deletes underlying storage objects when permanently deleting files', async ()=>{
380
- const file = buildFile({
381
- id: 'file-1',
382
- key: 'uploads/photo.jpg',
383
- storageConfigId: null,
384
- location: FileLocationEnum.LOCAL
385
- });
386
- mockFileRepo.findBy.mockResolvedValue([
387
- file
388
- ]);
389
- await service.beforeDeleteOperation({
390
- id: [
391
- 'file-1'
392
- ],
393
- type: 'permanent'
394
- }, buildMockUser(), {});
395
- expect(mockUploadService.deleteMultipleFile).toHaveBeenCalledWith([
396
- 'uploads/photo.jpg'
397
- ], undefined, buildMockUser(), FileLocationEnum.LOCAL);
398
- });
399
- it('groups files by storageConfigId before issuing delete calls', async ()=>{
400
- const fileA = buildFile({
401
- id: 'a',
402
- key: 'a.jpg',
403
- storageConfigId: 'cfg-1',
404
- location: FileLocationEnum.AWS
405
- });
406
- const fileB = buildFile({
407
- id: 'b',
408
- key: 'b.jpg',
409
- storageConfigId: 'cfg-2',
410
- location: FileLocationEnum.AZURE
411
- });
412
- mockFileRepo.findBy.mockResolvedValue([
413
- fileA,
414
- fileB
415
- ]);
416
- await service.beforeDeleteOperation({
417
- id: [
418
- 'a',
419
- 'b'
420
- ],
421
- type: 'permanent'
422
- }, buildMockUser(), {});
423
- expect(mockUploadService.deleteMultipleFile).toHaveBeenCalledTimes(2);
424
- expect(mockUploadService.deleteMultipleFile).toHaveBeenCalledWith([
425
- 'a.jpg'
426
- ], 'cfg-1', buildMockUser(), FileLocationEnum.AWS);
427
- expect(mockUploadService.deleteMultipleFile).toHaveBeenCalledWith([
428
- 'b.jpg'
429
- ], 'cfg-2', buildMockUser(), FileLocationEnum.AZURE);
430
- });
431
- it('prefixes local keys with the resolved storage config basePath when not already nested', async ()=>{
432
- const file = buildFile({
433
- id: 'a',
434
- key: 'plain-key.jpg',
435
- storageConfigId: 'cfg-1',
436
- location: FileLocationEnum.LOCAL
437
- });
438
- mockFileRepo.findBy.mockResolvedValue([
439
- file
440
- ]);
441
- mockStorageConfigRepo.findOne.mockResolvedValue({
442
- id: 'cfg-1',
443
- config: {
444
- basePath: './custom-uploads'
445
- }
446
- });
447
- await service.beforeDeleteOperation({
448
- id: [
449
- 'a'
450
- ],
451
- type: 'permanent'
452
- }, buildMockUser(), {});
453
- expect(mockUploadService.deleteMultipleFile).toHaveBeenCalledWith([
454
- 'custom-uploads/plain-key.jpg'
455
- ], 'cfg-1', buildMockUser(), FileLocationEnum.LOCAL);
456
- });
457
- });
458
- describe('getFiles', ()=>{
459
- it('throws BadRequestException when no valid file ids are provided', async ()=>{
460
- await expect(service.getFiles([], 'http', 'localhost:2002')).rejects.toThrow();
461
- });
462
- it('throws BadRequestException when all provided ids are falsy', async ()=>{
463
- await expect(service.getFiles([
464
- {
465
- id: ''
466
- },
467
- {
468
- id: undefined
469
- }
470
- ], 'http', 'localhost:2002')).rejects.toThrow();
471
- });
472
- it('returns file metadata for the requested ids', async ()=>{
473
- const file = buildFile({
474
- id: 'file-1',
475
- url: 'http://localhost:2002/storage/upload/file/uploads/photo.jpg'
476
- });
477
- mockFileRepo.findBy.mockResolvedValue([
478
- file
479
- ]);
480
- const result = await service.getFiles([
481
- {
482
- id: 'file-1'
483
- }
484
- ], 'http', 'localhost:2002');
485
- expect(result).toHaveLength(1);
486
- expect(result[0].id).toBe('file-1');
487
- expect(result[0].name).toBe('photo.jpg');
488
- });
489
- it('refreshes and persists file URLs that changed during the request', async ()=>{
490
- const file = buildFile({
491
- id: 'file-1',
492
- url: null,
493
- storageConfigId: 'cfg-1'
494
- });
495
- mockFileRepo.findBy.mockResolvedValue([
496
- file
497
- ]);
498
- mockUploadService.makeFileUrl.mockResolvedValue('https://cdn.example.com/photo.jpg');
499
- await service.getFiles([
500
- {
501
- id: 'file-1'
502
- }
503
- ], 'http', 'localhost:2002');
504
- expect(mockFileRepo.save).toHaveBeenCalled();
505
- });
506
- it('invalidates cached file responses for the files whose URL was refreshed', async ()=>{
507
- const file = buildFile({
508
- id: 'file-1',
509
- url: null,
510
- storageConfigId: 'cfg-1'
511
- });
512
- mockFileRepo.findBy.mockResolvedValue([
513
- file
514
- ]);
515
- mockUploadService.makeFileUrl.mockResolvedValue('https://cdn.example.com/photo.jpg');
516
- await service.getFiles([
517
- {
518
- id: 'file-1'
519
- }
520
- ], 'http', 'localhost:2002');
521
- expect(mockUtilsService.clearCache).toHaveBeenCalledWith('file_manager', expect.anything());
522
- expect(mockUtilsService.clearCache).toHaveBeenCalledWith('file_manager', expect.anything(), 'file-1');
523
- });
524
- it('does not persist when no file URL needed refreshing', async ()=>{
525
- const file = buildFile({
526
- id: 'file-1',
527
- url: 'http://localhost:2002/storage/upload/file/uploads/photo.jpg',
528
- location: FileLocationEnum.LOCAL,
529
- storageConfigId: null
530
- });
531
- mockFileRepo.findBy.mockResolvedValue([
532
- file
533
- ]);
534
- await service.getFiles([
535
- {
536
- id: 'file-1'
537
- }
538
- ], 'http', 'localhost:2002');
539
- expect(mockFileRepo.save).not.toHaveBeenCalled();
540
- });
541
- it('silently continues if persisting refreshed URLs fails (non-critical)', async ()=>{
542
- const file = buildFile({
543
- id: 'file-1',
544
- url: null,
545
- storageConfigId: 'cfg-1'
546
- });
547
- mockFileRepo.findBy.mockResolvedValue([
548
- file
549
- ]);
550
- mockFileRepo.save.mockRejectedValue(new Error('db write failed'));
551
- await expect(service.getFiles([
552
- {
553
- id: 'file-1'
554
- }
555
- ], 'http', 'localhost:2002')).resolves.toBeDefined();
556
- });
557
- it('falls back to the direct file-serve URL when makeFileUrl throws (e.g. missing storage config)', async ()=>{
558
- const file = buildFile({
559
- id: 'file-1',
560
- url: null,
561
- storageConfigId: 'cfg-missing',
562
- location: FileLocationEnum.AWS
563
- });
564
- mockFileRepo.findBy.mockResolvedValue([
565
- file
566
- ]);
567
- mockUploadService.makeFileUrl.mockRejectedValue(new Error('config not found'));
568
- const result = await service.getFiles([
569
- {
570
- id: 'file-1'
571
- }
572
- ], 'http', 'localhost:2002');
573
- expect(result[0].url).toContain('/storage/upload/file/');
574
- });
575
- });
576
- });
@@ -1,91 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
29
- import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
31
- import { Inject, Injectable, Scope } from '@nestjs/common';
32
- import { STORAGE_EVENT_ENTITIES, STORAGE_EVENT_MODULE } from '../config';
33
- import { Folder, FolderWithCompany } from '../entities';
34
- import { StorageConfigService } from './storage-config.service';
35
- import { StorageDataSourceProvider } from './storage-datasource.provider';
36
- export class FolderService extends ApiService {
37
- // ─── Override Methods ───────────────────────────────────────────────────────
38
- async convertSingleDtoToEntity(dto, user) {
39
- const entity = await super.convertSingleDtoToEntity(dto, user);
40
- if (this.storageConfig.isCompanyFeatureEnabled()) {
41
- entity.companyId = user?.companyId ?? null;
42
- }
43
- return entity;
44
- }
45
- async getSelectQuery(query, _user, select) {
46
- if (!select?.length) {
47
- select = [
48
- 'id',
49
- 'name',
50
- 'slug',
51
- 'createdAt',
52
- 'deletedAt'
53
- ];
54
- if (this.storageConfig.isCompanyFeatureEnabled()) {
55
- select.push('companyId');
56
- }
57
- }
58
- query.select(select.map((f)=>`${this.entityName}.${f}`));
59
- return {
60
- query,
61
- isRaw: false
62
- };
63
- }
64
- async getExtraManipulateQuery(query, filterDto, user) {
65
- const result = await super.getExtraManipulateQuery(query, filterDto, user);
66
- applyCompanyFilter(query, {
67
- isCompanyFeatureEnabled: this.storageConfig.isCompanyFeatureEnabled(),
68
- entityAlias: 'folder'
69
- }, user);
70
- return result;
71
- }
72
- constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
73
- super(STORAGE_EVENT_ENTITIES.FOLDER, cacheManager, utilsService, FolderService.name, true, STORAGE_EVENT_MODULE, dataSourceProvider, storageConfig.isCompanyFeatureEnabled() ? FolderWithCompany : Folder), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "storageConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.storageConfig = storageConfig;
74
- }
75
- }
76
- FolderService = _ts_decorate([
77
- Injectable({
78
- scope: Scope.REQUEST
79
- }),
80
- _ts_param(0, Inject('CACHE_INSTANCE')),
81
- _ts_param(1, Inject(UtilsService)),
82
- _ts_param(2, Inject(StorageConfigService)),
83
- _ts_param(3, Inject(StorageDataSourceProvider)),
84
- _ts_metadata("design:type", Function),
85
- _ts_metadata("design:paramtypes", [
86
- typeof HybridCache === "undefined" ? Object : HybridCache,
87
- typeof UtilsService === "undefined" ? Object : UtilsService,
88
- typeof StorageConfigService === "undefined" ? Object : StorageConfigService,
89
- typeof StorageDataSourceProvider === "undefined" ? Object : StorageDataSourceProvider
90
- ])
91
- ], FolderService);