@flusys/nestjs-storage 6.4.1 → 7.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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Domain event actions published by the storage module, alongside the CRUD
3
+ * lifecycle actions every ApiService emits.
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ function _export(target, all) {
9
+ for(var name in all)Object.defineProperty(target, name, {
10
+ enumerable: true,
11
+ get: Object.getOwnPropertyDescriptor(all, name).get
12
+ });
13
+ }
14
+ _export(exports, {
15
+ get STORAGE_EVENT_ACTIONS () {
16
+ return STORAGE_EVENT_ACTIONS;
17
+ },
18
+ get STORAGE_EVENT_ENTITIES () {
19
+ return STORAGE_EVENT_ENTITIES;
20
+ },
21
+ get STORAGE_EVENT_MODULE () {
22
+ return STORAGE_EVENT_MODULE;
23
+ }
24
+ });
25
+ const STORAGE_EVENT_ACTIONS = {
26
+ UPLOADED: 'uploaded',
27
+ REMOVED: 'removed'
28
+ };
29
+ const STORAGE_EVENT_ENTITIES = {
30
+ FILE: 'file_manager',
31
+ FOLDER: 'folder',
32
+ STORAGE_CONFIG: 'storageConfig'
33
+ };
34
+ const STORAGE_EVENT_MODULE = 'storage';
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- _export_star(require("./storage.constants"), exports);
5
+ _export_star(require("./event-actions"), exports);
6
6
  _export_star(require("./message-keys"), exports);
7
+ _export_star(require("./storage.constants"), exports);
7
8
  function _export_star(from, to) {
8
9
  Object.keys(from).forEach(function(k) {
9
10
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
@@ -16,6 +16,9 @@ _export(exports, {
16
16
  get FOLDER_MESSAGES () {
17
17
  return FOLDER_MESSAGES;
18
18
  },
19
+ get STORAGE_CONFIG_MESSAGES () {
20
+ return STORAGE_CONFIG_MESSAGES;
21
+ },
19
22
  get UPLOAD_MESSAGES () {
20
23
  return UPLOAD_MESSAGES;
21
24
  }
@@ -25,6 +28,9 @@ const FILE_MESSAGES = {
25
28
  DELETE_SUCCESS: 'file.delete.success',
26
29
  NOT_FOUND: 'file.not.found'
27
30
  };
31
+ const STORAGE_CONFIG_MESSAGES = {
32
+ DEFAULT_DELETE_FORBIDDEN: 'storage.config.default.delete.forbidden'
33
+ };
28
34
  const FOLDER_MESSAGES = {
29
35
  NOT_FOUND: 'folder.not.found'
30
36
  };
@@ -121,6 +121,7 @@ let StorageModule = class StorageModule {
121
121
  useValue: options
122
122
  });
123
123
  }
124
+ providers.push((0, _modules.createModuleEventsProvider)('storage', _storageconstants.STORAGE_MODULE_OPTIONS));
124
125
  return providers;
125
126
  }
126
127
  static createAsyncProviders(options) {
@@ -12,6 +12,8 @@ const _classes = require("@flusys/nestjs-shared/classes");
12
12
  const _modules = require("@flusys/nestjs-shared/modules");
13
13
  const _utils = require("@flusys/nestjs-shared/utils");
14
14
  const _common = require("@nestjs/common");
15
+ const _typeorm = require("typeorm");
16
+ const _config = require("../config");
15
17
  const _entities = require("../entities");
16
18
  const _storageconfigservice = require("./storage-config.service");
17
19
  const _storagedatasourceprovider = require("./storage-datasource.provider");
@@ -46,6 +48,29 @@ let StorageProviderConfigService = class StorageProviderConfigService extends _c
46
48
  resolveEntity() {
47
49
  return this.storageConfig.isCompanyFeatureEnabled() ? _entities.StorageConfigWithCompany : _entities.StorageConfig;
48
50
  }
51
+ async beforeDeleteOperation(dto, _user, _queryRunner) {
52
+ const ids = Array.isArray(dto.id) ? dto.id : [
53
+ dto.id
54
+ ];
55
+ await this.ensureDataSourceRepository();
56
+ const defaultConfigs = await this.repository.find({
57
+ where: {
58
+ id: (0, _typeorm.In)(ids),
59
+ isDefault: true,
60
+ deletedAt: (0, _typeorm.IsNull)()
61
+ }
62
+ });
63
+ if (defaultConfigs.length > 0) {
64
+ const names = defaultConfigs.map((c)=>c.name).join(', ');
65
+ throw new _common.ForbiddenException({
66
+ message: `Cannot delete the default storage config(s): ${names}. Set another config as default first.`,
67
+ messageKey: _config.STORAGE_CONFIG_MESSAGES.DEFAULT_DELETE_FORBIDDEN,
68
+ messageVariables: {
69
+ names
70
+ }
71
+ });
72
+ }
73
+ }
49
74
  // ─── Override Methods ───────────────────────────────────────────────────────
50
75
  async convertSingleDtoToEntity(dto, user) {
51
76
  const entity = await super.convertSingleDtoToEntity(dto, user);
@@ -240,4 +240,22 @@ describe('StorageProviderConfigService', ()=>{
240
240
  expect(result).toEqual([]);
241
241
  });
242
242
  });
243
+ describe('beforeDeleteOperation', ()=>{
244
+ it('rejects deleting the default storage config', async ()=>{
245
+ mockRepo.find.mockResolvedValue([
246
+ buildStorageConfig({
247
+ isDefault: true
248
+ })
249
+ ]);
250
+ await expect(service.beforeDeleteOperation({
251
+ id: 'config-uuid-1'
252
+ }, null, {})).rejects.toThrow('Cannot delete the default storage config');
253
+ });
254
+ it('allows deleting a non-default storage config', async ()=>{
255
+ mockRepo.find.mockResolvedValue([]);
256
+ await expect(service.beforeDeleteOperation({
257
+ id: 'config-uuid-1'
258
+ }, null, {})).resolves.toBeUndefined();
259
+ });
260
+ });
243
261
  });
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "UploadService", {
9
9
  }
