@dismissible/nestjs-dismissible 0.0.2-canary.c91edbc.0 → 0.0.2-canary.d2f56d7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -67
- package/package.json +3 -3
- package/src/api/dismissible-item-response.dto.ts +0 -8
- package/src/api/dismissible-item.mapper.spec.ts +0 -12
- package/src/api/dismissible-item.mapper.ts +2 -8
- package/src/api/index.ts +3 -0
- package/src/api/use-cases/dismiss/dismiss.controller.spec.ts +1 -2
- package/src/api/use-cases/dismiss/dismiss.controller.ts +8 -8
- package/src/api/use-cases/get-or-create/get-or-create.controller.spec.ts +2 -42
- package/src/api/use-cases/get-or-create/get-or-create.controller.ts +10 -56
- package/src/api/use-cases/get-or-create/index.ts +0 -1
- package/src/api/use-cases/restore/restore.controller.spec.ts +1 -2
- package/src/api/use-cases/restore/restore.controller.ts +8 -8
- package/src/api/validation/index.ts +2 -0
- package/src/api/validation/param-validation.pipe.spec.ts +317 -0
- package/src/api/validation/param-validation.pipe.ts +42 -0
- package/src/api/validation/param.decorators.ts +32 -0
- package/src/core/dismissible-core.service.spec.ts +3 -45
- package/src/core/dismissible-core.service.ts +10 -27
- package/src/core/dismissible.service.spec.ts +23 -16
- package/src/core/dismissible.service.ts +28 -11
- package/src/core/hook-runner.service.spec.ts +369 -19
- package/src/core/hook-runner.service.ts +17 -17
- package/src/core/index.ts +0 -1
- package/src/core/lifecycle-hook.interface.ts +8 -8
- package/src/core/service-responses.interface.ts +9 -9
- package/src/dismissible.module.integration.spec.ts +685 -0
- package/src/dismissible.module.ts +6 -10
- package/src/events/dismissible.events.ts +16 -39
- package/src/index.ts +1 -0
- package/src/request/request-context.decorator.ts +1 -0
- package/src/request/request-context.interface.ts +6 -0
- package/src/response/http-exception-filter.spec.ts +213 -0
- package/src/testing/factories.ts +5 -8
- package/src/utils/dismissible.helper.ts +2 -2
- package/src/validation/dismissible-input.dto.ts +47 -0
- package/src/validation/index.ts +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.spec.json +12 -0
- package/src/api/use-cases/get-or-create/get-or-create.request.dto.ts +0 -17
- package/src/core/create-options.ts +0 -9
|
@@ -4,16 +4,17 @@ import { DismissibleService } from './dismissible.service';
|
|
|
4
4
|
import { DismissibleCoreService } from './dismissible-core.service';
|
|
5
5
|
import { HookRunner, IHookRunResult } from './hook-runner.service';
|
|
6
6
|
import { IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
7
|
+
import { ValidationService } from '@dismissible/nestjs-validation';
|
|
7
8
|
import { DismissibleEvents } from '../events';
|
|
8
|
-
import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
|
|
9
9
|
import { createTestItem, createTestContext } from '../testing/factories';
|
|
10
10
|
|
|
11
11
|
describe('DismissibleService', () => {
|
|
12
|
-
let service: DismissibleService
|
|
13
|
-
let mockCoreService: jest.Mocked<DismissibleCoreService
|
|
14
|
-
let mockHookRunner: jest.Mocked<HookRunner
|
|
12
|
+
let service: DismissibleService;
|
|
13
|
+
let mockCoreService: jest.Mocked<DismissibleCoreService>;
|
|
14
|
+
let mockHookRunner: jest.Mocked<HookRunner>;
|
|
15
15
|
let mockEventEmitter: jest.Mocked<EventEmitter2>;
|
|
16
16
|
let mockLogger: jest.Mocked<IDismissibleLogger>;
|
|
17
|
+
let mockValidationService: jest.Mocked<ValidationService>;
|
|
17
18
|
|
|
18
19
|
const testUserId = 'test-user-id';
|
|
19
20
|
|
|
@@ -29,8 +30,18 @@ describe('DismissibleService', () => {
|
|
|
29
30
|
mockHookRunner = mock(HookRunner, { failIfMockNotProvided: false });
|
|
30
31
|
mockEventEmitter = mock(EventEmitter2, { failIfMockNotProvided: false });
|
|
31
32
|
mockLogger = mock<IDismissibleLogger>({ failIfMockNotProvided: false });
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
mockValidationService = mock(ValidationService, { failIfMockNotProvided: false });
|
|
34
|
+
|
|
35
|
+
// Mock validateDto to resolve successfully by default
|
|
36
|
+
mockValidationService.validateDto.mockResolvedValue({} as never);
|
|
37
|
+
|
|
38
|
+
service = new DismissibleService(
|
|
39
|
+
mockCoreService,
|
|
40
|
+
mockHookRunner,
|
|
41
|
+
mockEventEmitter,
|
|
42
|
+
mockLogger,
|
|
43
|
+
mockValidationService,
|
|
44
|
+
);
|
|
34
45
|
});
|
|
35
46
|
|
|
36
47
|
describe('getOrCreate', () => {
|
|
@@ -42,7 +53,7 @@ describe('DismissibleService', () => {
|
|
|
42
53
|
mockHookRunner.runPreGet.mockResolvedValue(createHookResult('existing-item'));
|
|
43
54
|
mockCoreService.get.mockResolvedValue(item);
|
|
44
55
|
|
|
45
|
-
const result = await service.getOrCreate('existing-item', testUserId,
|
|
56
|
+
const result = await service.getOrCreate('existing-item', testUserId, context);
|
|
46
57
|
|
|
47
58
|
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
48
59
|
expect(mockCoreService.get).toHaveBeenCalledWith('existing-item', testUserId);
|
|
@@ -79,13 +90,13 @@ describe('DismissibleService', () => {
|
|
|
79
90
|
return item;
|
|
80
91
|
});
|
|
81
92
|
|
|
82
|
-
const result = await service.getOrCreate('new-item', testUserId,
|
|
93
|
+
const result = await service.getOrCreate('new-item', testUserId, context);
|
|
83
94
|
|
|
84
95
|
// Verify pre-create hooks run BEFORE create
|
|
85
96
|
expect(callOrder).toEqual(['runPreCreate', 'create']);
|
|
86
97
|
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
87
98
|
expect(mockHookRunner.runPreCreate).toHaveBeenCalled();
|
|
88
|
-
expect(mockCoreService.create).toHaveBeenCalledWith('new-item', testUserId
|
|
99
|
+
expect(mockCoreService.create).toHaveBeenCalledWith('new-item', testUserId);
|
|
89
100
|
expect(mockHookRunner.runPostCreate).toHaveBeenCalled();
|
|
90
101
|
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
91
102
|
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
@@ -108,9 +119,7 @@ describe('DismissibleService', () => {
|
|
|
108
119
|
context: createTestContext(),
|
|
109
120
|
});
|
|
110
121
|
|
|
111
|
-
await expect(
|
|
112
|
-
service.getOrCreate('new-item', testUserId, undefined, context),
|
|
113
|
-
).rejects.toThrow();
|
|
122
|
+
await expect(service.getOrCreate('new-item', testUserId, context)).rejects.toThrow();
|
|
114
123
|
|
|
115
124
|
// Verify create was NOT called because pre-create hook blocked it
|
|
116
125
|
expect(mockCoreService.create).not.toHaveBeenCalled();
|
|
@@ -135,9 +144,7 @@ describe('DismissibleService', () => {
|
|
|
135
144
|
context: createTestContext(),
|
|
136
145
|
});
|
|
137
146
|
|
|
138
|
-
await expect(
|
|
139
|
-
service.getOrCreate('existing-item', testUserId, undefined, context),
|
|
140
|
-
).rejects.toThrow();
|
|
147
|
+
await expect(service.getOrCreate('existing-item', testUserId, context)).rejects.toThrow();
|
|
141
148
|
|
|
142
149
|
// Verify post-get hooks were NOT called because pre-get hook blocked
|
|
143
150
|
expect(mockHookRunner.runPostGet).not.toHaveBeenCalled();
|
|
@@ -208,7 +215,7 @@ describe('DismissibleService', () => {
|
|
|
208
215
|
mockHookRunner.runPreGet.mockResolvedValue(createHookResult('test-item'));
|
|
209
216
|
mockCoreService.get.mockResolvedValue(item);
|
|
210
217
|
|
|
211
|
-
await service.getOrCreate('test-item', testUserId,
|
|
218
|
+
await service.getOrCreate('test-item', testUserId, context);
|
|
212
219
|
|
|
213
220
|
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
214
221
|
expect.stringContaining('getOrCreate called'),
|
|
@@ -8,8 +8,6 @@ import {
|
|
|
8
8
|
IDismissServiceResponse,
|
|
9
9
|
IRestoreServiceResponse,
|
|
10
10
|
} from './service-responses.interface';
|
|
11
|
-
import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
|
|
12
|
-
import { ICreateItemOptions } from './create-options';
|
|
13
11
|
import { IRequestContext } from '../request/request-context.interface';
|
|
14
12
|
import { DismissibleEvents } from '../events';
|
|
15
13
|
import {
|
|
@@ -18,36 +16,49 @@ import {
|
|
|
18
16
|
ItemDismissedEvent,
|
|
19
17
|
ItemRestoredEvent,
|
|
20
18
|
} from '../events';
|
|
19
|
+
import { ValidationService } from '@dismissible/nestjs-validation';
|
|
20
|
+
import { DismissibleInputDto } from '../validation';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Main orchestration service for dismissible operations.
|
|
24
24
|
* Coordinates core logic, hooks, and events.
|
|
25
25
|
*/
|
|
26
26
|
@Injectable()
|
|
27
|
-
export class DismissibleService
|
|
27
|
+
export class DismissibleService {
|
|
28
28
|
constructor(
|
|
29
|
-
private readonly coreService: DismissibleCoreService
|
|
30
|
-
private readonly hookRunner: HookRunner
|
|
29
|
+
private readonly coreService: DismissibleCoreService,
|
|
30
|
+
private readonly hookRunner: HookRunner,
|
|
31
31
|
private readonly eventEmitter: EventEmitter2,
|
|
32
32
|
@Inject(DISMISSIBLE_LOGGER)
|
|
33
33
|
private readonly logger: IDismissibleLogger,
|
|
34
|
+
private readonly validationService: ValidationService,
|
|
34
35
|
) {}
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Validates input parameters for all service methods.
|
|
39
|
+
* Provides defense in depth when the service is used directly without controllers.
|
|
40
|
+
*/
|
|
41
|
+
private async validateInput(itemId: string, userId: string): Promise<void> {
|
|
42
|
+
// Validate itemId and userId
|
|
43
|
+
await this.validationService.validateDto(DismissibleInputDto, { itemId, userId });
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
/**
|
|
37
47
|
* Get an existing item or create a new one.
|
|
38
48
|
* @param itemId The item identifier
|
|
39
49
|
* @param userId The user identifier (required)
|
|
40
|
-
* @param options Optional creation options (metadata)
|
|
41
50
|
* @param context Optional request context for tracing
|
|
42
51
|
*/
|
|
43
52
|
async getOrCreate(
|
|
44
53
|
itemId: string,
|
|
45
54
|
userId: string,
|
|
46
|
-
options?: ICreateItemOptions<TMetadata>,
|
|
47
55
|
context?: IRequestContext,
|
|
48
|
-
): Promise<IGetOrCreateServiceResponse
|
|
56
|
+
): Promise<IGetOrCreateServiceResponse> {
|
|
49
57
|
this.logger.debug(`getOrCreate called`, { itemId, userId });
|
|
50
58
|
|
|
59
|
+
// Validate input parameters
|
|
60
|
+
await this.validateInput(itemId, userId);
|
|
61
|
+
|
|
51
62
|
// Run global pre-request hooks (auth, rate limiting, validation)
|
|
52
63
|
const preResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
53
64
|
HookRunner.throwIfBlocked(preResult);
|
|
@@ -103,7 +114,7 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
103
114
|
HookRunner.throwIfBlocked(preCreateResult);
|
|
104
115
|
|
|
105
116
|
// Now create the item
|
|
106
|
-
const createdItem = await this.coreService.create(resolvedId, resolvedUserId
|
|
117
|
+
const createdItem = await this.coreService.create(resolvedId, resolvedUserId);
|
|
107
118
|
|
|
108
119
|
// Run post-create hooks
|
|
109
120
|
await this.hookRunner.runPostCreate(resolvedId, createdItem, resolvedUserId, resolvedContext);
|
|
@@ -135,9 +146,12 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
135
146
|
itemId: string,
|
|
136
147
|
userId: string,
|
|
137
148
|
context?: IRequestContext,
|
|
138
|
-
): Promise<IDismissServiceResponse
|
|
149
|
+
): Promise<IDismissServiceResponse> {
|
|
139
150
|
this.logger.debug(`dismiss called`, { itemId, userId });
|
|
140
151
|
|
|
152
|
+
// Validate input parameters
|
|
153
|
+
await this.validateInput(itemId, userId);
|
|
154
|
+
|
|
141
155
|
// Run global pre-request hooks (auth, rate limiting, validation)
|
|
142
156
|
const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
143
157
|
HookRunner.throwIfBlocked(preRequestResult);
|
|
@@ -190,9 +204,12 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
190
204
|
itemId: string,
|
|
191
205
|
userId: string,
|
|
192
206
|
context?: IRequestContext,
|
|
193
|
-
): Promise<IRestoreServiceResponse
|
|
207
|
+
): Promise<IRestoreServiceResponse> {
|
|
194
208
|
this.logger.debug(`restore called`, { itemId, userId });
|
|
195
209
|
|
|
210
|
+
// Validate input parameters
|
|
211
|
+
await this.validateInput(itemId, userId);
|
|
212
|
+
|
|
196
213
|
// Run global pre-request hooks (auth, rate limiting, validation)
|
|
197
214
|
const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
198
215
|
HookRunner.throwIfBlocked(preRequestResult);
|