@dismissible/nestjs-dismissible 0.0.2-canary.6b97aa7.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 +490 -0
- package/jest.config.ts +29 -0
- package/package.json +66 -0
- package/project.json +42 -0
- package/src/api/dismissible-item-response.dto.ts +30 -0
- package/src/api/dismissible-item.mapper.spec.ts +51 -0
- package/src/api/dismissible-item.mapper.ts +27 -0
- package/src/api/index.ts +7 -0
- package/src/api/use-cases/api-tags.constants.ts +4 -0
- package/src/api/use-cases/dismiss/dismiss.controller.spec.ts +41 -0
- package/src/api/use-cases/dismiss/dismiss.controller.ts +63 -0
- package/src/api/use-cases/dismiss/dismiss.response.dto.ts +7 -0
- package/src/api/use-cases/dismiss/index.ts +2 -0
- package/src/api/use-cases/get-or-create/get-or-create.controller.spec.ts +36 -0
- package/src/api/use-cases/get-or-create/get-or-create.controller.ts +60 -0
- package/src/api/use-cases/get-or-create/get-or-create.response.dto.ts +7 -0
- package/src/api/use-cases/get-or-create/index.ts +2 -0
- package/src/api/use-cases/index.ts +3 -0
- package/src/api/use-cases/restore/index.ts +2 -0
- package/src/api/use-cases/restore/restore.controller.spec.ts +41 -0
- package/src/api/use-cases/restore/restore.controller.ts +63 -0
- package/src/api/use-cases/restore/restore.response.dto.ts +7 -0
- package/src/api/validation/index.ts +2 -0
- package/src/api/validation/param-validation.pipe.spec.ts +313 -0
- package/src/api/validation/param-validation.pipe.ts +38 -0
- package/src/api/validation/param.decorators.ts +32 -0
- package/src/core/dismissible-core.service.spec.ts +403 -0
- package/src/core/dismissible-core.service.ts +173 -0
- package/src/core/dismissible.service.spec.ts +226 -0
- package/src/core/dismissible.service.ts +227 -0
- package/src/core/hook-runner.service.spec.ts +736 -0
- package/src/core/hook-runner.service.ts +368 -0
- package/src/core/index.ts +5 -0
- package/src/core/lifecycle-hook.interface.ts +148 -0
- package/src/core/service-responses.interface.ts +34 -0
- package/src/dismissible.module.integration.spec.ts +687 -0
- package/src/dismissible.module.ts +79 -0
- package/src/events/dismissible.events.ts +82 -0
- package/src/events/events.constants.ts +21 -0
- package/src/events/index.ts +2 -0
- package/src/exceptions/dismissible.exceptions.spec.ts +50 -0
- package/src/exceptions/dismissible.exceptions.ts +69 -0
- package/src/exceptions/index.ts +1 -0
- package/src/index.ts +9 -0
- package/src/request/index.ts +2 -0
- package/src/request/request-context.decorator.ts +15 -0
- package/src/request/request-context.interface.ts +12 -0
- package/src/response/dtos/base-response.dto.ts +11 -0
- package/src/response/dtos/error-response.dto.ts +36 -0
- package/src/response/dtos/index.ts +3 -0
- package/src/response/dtos/success-response.dto.ts +34 -0
- package/src/response/http-exception-filter.spec.ts +179 -0
- package/src/response/http-exception-filter.ts +21 -0
- package/src/response/index.ts +4 -0
- package/src/response/response.module.ts +9 -0
- package/src/response/response.service.spec.ts +72 -0
- package/src/response/response.service.ts +20 -0
- package/src/testing/factories.ts +42 -0
- package/src/testing/index.ts +1 -0
- package/src/utils/date/date.service.spec.ts +104 -0
- package/src/utils/date/date.service.ts +19 -0
- package/src/utils/date/index.ts +1 -0
- package/src/utils/dismissible.helper.ts +9 -0
- package/src/utils/index.ts +3 -0
- package/src/validation/dismissible-input.dto.ts +47 -0
- package/src/validation/index.ts +1 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.spec.json +12 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { mock } from 'ts-jest-mocker';
|
|
2
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
3
|
+
import { DismissibleService } from './dismissible.service';
|
|
4
|
+
import { DismissibleCoreService } from './dismissible-core.service';
|
|
5
|
+
import { HookRunner, IHookRunResult } from './hook-runner.service';
|
|
6
|
+
import { IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
7
|
+
import { ValidationService } from '@dismissible/nestjs-validation';
|
|
8
|
+
import { DismissibleEvents } from '../events';
|
|
9
|
+
import { createTestItem, createTestContext } from '../testing/factories';
|
|
10
|
+
|
|
11
|
+
describe('DismissibleService', () => {
|
|
12
|
+
let service: DismissibleService;
|
|
13
|
+
let mockCoreService: jest.Mocked<DismissibleCoreService>;
|
|
14
|
+
let mockHookRunner: jest.Mocked<HookRunner>;
|
|
15
|
+
let mockEventEmitter: jest.Mocked<EventEmitter2>;
|
|
16
|
+
let mockLogger: jest.Mocked<IDismissibleLogger>;
|
|
17
|
+
let mockValidationService: jest.Mocked<ValidationService>;
|
|
18
|
+
|
|
19
|
+
const testUserId = 'test-user-id';
|
|
20
|
+
|
|
21
|
+
const createHookResult = (id: string, userId = testUserId): IHookRunResult => ({
|
|
22
|
+
proceed: true,
|
|
23
|
+
id,
|
|
24
|
+
userId,
|
|
25
|
+
context: createTestContext(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
mockCoreService = mock(DismissibleCoreService, { failIfMockNotProvided: false });
|
|
30
|
+
mockHookRunner = mock(HookRunner, { failIfMockNotProvided: false });
|
|
31
|
+
mockEventEmitter = mock(EventEmitter2, { failIfMockNotProvided: false });
|
|
32
|
+
mockLogger = mock<IDismissibleLogger>({ failIfMockNotProvided: false });
|
|
33
|
+
mockValidationService = mock(ValidationService, { failIfMockNotProvided: false });
|
|
34
|
+
|
|
35
|
+
mockValidationService.validateDto.mockResolvedValue({} as never);
|
|
36
|
+
|
|
37
|
+
service = new DismissibleService(
|
|
38
|
+
mockCoreService,
|
|
39
|
+
mockHookRunner,
|
|
40
|
+
mockEventEmitter,
|
|
41
|
+
mockLogger,
|
|
42
|
+
mockValidationService,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('getOrCreate', () => {
|
|
47
|
+
it('should run request and get hooks for existing item', async () => {
|
|
48
|
+
const item = createTestItem({ id: 'existing-item' });
|
|
49
|
+
const context = createTestContext();
|
|
50
|
+
|
|
51
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('existing-item'));
|
|
52
|
+
mockHookRunner.runPreGet.mockResolvedValue(createHookResult('existing-item'));
|
|
53
|
+
mockCoreService.get.mockResolvedValue(item);
|
|
54
|
+
|
|
55
|
+
const result = await service.getOrCreate('existing-item', testUserId, context);
|
|
56
|
+
|
|
57
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
58
|
+
expect(mockCoreService.get).toHaveBeenCalledWith('existing-item', testUserId);
|
|
59
|
+
expect(mockHookRunner.runPreGet).toHaveBeenCalledWith(
|
|
60
|
+
'existing-item',
|
|
61
|
+
item,
|
|
62
|
+
testUserId,
|
|
63
|
+
expect.anything(),
|
|
64
|
+
);
|
|
65
|
+
expect(mockHookRunner.runPostGet).toHaveBeenCalled();
|
|
66
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
67
|
+
expect(mockCoreService.create).not.toHaveBeenCalled();
|
|
68
|
+
expect(mockHookRunner.runPreCreate).not.toHaveBeenCalled();
|
|
69
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
70
|
+
DismissibleEvents.ITEM_RETRIEVED,
|
|
71
|
+
expect.anything(),
|
|
72
|
+
);
|
|
73
|
+
expect(result.created).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should run pre-create hooks BEFORE creating for new item', async () => {
|
|
77
|
+
const item = createTestItem({ id: 'new-item' });
|
|
78
|
+
const context = createTestContext();
|
|
79
|
+
const callOrder: string[] = [];
|
|
80
|
+
|
|
81
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('new-item'));
|
|
82
|
+
mockCoreService.get.mockResolvedValue(null);
|
|
83
|
+
mockHookRunner.runPreCreate.mockImplementation(async () => {
|
|
84
|
+
callOrder.push('runPreCreate');
|
|
85
|
+
return createHookResult('new-item');
|
|
86
|
+
});
|
|
87
|
+
mockCoreService.create.mockImplementation(async () => {
|
|
88
|
+
callOrder.push('create');
|
|
89
|
+
return item;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = await service.getOrCreate('new-item', testUserId, context);
|
|
93
|
+
|
|
94
|
+
expect(callOrder).toEqual(['runPreCreate', 'create']);
|
|
95
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
96
|
+
expect(mockHookRunner.runPreCreate).toHaveBeenCalled();
|
|
97
|
+
expect(mockCoreService.create).toHaveBeenCalledWith('new-item', testUserId);
|
|
98
|
+
expect(mockHookRunner.runPostCreate).toHaveBeenCalled();
|
|
99
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
100
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
101
|
+
DismissibleEvents.ITEM_CREATED,
|
|
102
|
+
expect.anything(),
|
|
103
|
+
);
|
|
104
|
+
expect(result.created).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should NOT create item when pre-create hook blocks the operation', async () => {
|
|
108
|
+
const context = createTestContext();
|
|
109
|
+
|
|
110
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('new-item'));
|
|
111
|
+
mockCoreService.get.mockResolvedValue(null);
|
|
112
|
+
mockHookRunner.runPreCreate.mockResolvedValue({
|
|
113
|
+
proceed: false,
|
|
114
|
+
reason: 'Plan limit reached',
|
|
115
|
+
id: 'new-item',
|
|
116
|
+
userId: testUserId,
|
|
117
|
+
context: createTestContext(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await expect(service.getOrCreate('new-item', testUserId, context)).rejects.toThrow();
|
|
121
|
+
|
|
122
|
+
expect(mockCoreService.create).not.toHaveBeenCalled();
|
|
123
|
+
expect(mockHookRunner.runPostCreate).not.toHaveBeenCalled();
|
|
124
|
+
expect(mockEventEmitter.emit).not.toHaveBeenCalledWith(
|
|
125
|
+
DismissibleEvents.ITEM_CREATED,
|
|
126
|
+
expect.anything(),
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should NOT return existing item when pre-get hook blocks the operation', async () => {
|
|
131
|
+
const item = createTestItem({ id: 'existing-item' });
|
|
132
|
+
const context = createTestContext();
|
|
133
|
+
|
|
134
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('existing-item'));
|
|
135
|
+
mockCoreService.get.mockResolvedValue(item);
|
|
136
|
+
mockHookRunner.runPreGet.mockResolvedValue({
|
|
137
|
+
proceed: false,
|
|
138
|
+
reason: 'Item access denied',
|
|
139
|
+
id: 'existing-item',
|
|
140
|
+
userId: testUserId,
|
|
141
|
+
context: createTestContext(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await expect(service.getOrCreate('existing-item', testUserId, context)).rejects.toThrow();
|
|
145
|
+
|
|
146
|
+
expect(mockHookRunner.runPostGet).not.toHaveBeenCalled();
|
|
147
|
+
expect(mockHookRunner.runPostRequest).not.toHaveBeenCalled();
|
|
148
|
+
expect(mockEventEmitter.emit).not.toHaveBeenCalledWith(
|
|
149
|
+
DismissibleEvents.ITEM_RETRIEVED,
|
|
150
|
+
expect.anything(),
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('dismiss', () => {
|
|
156
|
+
it('should run hooks, call core service, and emit ITEM_DISMISSED event', async () => {
|
|
157
|
+
const item = createTestItem({ id: 'test-item' });
|
|
158
|
+
const previousItem = createTestItem({ id: 'test-item' });
|
|
159
|
+
const context = createTestContext();
|
|
160
|
+
|
|
161
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('test-item'));
|
|
162
|
+
mockHookRunner.runPreDismiss.mockResolvedValue(createHookResult('test-item'));
|
|
163
|
+
mockCoreService.dismiss.mockResolvedValue({ item, previousItem });
|
|
164
|
+
|
|
165
|
+
const result = await service.dismiss('test-item', testUserId, context);
|
|
166
|
+
|
|
167
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
168
|
+
expect(mockHookRunner.runPreDismiss).toHaveBeenCalled();
|
|
169
|
+
expect(mockCoreService.dismiss).toHaveBeenCalledWith('test-item', testUserId);
|
|
170
|
+
expect(mockHookRunner.runPostDismiss).toHaveBeenCalled();
|
|
171
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
172
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
173
|
+
DismissibleEvents.ITEM_DISMISSED,
|
|
174
|
+
expect.anything(),
|
|
175
|
+
);
|
|
176
|
+
expect(result.item).toEqual(item);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('restore', () => {
|
|
181
|
+
it('should run hooks, call core service, and emit ITEM_RESTORED event', async () => {
|
|
182
|
+
const item = createTestItem({ id: 'test-item' });
|
|
183
|
+
const previousItem = createTestItem({ id: 'test-item' });
|
|
184
|
+
const context = createTestContext();
|
|
185
|
+
|
|
186
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('test-item'));
|
|
187
|
+
mockHookRunner.runPreRestore.mockResolvedValue(createHookResult('test-item'));
|
|
188
|
+
mockCoreService.restore.mockResolvedValue({ item, previousItem });
|
|
189
|
+
|
|
190
|
+
const result = await service.restore('test-item', testUserId, context);
|
|
191
|
+
|
|
192
|
+
expect(mockHookRunner.runPreRequest).toHaveBeenCalled();
|
|
193
|
+
expect(mockHookRunner.runPreRestore).toHaveBeenCalled();
|
|
194
|
+
expect(mockCoreService.restore).toHaveBeenCalledWith('test-item', testUserId);
|
|
195
|
+
expect(mockHookRunner.runPostRestore).toHaveBeenCalled();
|
|
196
|
+
expect(mockHookRunner.runPostRequest).toHaveBeenCalled();
|
|
197
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
198
|
+
DismissibleEvents.ITEM_RESTORED,
|
|
199
|
+
expect.anything(),
|
|
200
|
+
);
|
|
201
|
+
expect(result.item).toEqual(item);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('logging', () => {
|
|
206
|
+
it('should log debug messages for operations', async () => {
|
|
207
|
+
const item = createTestItem({ id: 'test-item' });
|
|
208
|
+
const context = createTestContext();
|
|
209
|
+
|
|
210
|
+
mockHookRunner.runPreRequest.mockResolvedValue(createHookResult('test-item'));
|
|
211
|
+
mockHookRunner.runPreGet.mockResolvedValue(createHookResult('test-item'));
|
|
212
|
+
mockCoreService.get.mockResolvedValue(item);
|
|
213
|
+
|
|
214
|
+
await service.getOrCreate('test-item', testUserId, context);
|
|
215
|
+
|
|
216
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
217
|
+
expect.stringContaining('getOrCreate called'),
|
|
218
|
+
expect.any(Object),
|
|
219
|
+
);
|
|
220
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
221
|
+
expect.stringContaining('getOrCreate completed'),
|
|
222
|
+
expect.any(Object),
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
2
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
3
|
+
import { DismissibleCoreService } from './dismissible-core.service';
|
|
4
|
+
import { HookRunner } from './hook-runner.service';
|
|
5
|
+
import { DISMISSIBLE_LOGGER, IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
6
|
+
import {
|
|
7
|
+
IGetOrCreateServiceResponse,
|
|
8
|
+
IDismissServiceResponse,
|
|
9
|
+
IRestoreServiceResponse,
|
|
10
|
+
} from './service-responses.interface';
|
|
11
|
+
import { IRequestContext } from '../request/request-context.interface';
|
|
12
|
+
import { DismissibleEvents } from '../events';
|
|
13
|
+
import {
|
|
14
|
+
ItemCreatedEvent,
|
|
15
|
+
ItemRetrievedEvent,
|
|
16
|
+
ItemDismissedEvent,
|
|
17
|
+
ItemRestoredEvent,
|
|
18
|
+
} from '../events';
|
|
19
|
+
import { ValidationService } from '@dismissible/nestjs-validation';
|
|
20
|
+
import { DismissibleInputDto } from '../validation';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Main orchestration service for dismissible operations.
|
|
24
|
+
* Coordinates core logic, hooks, and events.
|
|
25
|
+
*/
|
|
26
|
+
@Injectable()
|
|
27
|
+
export class DismissibleService {
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly coreService: DismissibleCoreService,
|
|
30
|
+
private readonly hookRunner: HookRunner,
|
|
31
|
+
private readonly eventEmitter: EventEmitter2,
|
|
32
|
+
@Inject(DISMISSIBLE_LOGGER)
|
|
33
|
+
private readonly logger: IDismissibleLogger,
|
|
34
|
+
private readonly validationService: ValidationService,
|
|
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
|
+
await this.validationService.validateDto(DismissibleInputDto, { itemId, userId });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get an existing item or create a new one.
|
|
47
|
+
* @param itemId The item identifier
|
|
48
|
+
* @param userId The user identifier (required)
|
|
49
|
+
* @param context Optional request context for tracing
|
|
50
|
+
*/
|
|
51
|
+
async getOrCreate(
|
|
52
|
+
itemId: string,
|
|
53
|
+
userId: string,
|
|
54
|
+
context?: IRequestContext,
|
|
55
|
+
): Promise<IGetOrCreateServiceResponse> {
|
|
56
|
+
this.logger.debug(`getOrCreate called`, { itemId, userId });
|
|
57
|
+
|
|
58
|
+
await this.validateInput(itemId, userId);
|
|
59
|
+
|
|
60
|
+
const preResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
61
|
+
HookRunner.throwIfBlocked(preResult);
|
|
62
|
+
|
|
63
|
+
const resolvedId = preResult.id;
|
|
64
|
+
const resolvedUserId = preResult.userId;
|
|
65
|
+
const resolvedContext = preResult.context;
|
|
66
|
+
|
|
67
|
+
const existingItem = await this.coreService.get(resolvedId, resolvedUserId);
|
|
68
|
+
|
|
69
|
+
if (existingItem) {
|
|
70
|
+
const preGetResult = await this.hookRunner.runPreGet(
|
|
71
|
+
resolvedId,
|
|
72
|
+
existingItem,
|
|
73
|
+
resolvedUserId,
|
|
74
|
+
resolvedContext,
|
|
75
|
+
);
|
|
76
|
+
HookRunner.throwIfBlocked(preGetResult);
|
|
77
|
+
|
|
78
|
+
this.eventEmitter.emit(
|
|
79
|
+
DismissibleEvents.ITEM_RETRIEVED,
|
|
80
|
+
new ItemRetrievedEvent(resolvedId, existingItem, resolvedUserId, resolvedContext),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await this.hookRunner.runPostGet(resolvedId, existingItem, resolvedUserId, resolvedContext);
|
|
84
|
+
|
|
85
|
+
await this.hookRunner.runPostRequest(
|
|
86
|
+
resolvedId,
|
|
87
|
+
existingItem,
|
|
88
|
+
resolvedUserId,
|
|
89
|
+
resolvedContext,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
this.logger.debug(`getOrCreate completed`, { itemId, created: false });
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
item: existingItem,
|
|
96
|
+
created: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const preCreateResult = await this.hookRunner.runPreCreate(
|
|
101
|
+
resolvedId,
|
|
102
|
+
resolvedUserId,
|
|
103
|
+
resolvedContext,
|
|
104
|
+
);
|
|
105
|
+
HookRunner.throwIfBlocked(preCreateResult);
|
|
106
|
+
|
|
107
|
+
const createdItem = await this.coreService.create(resolvedId, resolvedUserId);
|
|
108
|
+
|
|
109
|
+
await this.hookRunner.runPostCreate(resolvedId, createdItem, resolvedUserId, resolvedContext);
|
|
110
|
+
|
|
111
|
+
this.eventEmitter.emit(
|
|
112
|
+
DismissibleEvents.ITEM_CREATED,
|
|
113
|
+
new ItemCreatedEvent(resolvedId, createdItem, resolvedUserId, resolvedContext),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await this.hookRunner.runPostRequest(resolvedId, createdItem, resolvedUserId, resolvedContext);
|
|
117
|
+
|
|
118
|
+
this.logger.debug(`getOrCreate completed`, { itemId, created: true });
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
item: createdItem,
|
|
122
|
+
created: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Dismiss an item.
|
|
128
|
+
* @param itemId The item identifier
|
|
129
|
+
* @param userId The user identifier (required)
|
|
130
|
+
* @param context Optional request context for tracing
|
|
131
|
+
*/
|
|
132
|
+
async dismiss(
|
|
133
|
+
itemId: string,
|
|
134
|
+
userId: string,
|
|
135
|
+
context?: IRequestContext,
|
|
136
|
+
): Promise<IDismissServiceResponse> {
|
|
137
|
+
this.logger.debug(`dismiss called`, { itemId, userId });
|
|
138
|
+
|
|
139
|
+
await this.validateInput(itemId, userId);
|
|
140
|
+
|
|
141
|
+
const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
142
|
+
HookRunner.throwIfBlocked(preRequestResult);
|
|
143
|
+
|
|
144
|
+
const resolvedId = preRequestResult.id;
|
|
145
|
+
const resolvedUserId = preRequestResult.userId;
|
|
146
|
+
const resolvedContext = preRequestResult.context;
|
|
147
|
+
|
|
148
|
+
const preDismissResult = await this.hookRunner.runPreDismiss(
|
|
149
|
+
resolvedId,
|
|
150
|
+
resolvedUserId,
|
|
151
|
+
resolvedContext,
|
|
152
|
+
);
|
|
153
|
+
HookRunner.throwIfBlocked(preDismissResult);
|
|
154
|
+
|
|
155
|
+
const result = await this.coreService.dismiss(resolvedId, resolvedUserId);
|
|
156
|
+
|
|
157
|
+
await this.hookRunner.runPostDismiss(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
158
|
+
|
|
159
|
+
this.eventEmitter.emit(
|
|
160
|
+
DismissibleEvents.ITEM_DISMISSED,
|
|
161
|
+
new ItemDismissedEvent(
|
|
162
|
+
resolvedId,
|
|
163
|
+
result.item,
|
|
164
|
+
result.previousItem,
|
|
165
|
+
resolvedUserId,
|
|
166
|
+
resolvedContext,
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
await this.hookRunner.runPostRequest(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
171
|
+
|
|
172
|
+
this.logger.debug(`dismiss completed`, { itemId });
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Restore a dismissed item.
|
|
179
|
+
* @param itemId The item identifier
|
|
180
|
+
* @param userId The user identifier (required)
|
|
181
|
+
* @param context Optional request context for tracing
|
|
182
|
+
*/
|
|
183
|
+
async restore(
|
|
184
|
+
itemId: string,
|
|
185
|
+
userId: string,
|
|
186
|
+
context?: IRequestContext,
|
|
187
|
+
): Promise<IRestoreServiceResponse> {
|
|
188
|
+
this.logger.debug(`restore called`, { itemId, userId });
|
|
189
|
+
|
|
190
|
+
await this.validateInput(itemId, userId);
|
|
191
|
+
|
|
192
|
+
const preRequestResult = await this.hookRunner.runPreRequest(itemId, userId, context);
|
|
193
|
+
HookRunner.throwIfBlocked(preRequestResult);
|
|
194
|
+
|
|
195
|
+
const resolvedId = preRequestResult.id;
|
|
196
|
+
const resolvedUserId = preRequestResult.userId;
|
|
197
|
+
const resolvedContext = preRequestResult.context;
|
|
198
|
+
|
|
199
|
+
const preRestoreResult = await this.hookRunner.runPreRestore(
|
|
200
|
+
resolvedId,
|
|
201
|
+
resolvedUserId,
|
|
202
|
+
resolvedContext,
|
|
203
|
+
);
|
|
204
|
+
HookRunner.throwIfBlocked(preRestoreResult);
|
|
205
|
+
|
|
206
|
+
const result = await this.coreService.restore(resolvedId, resolvedUserId);
|
|
207
|
+
|
|
208
|
+
await this.hookRunner.runPostRestore(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
209
|
+
|
|
210
|
+
this.eventEmitter.emit(
|
|
211
|
+
DismissibleEvents.ITEM_RESTORED,
|
|
212
|
+
new ItemRestoredEvent(
|
|
213
|
+
resolvedId,
|
|
214
|
+
result.item,
|
|
215
|
+
result.previousItem,
|
|
216
|
+
resolvedUserId,
|
|
217
|
+
resolvedContext,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
await this.hookRunner.runPostRequest(resolvedId, result.item, resolvedUserId, resolvedContext);
|
|
222
|
+
|
|
223
|
+
this.logger.debug(`restore completed`, { itemId });
|
|
224
|
+
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
}
|