10
10
  });
11
11
  const _nestjsshared = require("@flusys/nestjs-shared");
12
+ const _classes = require("@flusys/nestjs-shared/classes");
12
13
  const _interfaces = require("@flusys/nestjs-shared/interfaces");
13
14
  const _utils = require("@flusys/nestjs-shared/utils");
14
15
  const _common = require("@nestjs/common");
@@ -172,6 +173,14 @@ let UploadService = class UploadService {
172
173
  this.validateFile(file);
173
174
  const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
174
175
  const result = await provider.uploadFile(file, options);
176
+ await this.publishFileEvent(_config.STORAGE_EVENT_ACTIONS.UPLOADED, [
177
+ result.key
178
+ ], user, {
179
+ location,
180
+ storageConfigId: configId,
181
+ totalSize: result.size,
182
+ count: 1
183
+ });
175
184
  return {
176
185
  ...result,
177
186
  location,
@@ -184,6 +193,12 @@ let UploadService = class UploadService {
184
193
  }
185
194
  const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
186
195
  const results = await provider.uploadMultipleFiles(files, options);
196
+ await this.publishFileEvent(_config.STORAGE_EVENT_ACTIONS.UPLOADED, results.map((result)=>result.key), user, {
197
+ location,
198
+ storageConfigId: configId,
199
+ totalSize: results.reduce((sum, result)=>sum + (result.size ?? 0), 0),
200
+ count: results.length
201
+ });
187
202
  return results.map((result)=>({
188
203
  ...result,
189
204
  location,
@@ -197,6 +212,12 @@ let UploadService = class UploadService {
197
212
  });
198
213
  const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
199
214
  await provider.deleteFile(key);
215
+ await this.publishFileEvent(_config.STORAGE_EVENT_ACTIONS.REMOVED, [
216
+ key
217
+ ], user, {
218
+ storageConfigId,
219
+ count: 1
220
+ });
200
221
  return true;
201
222
  }
202
223
  async deleteMultipleFile(keys, storageConfigId, user, locationHint) {
@@ -206,8 +227,22 @@ let UploadService = class UploadService {
206
227
  });
207
228
  const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
208
229
  await provider.deleteMultipleFiles(keys);
230
+ await this.publishFileEvent(_config.STORAGE_EVENT_ACTIONS.REMOVED, keys, user, {
231
+ storageConfigId,
232
+ count: keys.length
233
+ });
209
234
  return true;
210
235
  }
236
+ async publishFileEvent(action, keys, user, metadata) {
237
+ await (0, _classes.publishDomainEvent)({
238
+ module: _config.STORAGE_EVENT_MODULE,
239
+ entity: _config.STORAGE_EVENT_ENTITIES.FILE,
240
+ action,
241
+ ids: keys,
242
+ metadata,
243
+ user: user ?? null
244
+ });
245
+ }
211
246
  bytesToKb(bytes) {
212
247
  return Number((bytes * 0.001).toFixed(2));
213
248
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  const _testing = require("@nestjs/testing");
6
+ const _classes = require("@flusys/nestjs-shared/classes");
6
7
  const _uploadservice = require("./upload.service");
7
8
  const _storagefactoryservice = require("../providers/storage-factory.service");
8
9
  const _storageconfigservice = require("./storage-config.service");
@@ -369,4 +370,57 @@ describe('UploadService', ()=>{
369
370
  expect(mockProvider.generatePresignedUrl).toHaveBeenCalledWith('uploads/photo.jpg', 3600);
370
371
  });
371
372
  });
373
+ describe('domain events', ()=>{
374
+ let publish;
375
+ beforeEach(()=>{
376
+ publish = jest.fn().mockResolvedValue(undefined);
377
+ _classes.EventBusRegistry.reset();
378
+ _classes.EventBusRegistry.setBus({
379
+ publish,
380
+ subscribe: jest.fn(),
381
+ close: jest.fn()
382
+ });
383
+ _classes.EventBusRegistry.configureModule('storage', {
384
+ enabled: true
385
+ });
386
+ mockStorageProviderConfigService.getDefaultConfig.mockResolvedValue({
387
+ id: 'cfg-default',
388
+ storage: 'local',
389
+ config: {}
390
+ });
391
+ });
392
+ afterEach(()=>{
393
+ _classes.EventBusRegistry.reset();
394
+ });
395
+ it('publishes storage.file_manager.uploaded after a single upload', async ()=>{
396
+ await service.uploadSingleFile(buildMulterFile(), {}, (0, _loggedusermock.buildMockUser)());
397
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
398
+ name: 'storage.file_manager.uploaded',
399
+ payload: expect.objectContaining({
400
+ metadata: expect.objectContaining({
401
+ count: 1,
402
+ storageConfigId: 'cfg-default'
403
+ })
404
+ })
405
+ }));
406
+ });
407
+ it('publishes storage.file_manager.removed after a delete', async ()=>{
408
+ await service.deleteSingleFile('uploads/photo.jpg', 'cfg-1', (0, _loggedusermock.buildMockUser)());
409
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
410
+ name: 'storage.file_manager.removed',
411
+ payload: expect.objectContaining({
412
+ ids: [
413
+ 'uploads/photo.jpg'
414
+ ]
415
+ })
416
+ }));
417
+ });
418
+ it('does not publish when the storage module has events disabled', async ()=>{
419
+ _classes.EventBusRegistry.configureModule('storage', {
420
+ enabled: false
421
+ });
422
+ await service.uploadSingleFile(buildMulterFile(), {}, (0, _loggedusermock.buildMockUser)());
423
+ expect(publish).not.toHaveBeenCalled();
424
+ });
425
+ });
372
426
  });
