@dismissible/nestjs-dismissible 0.0.2-canary.8976e84.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 +4 -4
- 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 +75 -29
- package/src/core/dismissible-core.service.ts +40 -24
- package/src/core/dismissible.service.spec.ts +111 -25
- package/src/core/dismissible.service.ts +115 -49
- package/src/core/hook-runner.service.spec.ts +486 -53
- package/src/core/hook-runner.service.ts +144 -18
- package/src/core/index.ts +0 -1
- package/src/core/lifecycle-hook.interface.ts +56 -10
- 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/response/http-exception-filter.ts +3 -3
- 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,27 +30,43 @@ 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', () => {
|
|
37
|
-
it('should run
|
|
48
|
+
it('should run request and get hooks for existing item', async () => {
|
|
38
49
|
const item = createTestItem({ id: 'existing-item' });
|
|
39
50
|
const context = createTestContext();
|
|
40
51
|
|
|
41
|
-
mockHookRunner.
|
|
42
|
-
|
|
52
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('existing-item'));
|
|
53
|
+
mockHookRunner.runPreGet.mockResolvedValue(createHookResult('existing-item'));
|
|
54
|
+
mockCoreService.get.mockResolvedValue(item);
|
|
43
55
|
|
|
44
|
-
const result = await service.getOrCreate('existing-item', testUserId,
|
|
56
|
+
const result = await service.getOrCreate('existing-item', testUserId, context);
|
|
45
57
|
|
|
46
|
-
expect(mockHookRunner.
|
|
47
|
-
expect(mockCoreService.
|
|
58
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
59
|
+
expect(mockCoreService.get).toHaveBeenCalledWith('existing-item', testUserId);
|
|
60
|
+
expect(mockHookRunner.runPreGet).toHaveBeenCalledWith(
|
|
48
61
|
'existing-item',
|
|
62
|
+
item,
|
|
49
63
|
testUserId,
|
|
50
|
-
|
|
64
|
+
expect.anything(),
|
|
51
65
|
);
|
|
52
|
-
expect(mockHookRunner.
|
|
66
|
+
expect(mockHookRunner.runPostGet).toHaveBeenCalled();
|
|
67
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
68
|
+
expect(mockCoreService.create).not.toHaveBeenCalled();
|
|
69
|
+
expect(mockHookRunner.runPreCreate).not.toHaveBeenCalled();
|
|
53
70
|
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
54
71
|
DismissibleEvents.ITEM_RETRIEVED,
|
|
55
72
|
expect.anything(),
|
|
@@ -57,24 +74,86 @@ describe('DismissibleService', () => {
|
|
|
57
74
|
expect(result.created).toBe(false);
|
|
58
75
|
});
|
|
59
76
|
|
|
60
|
-
it('should run create hooks
|
|
77
|
+
it('should run pre-create hooks BEFORE creating for new item', async () => {
|
|
61
78
|
const item = createTestItem({ id: 'new-item' });
|
|
62
79
|
const context = createTestContext();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
mockHookRunner.
|
|
66
|
-
mockCoreService.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
const callOrder: string[] = [];
|
|
81
|
+
|
|
82
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('new-item'));
|
|
83
|
+
mockCoreService.get.mockResolvedValue(null);
|
|
84
|
+
mockHookRunner.runPreCreate.mockImplementation(async () => {
|
|
85
|
+
callOrder.push('runPreCreate');
|
|
86
|
+
return createHookResult('new-item');
|
|
87
|
+
});
|
|
88
|
+
mockCoreService.create.mockImplementation(async () => {
|
|
89
|
+
callOrder.push('create');
|
|
90
|
+
return item;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const result = await service.getOrCreate('new-item', testUserId, context);
|
|
94
|
+
|
|
95
|
+
// Verify pre-create hooks run BEFORE create
|
|
96
|
+
expect(callOrder).toEqual(['runPreCreate', 'create']);
|
|
97
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
70
98
|
expect(mockHookRunner.runPreCreate).toHaveBeenCalled();
|
|
99
|
+
expect(mockCoreService.create).toHaveBeenCalledWith('new-item', testUserId);
|
|
71
100
|
expect(mockHookRunner.runPostCreate).toHaveBeenCalled();
|
|
101
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
72
102
|
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
73
103
|
DismissibleEvents.ITEM_CREATED,
|
|
74
104
|
expect.anything(),
|
|
75
105
|
);
|
|
76
106
|
expect(result.created).toBe(true);
|
|
77
107
|
});
|
|
108
|
+
|
|
109
|
+
it('should NOT create item when pre-create hook blocks the operation', async () => {
|
|
110
|
+
const context = createTestContext();
|
|
111
|
+
|
|
112
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('new-item'));
|
|
113
|
+
mockCoreService.get.mockResolvedValue(null);
|
|
114
|
+
mockHookRunner.runPreCreate.mockResolvedValue({
|
|
115
|
+
proceed: false,
|
|
116
|
+
reason: 'Plan limit reached',
|
|
117
|
+
id: 'new-item',
|
|
118
|
+
userId: testUserId,
|
|
119
|
+
context: createTestContext(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await expect(service.getOrCreate('new-item', testUserId, context)).rejects.toThrow();
|
|
123
|
+
|
|
124
|
+
// Verify create was NOT called because pre-create hook blocked it
|
|
125
|
+
expect(mockCoreService.create).not.toHaveBeenCalled();
|
|
126
|
+
expect(mockHookRunner.runPostCreate).not.toHaveBeenCalled();
|
|
127
|
+
expect(mockEventEmitter.emit).not.toHaveBeenCalledWith(
|
|
128
|
+
DismissibleEvents.ITEM_CREATED,
|
|
129
|
+
expect.anything(),
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should NOT return existing item when pre-get hook blocks the operation', async () => {
|
|
134
|
+
const item = createTestItem({ id: 'existing-item' });
|
|
135
|
+
const context = createTestContext();
|
|
136
|
+
|
|
137
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('existing-item'));
|
|
138
|
+
mockCoreService.get.mockResolvedValue(item);
|
|
139
|
+
mockHookRunner.runPreGet.mockResolvedValue({
|
|
140
|
+
proceed: false,
|
|
141
|
+
reason: 'Item access denied',
|
|
142
|
+
id: 'existing-item',
|
|
143
|
+
userId: testUserId,
|
|
144
|
+
context: createTestContext(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await expect(service.getOrCreate('existing-item', testUserId, context)).rejects.toThrow();
|
|
148
|
+
|
|
149
|
+
// Verify post-get hooks were NOT called because pre-get hook blocked
|
|
150
|
+
expect(mockHookRunner.runPostGet).not.toHaveBeenCalled();
|
|
151
|
+
expect(mockHookRunner.runPostRequest).not.toHaveBeenCalled();
|
|
152
|
+
expect(mockEventEmitter.emit).not.toHaveBeenCalledWith(
|
|
153
|
+
DismissibleEvents.ITEM_RETRIEVED,
|
|
154
|
+
expect.anything(),
|
|
155
|
+
);
|
|
156
|
+
});
|
|
78
157
|
});
|
|
79
158
|
|
|
80
159
|
describe('dismiss', () => {
|
|
@@ -83,14 +162,17 @@ describe('DismissibleService', () => {
|
|
|
83
162
|
const previousItem = createTestItem({ id: 'test-item' });
|
|
84
163
|
const context = createTestContext();
|
|
85
164
|
|
|
165
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('test-item'));
|
|
86
166
|
mockHookRunner.runPreDismiss.mockResolvedValue(createHookResult('test-item'));
|
|
87
167
|
mockCoreService.dismiss.mockResolvedValue({ item, previousItem });
|
|
88
168
|
|
|
89
169
|
const result = await service.dismiss('test-item', testUserId, context);
|
|
90
170
|
|
|
171
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
91
172
|
expect(mockHookRunner.runPreDismiss).toHaveBeenCalled();
|
|
92
173
|
expect(mockCoreService.dismiss).toHaveBeenCalledWith('test-item', testUserId);
|
|
93
174
|
expect(mockHookRunner.runPostDismiss).toHaveBeenCalled();
|
|
175
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
94
176
|
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
95
177
|
DismissibleEvents.ITEM_DISMISSED,
|
|
96
178
|
expect.anything(),
|
|
@@ -105,14 +187,17 @@ describe('DismissibleService', () => {
|
|
|
105
187
|
const previousItem = createTestItem({ id: 'test-item' });
|
|
106
188
|
const context = createTestContext();
|
|
107
189
|
|
|
190
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('test-item'));
|
|
108
191
|
mockHookRunner.runPreRestore.mockResolvedValue(createHookResult('test-item'));
|
|
109
192
|
mockCoreService.restore.mockResolvedValue({ item, previousItem });
|
|
110
193
|
|
|
111
194
|
const result = await service.restore('test-item', testUserId, context);
|
|
112
195
|
|
|
196
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
113
197
|
expect(mockHookRunner.runPreRestore).toHaveBeenCalled();
|
|
114
198
|
expect(mockCoreService.restore).toHaveBeenCalledWith('test-item', testUserId);
|
|
115
199
|
expect(mockHookRunner.runPostRestore).toHaveBeenCalled();
|
|
200
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
116
201
|
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
117
202
|
DismissibleEvents.ITEM_RESTORED,
|
|
118
203
|
expect.anything(),
|
|
@@ -126,10 +211,11 @@ describe('DismissibleService', () => {
|
|
|
126
211
|
const item = createTestItem({ id: 'test-item' });
|
|
127
212
|
const context = createTestContext();
|
|
128
213
|
|
|
129
|
-
mockHookRunner.
|
|
130
|
-
|
|
214
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('test-item'));
|
|
215
|
+
mockHookRunner.runPreGet.mockResolvedValue(createHookResult('test-item'));
|
|
216
|
+
mockCoreService.get.mockResolvedValue(item);
|
|
131
217
|
|
|
132
|
-
await service.getOrCreate('test-item', testUserId,
|
|
218
|
+
await service.getOrCreate('test-item', testUserId, context);
|
|
133
219
|
|
|
134
220
|
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
135
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,84 +16,124 @@ 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
|
|
|
51
|
-
//
|
|
52
|
-
|
|
59
|
+
// Validate input parameters
|
|
60
|
+
await this.validateInput(itemId, userId);
|
|
61
|
+
|
|
62
|
+
// Run global pre-request hooks (auth, rate limiting, validation)
|
|
63
|
+
const preResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
53
64
|
HookRunner.throwIfBlocked(preResult);
|
|
54
65
|
|
|
55
66
|
const resolvedId = preResult.id;
|
|
56
67
|
const resolvedUserId = preResult.userId;
|
|
57
68
|
const resolvedContext = preResult.context;
|
|
58
69
|
|
|
59
|
-
// Check if
|
|
60
|
-
const
|
|
70
|
+
// Check if item already exists
|
|
71
|
+
const existingItem = await this.coreService.get(resolvedId, resolvedUserId);
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const preCreateResult = await this.hookRunner.runPreCreate(
|
|
73
|
+
if (existingItem) {
|
|
74
|
+
// Item exists - run pre-get hooks (access control based on item state)
|
|
75
|
+
const preGetResult = await this.hookRunner.runPreGet(
|
|
66
76
|
resolvedId,
|
|
77
|
+
existingItem,
|
|
67
78
|
resolvedUserId,
|
|
68
79
|
resolvedContext,
|
|
69
80
|
);
|
|
70
|
-
HookRunner.throwIfBlocked(
|
|
71
|
-
|
|
72
|
-
// Run post-create hooks
|
|
73
|
-
await this.hookRunner.runPostCreate(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
81
|
+
HookRunner.throwIfBlocked(preGetResult);
|
|
74
82
|
|
|
75
|
-
// Emit
|
|
76
|
-
this.eventEmitter.emit(
|
|
77
|
-
DismissibleEvents.ITEM_CREATED,
|
|
78
|
-
new ItemCreatedEvent(resolvedId, result.item, resolvedUserId, resolvedContext),
|
|
79
|
-
);
|
|
80
|
-
} else {
|
|
81
|
-
// Emit retrieved event (async)
|
|
83
|
+
// Emit retrieved event
|
|
82
84
|
this.eventEmitter.emit(
|
|
83
85
|
DismissibleEvents.ITEM_RETRIEVED,
|
|
84
|
-
new ItemRetrievedEvent(resolvedId,
|
|
86
|
+
new ItemRetrievedEvent(resolvedId, existingItem, resolvedUserId, resolvedContext),
|
|
85
87
|
);
|
|
88
|
+
|
|
89
|
+
// Run post-get hooks
|
|
90
|
+
await this.hookRunner.runPostGet(resolvedId, existingItem, resolvedUserId, resolvedContext);
|
|
91
|
+
|
|
92
|
+
// Run global post-request hooks
|
|
93
|
+
await this.hookRunner.runPostRequest(
|
|
94
|
+
resolvedId,
|
|
95
|
+
existingItem,
|
|
96
|
+
resolvedUserId,
|
|
97
|
+
resolvedContext,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
this.logger.debug(`getOrCreate completed`, { itemId, created: false });
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
item: existingItem,
|
|
104
|
+
created: false,
|
|
105
|
+
};
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
//
|
|
89
|
-
await this.hookRunner.
|
|
108
|
+
// Item doesn't exist - run pre-create hooks BEFORE creating
|
|
109
|
+
const preCreateResult = await this.hookRunner.runPreCreate(
|
|
90
110
|
resolvedId,
|
|
91
|
-
result.item,
|
|
92
111
|
resolvedUserId,
|
|
93
112
|
resolvedContext,
|
|
94
113
|
);
|
|
114
|
+
HookRunner.throwIfBlocked(preCreateResult);
|
|
95
115
|
|
|
96
|
-
|
|
116
|
+
// Now create the item
|
|
117
|
+
const createdItem = await this.coreService.create(resolvedId, resolvedUserId);
|
|
97
118
|
|
|
98
|
-
|
|
119
|
+
// Run post-create hooks
|
|
120
|
+
await this.hookRunner.runPostCreate(resolvedId, createdItem, resolvedUserId, resolvedContext);
|
|
121
|
+
|
|
122
|
+
// Emit created event
|
|
123
|
+
this.eventEmitter.emit(
|
|
124
|
+
DismissibleEvents.ITEM_CREATED,
|
|
125
|
+
new ItemCreatedEvent(resolvedId, createdItem, resolvedUserId, resolvedContext),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Run global post-request hooks
|
|
129
|
+
await this.hookRunner.runPostRequest(resolvedId, createdItem, resolvedUserId, resolvedContext);
|
|
130
|
+
|
|
131
|
+
this.logger.debug(`getOrCreate completed`, { itemId, created: true });
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
item: createdItem,
|
|
135
|
+
created: true,
|
|
136
|
+
};
|
|
99
137
|
}
|
|
100
138
|
|
|
101
139
|
/**
|
|
@@ -108,16 +146,27 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
108
146
|
itemId: string,
|
|
109
147
|
userId: string,
|
|
110
148
|
context?: IRequestContext,
|
|
111
|
-
): Promise<IDismissServiceResponse
|
|
149
|
+
): Promise<IDismissServiceResponse> {
|
|
112
150
|
this.logger.debug(`dismiss called`, { itemId, userId });
|
|
113
151
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
HookRunner.throwIfBlocked(preResult);
|
|
152
|
+
// Validate input parameters
|
|
153
|
+
await this.validateInput(itemId, userId);
|
|
117
154
|
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
155
|
+
// Run global pre-request hooks (auth, rate limiting, validation)
|
|
156
|
+
const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
157
|
+
HookRunner.throwIfBlocked(preRequestResult);
|
|
158
|
+
|
|
159
|
+
const resolvedId = preRequestResult.id;
|
|
160
|
+
const resolvedUserId = preRequestResult.userId;
|
|
161
|
+
const resolvedContext = preRequestResult.context;
|
|
162
|
+
|
|
163
|
+
// Run pre-dismiss hooks
|
|
164
|
+
const preDismissResult = await this.hookRunner.runPreDismiss(
|
|
165
|
+
resolvedId,
|
|
166
|
+
resolvedUserId,
|
|
167
|
+
resolvedContext,
|
|
168
|
+
);
|
|
169
|
+
HookRunner.throwIfBlocked(preDismissResult);
|
|
121
170
|
|
|
122
171
|
// Execute core dismiss operation
|
|
123
172
|
const result = await this.coreService.dismiss(resolvedId, resolvedUserId);
|
|
@@ -125,7 +174,7 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
125
174
|
// Run post-dismiss hooks
|
|
126
175
|
await this.hookRunner.runPostDismiss(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
127
176
|
|
|
128
|
-
// Emit dismissed event
|
|
177
|
+
// Emit dismissed event
|
|
129
178
|
this.eventEmitter.emit(
|
|
130
179
|
DismissibleEvents.ITEM_DISMISSED,
|
|
131
180
|
new ItemDismissedEvent(
|
|
@@ -137,6 +186,9 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
137
186
|
),
|
|
138
187
|
);
|
|
139
188
|
|
|
189
|
+
// Run global post-request hooks
|
|
190
|
+
await this.hookRunner.runPostRequest(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
191
|
+
|
|
140
192
|
this.logger.debug(`dismiss completed`, { itemId });
|
|
141
193
|
|
|
142
194
|
return result;
|
|
@@ -152,16 +204,27 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
152
204
|
itemId: string,
|
|
153
205
|
userId: string,
|
|
154
206
|
context?: IRequestContext,
|
|
155
|
-
): Promise<IRestoreServiceResponse
|
|
207
|
+
): Promise<IRestoreServiceResponse> {
|
|
156
208
|
this.logger.debug(`restore called`, { itemId, userId });
|
|
157
209
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
HookRunner.throwIfBlocked(preResult);
|
|
210
|
+
// Validate input parameters
|
|
211
|
+
await this.validateInput(itemId, userId);
|
|
161
212
|
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
213
|
+
// Run global pre-request hooks (auth, rate limiting, validation)
|
|
214
|
+
const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
215
|
+
HookRunner.throwIfBlocked(preRequestResult);
|
|
216
|
+
|
|
217
|
+
const resolvedId = preRequestResult.id;
|
|
218
|
+
const resolvedUserId = preRequestResult.userId;
|
|
219
|
+
const resolvedContext = preRequestResult.context;
|
|
220
|
+
|
|
221
|
+
// Run pre-restore hooks
|
|
222
|
+
const preRestoreResult = await this.hookRunner.runPreRestore(
|
|
223
|
+
resolvedId,
|
|
224
|
+
resolvedUserId,
|
|
225
|
+
resolvedContext,
|
|
226
|
+
);
|
|
227
|
+
HookRunner.throwIfBlocked(preRestoreResult);
|
|
165
228
|
|
|
166
229
|
// Execute core restore operation
|
|
167
230
|
const result = await this.coreService.restore(resolvedId, resolvedUserId);
|
|
@@ -169,7 +232,7 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
169
232
|
// Run post-restore hooks
|
|
170
233
|
await this.hookRunner.runPostRestore(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
171
234
|
|
|
172
|
-
// Emit restored event
|
|
235
|
+
// Emit restored event
|
|
173
236
|
this.eventEmitter.emit(
|
|
174
237
|
DismissibleEvents.ITEM_RESTORED,
|
|
175
238
|
new ItemRestoredEvent(
|
|
@@ -181,6 +244,9 @@ export class DismissibleService<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
181
244
|
),
|
|
182
245
|
);
|
|
183
246
|
|
|
247
|
+
// Run global post-request hooks
|
|
248
|
+
await this.hookRunner.runPostRequest(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
249
|
+
|
|
184
250
|
this.logger.debug(`restore completed`, { itemId });
|
|
185
251
|
|
|
186
252
|
return result;
|