@@ -0,0 +1,10 @@
1
+ export declare const STORAGE_EVENT_ACTIONS: {
2
+ readonly UPLOADED: "uploaded";
3
+ readonly REMOVED: "removed";
4
+ };
5
+ export declare const STORAGE_EVENT_ENTITIES: {
6
+ readonly FILE: "file_manager";
7
+ readonly FOLDER: "folder";
8
+ readonly STORAGE_CONFIG: "storageConfig";
9
+ };
10
+ export declare const STORAGE_EVENT_MODULE = "storage";
package/config/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export * from './storage.constants';
1
+ export * from './event-actions';
2
2
  export * from './message-keys';
3
+ export * from './storage.constants';
@@ -3,6 +3,9 @@ export declare const FILE_MESSAGES: {
3
3
  readonly DELETE_SUCCESS: "file.delete.success";
4
4
  readonly NOT_FOUND: "file.not.found";
5
5
  };
6
+ export declare const STORAGE_CONFIG_MESSAGES: {
7
+ readonly DEFAULT_DELETE_FORBIDDEN: "storage.config.default.delete.forbidden";
8
+ };
6
9
  export declare const FOLDER_MESSAGES: {
7
10
  readonly NOT_FOUND: "folder.not.found";
8
11
  };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Domain event actions published by the storage module, alongside the CRUD
3
+ * lifecycle actions every ApiService emits.
4
+ */ export const STORAGE_EVENT_ACTIONS = {
5
+ UPLOADED: 'uploaded',
6
+ REMOVED: 'removed'
7
+ };
8
+ /** Every entity the storage module publishes under, ApiService backed or not */ export const STORAGE_EVENT_ENTITIES = {
9
+ FILE: 'file_manager',
10
+ FOLDER: 'folder',
11
+ STORAGE_CONFIG: 'storageConfig'
12
+ };
13
+ export const STORAGE_EVENT_MODULE = 'storage';
@@ -1,2 +1,3 @@
1
- export * from './storage.constants';
1
+ export * from './event-actions';
2
2
  export * from './message-keys';
3
+ export * from './storage.constants';
@@ -4,6 +4,9 @@ export const FILE_MESSAGES = {
4
4
  DELETE_SUCCESS: 'file.delete.success',
5
5
  NOT_FOUND: 'file.not.found'
6
6
  };
7
+ export const STORAGE_CONFIG_MESSAGES = {
8
+ DEFAULT_DELETE_FORBIDDEN: 'storage.config.default.delete.forbidden'
9
+ };
7
10
  export const FOLDER_MESSAGES = {
8
11
  NOT_FOUND: 'folder.not.found'
9
12
  };
@@ -4,7 +4,7 @@ function _ts_decorate(decorators, target, key, desc) {
4
4
  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;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
- import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
7
+ import { CacheModule, UtilsModule, createModuleEventsProvider } from '@flusys/nestjs-shared/modules';
8
8
  import { Logger, Module, RequestMethod } from '@nestjs/common';
9
9
  import { FileServeMiddleware } from '../middlewares';
10
10
  import { StorageConfigService } from '../services';
@@ -112,6 +112,7 @@ export class StorageModule {
112
112
  useValue: options
113
113
  });
114
114
  }
115
+ providers.push(createModuleEventsProvider('storage', STORAGE_MODULE_OPTIONS));
115
116
  return providers;
116
117
  }
117
118
  static createAsyncProviders(options) {
@@ -28,7 +28,9 @@ function _ts_param(paramIndex, decorator) {
28
28
  import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
29
29
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
30
  import { applyCompanyFilter, buildCompanyWhereCondition } from '@flusys/nestjs-shared/utils';
31
- import { Inject, Injectable, Scope } from '@nestjs/common';
31
+ import { ForbiddenException, Inject, Injectable, Scope } from '@nestjs/common';
32
+ import { In, IsNull } from 'typeorm';
33
+ import { STORAGE_CONFIG_MESSAGES } from '../config';
32
34
  import { StorageConfig, StorageConfigWithCompany } from '../entities';
33
35
  import { StorageConfigService } from './storage-config.service';
34
36
  import { StorageDataSourceProvider } from './storage-datasource.provider';
@@ -36,6 +38,29 @@ export class StorageProviderConfigService extends ApiService {
36
38
  resolveEntity() {
37
39
  return this.storageConfig.isCompanyFeatureEnabled() ? StorageConfigWithCompany : StorageConfig;
38
40
  }
41
+ async beforeDeleteOperation(dto, _user, _queryRunner) {
42
+ const ids = Array.isArray(dto.id) ? dto.id : [
43
+ dto.id
44
+ ];
45
+ await this.ensureDataSourceRepository();
46
+ const defaultConfigs = await this.repository.find({
47
+ where: {
48
+ id: In(ids),
49
+ isDefault: true,
50
+ deletedAt: IsNull()
51
+ }
52
+ });
53
+ if (defaultConfigs.length > 0) {
54
+ const names = defaultConfigs.map((c)=>c.name).join(', ');
55
+ throw new ForbiddenException({
56
+ message: `Cannot delete the default storage config(s): ${names}. Set another config as default first.`,
57
+ messageKey: STORAGE_CONFIG_MESSAGES.DEFAULT_DELETE_FORBIDDEN,
58
+ messageVariables: {
59
+ names
60
+ }
61
+ });
62
+ }
63
+ }
39
64
  // ─── Override Methods ───────────────────────────────────────────────────────
40
65
  async convertSingleDtoToEntity(dto, user) {
41
66
  const entity = await super.convertSingleDtoToEntity(dto, user);
@@ -236,4 +236,22 @@ describe('StorageProviderConfigService', ()=>{
236
236
  expect(result).toEqual([]);
237
237
  });
238
238
  });
239
+ describe('beforeDeleteOperation', ()=>{
240
+ it('rejects deleting the default storage config', async ()=>{
241
+ mockRepo.find.mockResolvedValue([
242
+ buildStorageConfig({
243
+ isDefault: true
244
+ })
245
+ ]);
246
+ await expect(service.beforeDeleteOperation({
247
+ id: 'config-uuid-1'
248
+ }, null, {})).rejects.toThrow('Cannot delete the default storage config');
249
+ });
250
+ it('allows deleting a non-default storage config', async ()=>{
251
+ mockRepo.find.mockResolvedValue([]);
252
+ await expect(service.beforeDeleteOperation({
253
+ id: 'config-uuid-1'
254
+ }, null, {})).resolves.toBeUndefined();
255
+ });
256
+ });
239
257
  });
@@ -26,10 +26,11 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { LogAction } from '@flusys/nestjs-shared';
29
+ import { publishDomainEvent } from '@flusys/nestjs-shared/classes';
29
30
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
30
31
  import { validateCompanyOwnership } from '@flusys/nestjs-shared/utils';
31
32
  import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
32
- import { UPLOAD_MESSAGES } from '../config';
33
+ import { STORAGE_EVENT_ACTIONS, STORAGE_EVENT_ENTITIES, STORAGE_EVENT_MODULE, UPLOAD_MESSAGES } from '../config';
33
34
  import { UploadOptionsDto } from '../dtos/upload.dto';
34
35
  import { FileLocationEnum } from '../enums/file-location.enum';
35
36
  import { StorageFactoryService } from '../providers/storage-factory.service';
@@ -162,6 +163,14 @@ export class UploadService {
162
163
  this.validateFile(file);
163
164
  const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
164
165
  const result = await provider.uploadFile(file, options);
166
+ await this.publishFileEvent(STORAGE_EVENT_ACTIONS.UPLOADED, [
167
+ result.key
168
+ ], user, {
169
+ location,
170
+ storageConfigId: configId,
171
+ totalSize: result.size,
172
+ count: 1
173
+ });
165
174
  return {
166
175
  ...result,
167
176
  location,
@@ -174,6 +183,12 @@ export class UploadService {
174
183
  }
175
184
  const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
176
185
  const results = await provider.uploadMultipleFiles(files, options);
186
+ await this.publishFileEvent(STORAGE_EVENT_ACTIONS.UPLOADED, results.map((result)=>result.key), user, {
187
+ location,
188
+ storageConfigId: configId,
189
+ totalSize: results.reduce((sum, result)=>sum + (result.size ?? 0), 0),
190
+ count: results.length
191
+ });
177
192
  return results.map((result)=>({
178
193
  ...result,
179
194
  location,
@@ -187,6 +202,12 @@ export class UploadService {
187
202
  });
188
203
  const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
189
204
  await provider.deleteFile(key);
205
+ await this.publishFileEvent(STORAGE_EVENT_ACTIONS.REMOVED, [
206
+ key
207
+ ], user, {
208
+ storageConfigId,
209
+ count: 1
210
+ });
190
211
  return true;
191
212
  }
192
213
  async deleteMultipleFile(keys, storageConfigId, user, locationHint) {
@@ -196,8 +217,22 @@ export class UploadService {
196
217
  });
197
218
  const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
198
219
  await provider.deleteMultipleFiles(keys);
220
+ await this.publishFileEvent(STORAGE_EVENT_ACTIONS.REMOVED, keys, user, {
221
+ storageConfigId,
222
+ count: keys.length
223
+ });
199
224
  return true;
200
225
  }
226
+ async publishFileEvent(action, keys, user, metadata) {
227
+ await publishDomainEvent({
228
+ module: STORAGE_EVENT_MODULE,
229
+ entity: STORAGE_EVENT_ENTITIES.FILE,
230
+ action,
231
+ ids: keys,
232
+ metadata,
233
+ user: user ?? null
234
+ });
235
+ }
201
236
  bytesToKb(bytes) {
202
237
  return Number((bytes * 0.001).toFixed(2));
203
238
  }
@@ -1,4 +1,5 @@
1
1
  import { Test } from '@nestjs/testing';
2
+ import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
2
3
  import { UploadService } from './upload.service';
3
4
  import { StorageFactoryService } from '../providers/storage-factory.service';
4
5
  import { StorageConfigService } from './storage-config.service';
@@ -365,4 +366,57 @@ describe('UploadService', ()=>{
365
366
  expect(mockProvider.generatePresignedUrl).toHaveBeenCalledWith('uploads/photo.jpg', 3600);
366
367
  });
367
368
  });
369
+ describe('domain events', ()=>{
370
+ let publish;
371
+ beforeEach(()=>{
372
+ publish = jest.fn().mockResolvedValue(undefined);
373
+ EventBusRegistry.reset();
374
+ EventBusRegistry.setBus({
375
+ publish,
376
+ subscribe: jest.fn(),
377
+ close: jest.fn()
378
+ });
379
+ EventBusRegistry.configureModule('storage', {
380
+ enabled: true
381
+ });
382
+ mockStorageProviderConfigService.getDefaultConfig.mockResolvedValue({
383
+ id: 'cfg-default',
384
+ storage: 'local',
385
+ config: {}
386
+ });
387
+ });
388
+ afterEach(()=>{
389
+ EventBusRegistry.reset();
390
+ });
391
+ it('publishes storage.file_manager.uploaded after a single upload', async ()=>{
392
+ await service.uploadSingleFile(buildMulterFile(), {}, buildMockUser());
393
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
394
+ name: 'storage.file_manager.uploaded',
395
+ payload: expect.objectContaining({
396
+ metadata: expect.objectContaining({
397
+ count: 1,
398
+ storageConfigId: 'cfg-default'
399
+ })
400
+ })
401
+ }));
402
+ });
403
+ it('publishes storage.file_manager.removed after a delete', async ()=>{
404
+ await service.deleteSingleFile('uploads/photo.jpg', 'cfg-1', buildMockUser());
405
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
406
+ name: 'storage.file_manager.removed',
407
+ payload: expect.objectContaining({
408
+ ids: [
409
+ 'uploads/photo.jpg'
410
+ ]
411
+ })
412
+ }));
413
+ });
414
+ it('does not publish when the storage module has events disabled', async ()=>{
415
+ EventBusRegistry.configureModule('storage', {
416
+ enabled: false
417
+ });
418
+ await service.uploadSingleFile(buildMulterFile(), {}, buildMockUser());
419
+ expect(publish).not.toHaveBeenCalled();
420
+ });
421
+ });
368
422
  });
@@ -1,4 +1,5 @@
1
1
  import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
2
+ import { IModuleEventsConfig } from '@flusys/nestjs-shared/interfaces';
2
3
  import { ModuleMetadata, Type } from '@nestjs/common';
3
4
  export interface IStorageModuleConfig extends IDataSourceServiceOptions {
4
5
  maxFileSize?: number;
@@ -6,6 +7,7 @@ export interface IStorageModuleConfig extends IDataSourceServiceOptions {
6
7
  defaultStorageProvider?: string;
7
8
  localStoragePath?: string;
8
9
  appUrl?: string;
10
+ events?: IModuleEventsConfig;
9
11
  }
10
12
  export interface StorageModuleOptions extends IDynamicModuleConfig {
11
13
  bootstrapAppConfig?: IBootstrapAppConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-storage",
3
- "version": "6.4.1",
3
+ "version": "7.0.0-beta.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": "6.4.1",
133
- "@flusys/nestjs-shared": "6.4.1"
132
+ "@flusys/nestjs-core": "7.0.0-beta.1",
133
+ "@flusys/nestjs-shared": "7.0.0-beta.1"
134
134
  }
135
135
  }
@@ -1,7 +1,8 @@
1
1
  import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
2
+ import { DeleteDto } from '@flusys/nestjs-shared/dtos';
2
3
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
3
4
  import { UtilsService } from '@flusys/nestjs-shared/modules';
4
- import { EntityTarget, SelectQueryBuilder } from 'typeorm';
5
+ import { EntityTarget, QueryRunner, SelectQueryBuilder } from 'typeorm';
5
6
  import { CreateStorageConfigDto, UpdateStorageConfigDto } from '../dtos';
6
7
  import { StorageConfig, StorageConfigBase } from '../entities';
7
8
  import { IStorageConfig } from '../interfaces';
@@ -13,6 +14,7 @@ export declare class StorageProviderConfigService extends ApiService<CreateStora
13
14
  private readonly storageConfig;
14
15
  constructor(cacheManager: HybridCache, utilsService: UtilsService, storageConfig: StorageConfigService, dataSourceProvider: StorageDataSourceProvider);
15
16
  protected resolveEntity(): EntityTarget<StorageConfigBase>;
17
+ protected beforeDeleteOperation(dto: DeleteDto, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
16
18
  convertSingleDtoToEntity(dto: CreateStorageConfigDto | UpdateStorageConfigDto, user: ILoggedUserInfo | null): Promise<StorageConfigBase>;
17
19
  getSelectQuery(query: SelectQueryBuilder<StorageConfigBase>, _user: ILoggedUserInfo | null, select?: string[]): Promise<{
18
20
  query: SelectQueryBuilder<StorageConfig>;
@@ -18,6 +18,7 @@ export declare class UploadService {
18
18
  uploadMultipleFiles(files: Express.Multer.File[], options: UploadOptionsDto, user?: ILoggedUserInfo): Promise<IUploadedFileInfo[]>;
19
19
  deleteSingleFile(key: string, storageConfigId?: string, user?: ILoggedUserInfo, locationHint?: string): Promise<boolean>;
20
20
  deleteMultipleFile(keys: string[], storageConfigId?: string, user?: ILoggedUserInfo, locationHint?: string): Promise<boolean>;
21
+ private publishFileEvent;
21
22
  bytesToKb(bytes: number): number;
22
23
  getLocalStorageBasePath(): Promise<string | null>;
23
24
  makeFileUrl(key: string, storageConfigId: string, expiresIn?: number, user?: ILoggedUserInfo): Promise<string>;