@dismissible/nestjs-dismissible 0.0.2-canary.738340d.0 → 0.0.2-canary.a611bd3.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.
Files changed (223) hide show
  1. package/README.md +58 -74
  2. package/package.json +23 -18
  3. package/src/api/dismissible-item-response.dto.d.ts +9 -0
  4. package/src/api/dismissible-item-response.dto.js +40 -0
  5. package/src/api/dismissible-item-response.dto.js.map +1 -0
  6. package/src/api/dismissible-item.mapper.d.ts +12 -0
  7. package/src/api/dismissible-item.mapper.js +30 -0
  8. package/src/api/dismissible-item.mapper.js.map +1 -0
  9. package/src/api/dismissible-item.mapper.spec.d.ts +1 -0
  10. package/src/api/dismissible-item.mapper.spec.js +43 -0
  11. package/src/api/dismissible-item.mapper.spec.js.map +1 -0
  12. package/src/api/{index.ts → index.d.ts} +1 -4
  13. package/src/api/index.js +8 -0
  14. package/src/api/index.js.map +1 -0
  15. package/src/api/use-cases/{api-tags.constants.ts → api-tags.constants.d.ts} +1 -1
  16. package/src/api/use-cases/api-tags.constants.js +8 -0
  17. package/src/api/use-cases/api-tags.constants.js.map +1 -0
  18. package/src/api/use-cases/dismiss/dismiss.controller.d.ts +15 -0
  19. package/src/api/use-cases/dismiss/dismiss.controller.js +74 -0
  20. package/src/api/use-cases/dismiss/dismiss.controller.js.map +1 -0
  21. package/src/api/use-cases/dismiss/dismiss.controller.spec.d.ts +1 -0
  22. package/src/api/use-cases/dismiss/dismiss.controller.spec.js +37 -0
  23. package/src/api/use-cases/dismiss/dismiss.controller.spec.js.map +1 -0
  24. package/src/api/use-cases/dismiss/dismiss.response.dto.d.ts +12 -0
  25. package/src/api/use-cases/dismiss/dismiss.response.dto.js +12 -0
  26. package/src/api/use-cases/dismiss/dismiss.response.dto.js.map +1 -0
  27. package/src/api/use-cases/dismiss/index.js +6 -0
  28. package/src/api/use-cases/dismiss/index.js.map +1 -0
  29. package/src/api/use-cases/get-or-create/get-or-create.controller.d.ts +15 -0
  30. package/src/api/use-cases/get-or-create/get-or-create.controller.js +70 -0
  31. package/src/api/use-cases/get-or-create/get-or-create.controller.js.map +1 -0
  32. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.d.ts +1 -0
  33. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.js +32 -0
  34. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.js.map +1 -0
  35. package/src/api/use-cases/get-or-create/get-or-create.response.dto.d.ts +12 -0
  36. package/src/api/use-cases/get-or-create/get-or-create.response.dto.js +12 -0
  37. package/src/api/use-cases/get-or-create/get-or-create.response.dto.js.map +1 -0
  38. package/src/api/use-cases/get-or-create/{index.ts → index.d.ts} +0 -1
  39. package/src/api/use-cases/get-or-create/index.js +6 -0
  40. package/src/api/use-cases/get-or-create/index.js.map +1 -0
  41. package/src/api/use-cases/index.js +7 -0
  42. package/src/api/use-cases/index.js.map +1 -0
  43. package/src/api/use-cases/restore/index.js +6 -0
  44. package/src/api/use-cases/restore/index.js.map +1 -0
  45. package/src/api/use-cases/restore/restore.controller.d.ts +15 -0
  46. package/src/api/use-cases/restore/restore.controller.js +74 -0
  47. package/src/api/use-cases/restore/restore.controller.js.map +1 -0
  48. package/src/api/use-cases/restore/restore.controller.spec.d.ts +1 -0
  49. package/src/api/use-cases/restore/restore.controller.spec.js +37 -0
  50. package/src/api/use-cases/restore/restore.controller.spec.js.map +1 -0
  51. package/src/api/use-cases/restore/restore.response.dto.d.ts +12 -0
  52. package/src/api/use-cases/restore/restore.response.dto.js +12 -0
  53. package/src/api/use-cases/restore/restore.response.dto.js.map +1 -0
  54. package/src/api/validation/index.d.ts +2 -0
  55. package/src/api/validation/index.js +6 -0
  56. package/src/api/validation/index.js.map +1 -0
  57. package/src/api/validation/param-validation.pipe.d.ts +11 -0
  58. package/src/api/validation/param-validation.pipe.js +36 -0
  59. package/src/api/validation/param-validation.pipe.js.map +1 -0
  60. package/src/api/validation/param-validation.pipe.spec.d.ts +1 -0
  61. package/src/api/validation/param-validation.pipe.spec.js +269 -0
  62. package/src/api/validation/param-validation.pipe.spec.js.map +1 -0
  63. package/src/api/validation/param.decorators.d.ts +28 -0
  64. package/src/api/validation/param.decorators.js +36 -0
  65. package/src/api/validation/param.decorators.js.map +1 -0
  66. package/src/core/dismissible-core.service.d.ts +56 -0
  67. package/src/core/dismissible-core.service.js +147 -0
  68. package/src/core/dismissible-core.service.js.map +1 -0
  69. package/src/core/dismissible-core.service.spec.d.ts +1 -0
  70. package/src/core/dismissible-core.service.spec.js +309 -0
  71. package/src/core/dismissible-core.service.spec.js.map +1 -0
  72. package/src/core/dismissible.service.d.ts +45 -0
  73. package/src/core/dismissible.service.js +127 -0
  74. package/src/core/dismissible.service.js.map +1 -0
  75. package/src/core/dismissible.service.spec.d.ts +1 -0
  76. package/src/core/dismissible.service.spec.js +159 -0
  77. package/src/core/dismissible.service.spec.js.map +1 -0
  78. package/src/core/hook-runner.service.d.ts +88 -0
  79. package/src/core/hook-runner.service.js +226 -0
  80. package/src/core/hook-runner.service.js.map +1 -0
  81. package/src/core/hook-runner.service.spec.d.ts +1 -0
  82. package/src/core/hook-runner.service.spec.js +538 -0
  83. package/src/core/hook-runner.service.spec.js.map +1 -0
  84. package/src/core/{index.ts → index.d.ts} +0 -1
  85. package/src/core/index.js +9 -0
  86. package/src/core/index.js.map +1 -0
  87. package/src/core/lifecycle-hook.interface.d.ts +1 -0
  88. package/src/core/lifecycle-hook.interface.js +7 -0
  89. package/src/core/lifecycle-hook.interface.js.map +1 -0
  90. package/src/core/service-responses.interface.d.ts +28 -0
  91. package/src/core/service-responses.interface.js +3 -0
  92. package/src/core/service-responses.interface.js.map +1 -0
  93. package/src/dismissible.module.d.ts +13 -0
  94. package/src/dismissible.module.integration.spec.d.ts +1 -0
  95. package/src/dismissible.module.integration.spec.js +529 -0
  96. package/src/dismissible.module.integration.spec.js.map +1 -0
  97. package/src/dismissible.module.js +77 -0
  98. package/src/dismissible.module.js.map +1 -0
  99. package/src/events/dismissible.events.d.ts +45 -0
  100. package/src/events/dismissible.events.js +53 -0
  101. package/src/events/dismissible.events.js.map +1 -0
  102. package/src/events/events.constants.d.ts +17 -0
  103. package/src/events/events.constants.js +17 -0
  104. package/src/events/events.constants.js.map +1 -0
  105. package/src/events/index.js +6 -0
  106. package/src/events/index.js.map +1 -0
  107. package/src/exceptions/dismissible.exceptions.d.ts +26 -0
  108. package/src/exceptions/dismissible.exceptions.js +49 -0
  109. package/src/exceptions/dismissible.exceptions.js.map +1 -0
  110. package/src/exceptions/dismissible.exceptions.spec.d.ts +1 -0
  111. package/src/exceptions/dismissible.exceptions.spec.js +40 -0
  112. package/src/exceptions/dismissible.exceptions.spec.js.map +1 -0
  113. package/src/exceptions/index.js +5 -0
  114. package/src/exceptions/index.js.map +1 -0
  115. package/src/{index.ts → index.d.ts} +1 -1
  116. package/src/index.js +12 -0
  117. package/src/index.js.map +1 -0
  118. package/src/response/dtos/base-response.dto.d.ts +6 -0
  119. package/src/response/dtos/base-response.dto.js +18 -0
  120. package/src/response/dtos/base-response.dto.js.map +1 -0
  121. package/src/response/dtos/error-response.dto.d.ts +19 -0
  122. package/src/response/dtos/error-response.dto.js +39 -0
  123. package/src/response/dtos/error-response.dto.js.map +1 -0
  124. package/src/response/dtos/index.js +7 -0
  125. package/src/response/dtos/index.js.map +1 -0
  126. package/src/response/dtos/{success-response.dto.ts → success-response.dto.d.ts} +6 -12
  127. package/src/response/dtos/success-response.dto.js +34 -0
  128. package/src/response/dtos/success-response.dto.js.map +1 -0
  129. package/src/response/http-exception-filter.d.ts +4 -0
  130. package/src/response/http-exception-filter.js +24 -0
  131. package/src/response/http-exception-filter.js.map +1 -0
  132. package/src/response/http-exception-filter.spec.d.ts +1 -0
  133. package/src/response/http-exception-filter.spec.js +137 -0
  134. package/src/response/http-exception-filter.spec.js.map +1 -0
  135. package/src/response/index.js +8 -0
  136. package/src/response/index.js.map +1 -0
  137. package/src/response/response.module.d.ts +2 -0
  138. package/src/response/response.module.js +17 -0
  139. package/src/response/response.module.js.map +1 -0
  140. package/src/response/response.service.d.ts +6 -0
  141. package/src/response/response.service.js +25 -0
  142. package/src/response/response.service.js.map +1 -0
  143. package/src/response/response.service.spec.d.ts +1 -0
  144. package/src/response/response.service.spec.js +58 -0
  145. package/src/response/response.service.spec.js.map +1 -0
  146. package/src/testing/factories.d.ts +14 -0
  147. package/src/testing/factories.js +58 -0
  148. package/src/testing/factories.js.map +1 -0
  149. package/src/testing/index.js +5 -0
  150. package/src/testing/index.js.map +1 -0
  151. package/src/utils/date/date.service.d.ts +8 -0
  152. package/src/utils/date/date.service.js +24 -0
  153. package/src/utils/date/date.service.js.map +1 -0
  154. package/src/utils/date/date.service.spec.d.ts +1 -0
  155. package/src/utils/date/date.service.spec.js +83 -0
  156. package/src/utils/date/date.service.spec.js.map +1 -0
  157. package/src/utils/date/index.js +5 -0
  158. package/src/utils/date/index.js.map +1 -0
  159. package/src/utils/dismissible.helper.d.ts +4 -0
  160. package/src/utils/dismissible.helper.js +15 -0
  161. package/src/utils/dismissible.helper.js.map +1 -0
  162. package/src/utils/index.js +7 -0
  163. package/src/utils/index.js.map +1 -0
  164. package/src/validation/dismissible-input.dto.d.ts +21 -0
  165. package/src/validation/dismissible-input.dto.js +54 -0
  166. package/src/validation/dismissible-input.dto.js.map +1 -0
  167. package/src/validation/index.d.ts +1 -0
  168. package/src/validation/index.js +5 -0
  169. package/src/validation/index.js.map +1 -0
  170. package/jest.config.ts +0 -29
  171. package/project.json +0 -42
  172. package/src/api/dismissible-item-response.dto.ts +0 -38
  173. package/src/api/dismissible-item.mapper.spec.ts +0 -63
  174. package/src/api/dismissible-item.mapper.ts +0 -33
  175. package/src/api/use-cases/dismiss/dismiss.controller.spec.ts +0 -42
  176. package/src/api/use-cases/dismiss/dismiss.controller.ts +0 -63
  177. package/src/api/use-cases/dismiss/dismiss.response.dto.ts +0 -7
  178. package/src/api/use-cases/get-or-create/get-or-create.controller.spec.ts +0 -76
  179. package/src/api/use-cases/get-or-create/get-or-create.controller.ts +0 -106
  180. package/src/api/use-cases/get-or-create/get-or-create.request.dto.ts +0 -17
  181. package/src/api/use-cases/get-or-create/get-or-create.response.dto.ts +0 -7
  182. package/src/api/use-cases/restore/restore.controller.spec.ts +0 -42
  183. package/src/api/use-cases/restore/restore.controller.ts +0 -63
  184. package/src/api/use-cases/restore/restore.response.dto.ts +0 -7
  185. package/src/core/create-options.ts +0 -9
  186. package/src/core/dismissible-core.service.spec.ts +0 -357
  187. package/src/core/dismissible-core.service.ts +0 -161
  188. package/src/core/dismissible.service.spec.ts +0 -144
  189. package/src/core/dismissible.service.ts +0 -188
  190. package/src/core/hook-runner.service.spec.ts +0 -304
  191. package/src/core/hook-runner.service.ts +0 -267
  192. package/src/core/lifecycle-hook.interface.ts +0 -122
  193. package/src/core/service-responses.interface.ts +0 -34
  194. package/src/dismissible.module.ts +0 -83
  195. package/src/events/dismissible.events.ts +0 -105
  196. package/src/events/events.constants.ts +0 -21
  197. package/src/exceptions/dismissible.exceptions.spec.ts +0 -50
  198. package/src/exceptions/dismissible.exceptions.ts +0 -69
  199. package/src/request/index.ts +0 -2
  200. package/src/request/request-context.decorator.ts +0 -14
  201. package/src/request/request-context.interface.ts +0 -6
  202. package/src/response/dtos/base-response.dto.ts +0 -11
  203. package/src/response/dtos/error-response.dto.ts +0 -36
  204. package/src/response/http-exception-filter.ts +0 -21
  205. package/src/response/response.module.ts +0 -9
  206. package/src/response/response.service.spec.ts +0 -86
  207. package/src/response/response.service.ts +0 -20
  208. package/src/testing/factories.ts +0 -45
  209. package/src/utils/date/date.service.spec.ts +0 -104
  210. package/src/utils/date/date.service.ts +0 -19
  211. package/src/utils/dismissible.helper.ts +0 -9
  212. package/tsconfig.json +0 -13
  213. package/tsconfig.lib.json +0 -14
  214. /package/src/api/use-cases/dismiss/{index.ts → index.d.ts} +0 -0
  215. /package/src/api/use-cases/{index.ts → index.d.ts} +0 -0
  216. /package/src/api/use-cases/restore/{index.ts → index.d.ts} +0 -0
  217. /package/src/events/{index.ts → index.d.ts} +0 -0
  218. /package/src/exceptions/{index.ts → index.d.ts} +0 -0
  219. /package/src/response/dtos/{index.ts → index.d.ts} +0 -0
  220. /package/src/response/{index.ts → index.d.ts} +0 -0
  221. /package/src/testing/{index.ts → index.d.ts} +0 -0
  222. /package/src/utils/date/{index.ts → index.d.ts} +0 -0
  223. /package/src/utils/{index.ts → index.d.ts} +0 -0
@@ -1,63 +0,0 @@
1
- import { Controller, Post, Param, UseFilters } from '@nestjs/common';
2
- import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
3
- import { DismissibleService } from '../../../core/dismissible.service';
4
- import { DismissibleItemMapper } from '../../dismissible-item.mapper';
5
- import { RequestContext } from '../../../request/request-context.decorator';
6
- import { IRequestContext } from '../../../request/request-context.interface';
7
- import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
8
- import { RestoreResponseDto } from './restore.response.dto';
9
- import { ResponseService } from '../../../response/response.service';
10
- import { HttpExceptionFilter } from '../../../response/http-exception-filter';
11
- import { API_TAG_DISMISSIBLE } from '../api-tags.constants';
12
-
13
- /**
14
- * Controller for restore dismissible item operations.
15
- */
16
- @ApiTags(API_TAG_DISMISSIBLE)
17
- @Controller('v1/user/:userId/dismissible-item')
18
- export class RestoreController {
19
- constructor(
20
- private readonly dismissibleService: DismissibleService<BaseMetadata>,
21
- private readonly mapper: DismissibleItemMapper,
22
- private readonly responseService: ResponseService,
23
- ) {}
24
-
25
- @Post(':itemId')
26
- @ApiOperation({
27
- summary: 'Restore a dismissed item',
28
- description: 'Restores a previously dismissed item.',
29
- })
30
- @ApiParam({
31
- name: 'userId',
32
- description: 'User identifier',
33
- example: 'user-123',
34
- })
35
- @ApiParam({
36
- name: 'itemId',
37
- description: 'Unique identifier for the dismissible item',
38
- example: 'welcome-banner-v2',
39
- })
40
- @ApiResponse({
41
- status: 200,
42
- description: 'The restored item',
43
- type: RestoreResponseDto,
44
- })
45
- @ApiResponse({
46
- status: 400,
47
- description: 'Item not found or not dismissed',
48
- })
49
- @ApiResponse({
50
- status: 403,
51
- description: 'Operation blocked by lifecycle hook',
52
- })
53
- @UseFilters(HttpExceptionFilter)
54
- async restore(
55
- @Param('userId') userId: string,
56
- @Param('itemId') itemId: string,
57
- @RequestContext() context: IRequestContext,
58
- ): Promise<RestoreResponseDto> {
59
- const result = await this.dismissibleService.restore(itemId, userId, context);
60
-
61
- return this.responseService.success(this.mapper.toResponseDto(result.item));
62
- }
63
- }
@@ -1,7 +0,0 @@
1
- import { SuccessResponseDto } from '../../../response/dtos/success-response.dto';
2
- import { DismissibleItemResponseDto } from '../../dismissible-item-response.dto';
3
-
4
- /**
5
- * Response DTO for the restore operation.
6
- */
7
- export class RestoreResponseDto extends SuccessResponseDto(DismissibleItemResponseDto) {}
@@ -1,9 +0,0 @@
1
- import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
2
-
3
- /**
4
- * Options for creating a new dismissible item.
5
- */
6
- export interface ICreateItemOptions<TMetadata extends BaseMetadata = BaseMetadata> {
7
- /** Optional metadata to attach to the item */
8
- metadata?: TMetadata;
9
- }
@@ -1,357 +0,0 @@
1
- import { Mock, mock } from 'ts-jest-mocker';
2
- import { DismissibleCoreService } from './dismissible-core.service';
3
- import { IDismissibleStorage } from '@dismissible/nestjs-storage';
4
- import { createTestItem, createDismissedTestItem } from '../testing/factories';
5
- import {
6
- ItemNotFoundException,
7
- ItemAlreadyDismissedException,
8
- ItemNotDismissedException,
9
- } from '../exceptions';
10
- import { BaseMetadata } from '@dismissible/nestjs-dismissible-item';
11
- import { DismissibleItemFactory } from '@dismissible/nestjs-dismissible-item';
12
- import { IDismissibleLogger } from '@dismissible/nestjs-logger';
13
- import { ValidationService } from '@dismissible/nestjs-validation';
14
- import { BadRequestException } from '@nestjs/common';
15
- import { DismissibleHelper } from '../utils/dismissible.helper';
16
- import { DateService } from '../utils/date/date.service';
17
-
18
- describe('DismissibleCoreService', () => {
19
- let service: DismissibleCoreService<BaseMetadata>;
20
- let storage: Mock<IDismissibleStorage<BaseMetadata>>;
21
- let mockDateService: Mock<DateService>;
22
- let mockLogger: Mock<IDismissibleLogger>;
23
- let itemFactory: Mock<DismissibleItemFactory>;
24
- let validationService: Mock<ValidationService>;
25
- let dismissibleHelper: Mock<DismissibleHelper>;
26
-
27
- beforeEach(() => {
28
- mockDateService = mock(DateService);
29
- mockDateService.getNow.mockReturnValue(new Date('2024-01-15T10:00:00.000Z'));
30
- mockLogger = mock<IDismissibleLogger>({
31
- failIfMockNotProvided: false,
32
- });
33
- storage = mock<IDismissibleStorage<BaseMetadata>>({
34
- failIfMockNotProvided: false,
35
- });
36
- dismissibleHelper = mock(DismissibleHelper, { failIfMockNotProvided: false });
37
- itemFactory = mock(DismissibleItemFactory);
38
- validationService = mock(ValidationService, { failIfMockNotProvided: false });
39
- validationService.validateInstance.mockResolvedValue(undefined);
40
- service = new DismissibleCoreService(
41
- storage,
42
- mockDateService,
43
- mockLogger,
44
- itemFactory,
45
- validationService,
46
- dismissibleHelper,
47
- );
48
- });
49
-
50
- afterEach(() => {
51
- jest.clearAllMocks();
52
- });
53
-
54
- describe('getOrCreate', () => {
55
- it('should create a new item when it does not exist', async () => {
56
- const testDate = new Date('2024-01-15T10:00:00.000Z');
57
- const userId = 'user-123';
58
- const newItem = createTestItem({ id: 'new-item', userId, createdAt: testDate });
59
-
60
- storage.get.mockResolvedValue(null);
61
- storage.create.mockResolvedValue(newItem);
62
- mockDateService.getNow.mockReturnValue(testDate);
63
- itemFactory.create.mockReturnValue(newItem);
64
-
65
- const result = await service.getOrCreate('new-item', userId);
66
-
67
- expect(result.created).toBe(true);
68
- expect(result.item.id).toBe('new-item');
69
- expect(result.item.userId).toBe(userId);
70
- expect(result.item.createdAt).toBeInstanceOf(Date);
71
- expect(result.item.dismissedAt).toBeUndefined();
72
- expect(storage.create).toHaveBeenCalledWith(newItem);
73
- });
74
-
75
- it('should return existing item when it exists', async () => {
76
- const userId = 'user-123';
77
- const existingItem = createTestItem({ id: 'existing-item', userId });
78
- storage.get.mockResolvedValue(existingItem);
79
-
80
- const result = await service.getOrCreate('existing-item', userId);
81
-
82
- expect(result.created).toBe(false);
83
- expect(result.item).toEqual(existingItem);
84
- expect(storage.get).toHaveBeenCalledWith(userId, 'existing-item');
85
- });
86
-
87
- it('should create item with metadata when provided', async () => {
88
- const userId = 'user-123';
89
- const metadata = { version: 2, category: 'test' };
90
- const testDate = new Date('2024-01-15T10:00:00.000Z');
91
- const newItem = createTestItem({ id: 'new-item', userId, metadata, createdAt: testDate });
92
-
93
- storage.get.mockResolvedValue(null);
94
- storage.create.mockResolvedValue(newItem);
95
- mockDateService.getNow.mockReturnValue(testDate);
96
- itemFactory.create.mockReturnValue(newItem);
97
-
98
- const result = await service.getOrCreate('new-item', userId, { metadata });
99
-
100
- expect(result.item.metadata).toEqual(metadata);
101
- expect(storage.create).toHaveBeenCalledWith(newItem);
102
- });
103
- });
104
-
105
- describe('dismiss', () => {
106
- it('should dismiss an existing item', async () => {
107
- const userId = 'user-123';
108
- const item = createTestItem({ id: 'test-item', userId });
109
- const previousItem = createTestItem({ id: 'test-item', userId });
110
- const dismissedItem = createDismissedTestItem({ id: 'test-item', userId });
111
- const testDate = new Date('2024-01-15T12:00:00.000Z');
112
-
113
- storage.get.mockResolvedValue(item);
114
- storage.update.mockResolvedValue(dismissedItem);
115
- itemFactory.clone.mockReturnValue(previousItem);
116
- mockDateService.getNow.mockReturnValue(testDate);
117
- itemFactory.createDismissed.mockReturnValue(dismissedItem);
118
- dismissibleHelper.isDismissed.mockReturnValue(false);
119
-
120
- const result = await service.dismiss('test-item', userId);
121
-
122
- expect(result.item.dismissedAt).toBeDefined();
123
- expect(result.previousItem.dismissedAt).toBeUndefined();
124
- expect(storage.update).toHaveBeenCalledWith(dismissedItem);
125
- expect(storage.get).toHaveBeenCalledWith(userId, 'test-item');
126
- });
127
-
128
- it('should throw ItemNotFoundException for non-existent item', async () => {
129
- const userId = 'user-123';
130
- storage.get.mockResolvedValue(null);
131
-
132
- await expect(service.dismiss('non-existent', userId)).rejects.toThrow(ItemNotFoundException);
133
- });
134
-
135
- it('should throw ItemAlreadyDismissedException for already dismissed item', async () => {
136
- const userId = 'user-123';
137
- const dismissedItem = createDismissedTestItem({ id: 'dismissed-item', userId });
138
- storage.get.mockResolvedValue(dismissedItem);
139
- dismissibleHelper.isDismissed.mockReturnValue(true);
140
-
141
- await expect(service.dismiss('dismissed-item', userId)).rejects.toThrow(
142
- ItemAlreadyDismissedException,
143
- );
144
- });
145
-
146
- it('should return the previous item state', async () => {
147
- const userId = 'user-123';
148
- const item = createTestItem({
149
- id: 'test-item',
150
- userId,
151
- metadata: { key: 'value' },
152
- });
153
- const previousItem = createTestItem({
154
- id: 'test-item',
155
- userId,
156
- metadata: { key: 'value' },
157
- });
158
- const dismissedItem = createDismissedTestItem({
159
- id: 'test-item',
160
- userId,
161
- metadata: { key: 'value' },
162
- });
163
- const testDate = new Date('2024-01-15T12:00:00.000Z');
164
-
165
- storage.get.mockResolvedValue(item);
166
- storage.update.mockResolvedValue(dismissedItem);
167
- itemFactory.clone.mockReturnValue(previousItem);
168
- mockDateService.getNow.mockReturnValue(testDate);
169
- itemFactory.createDismissed.mockReturnValue(dismissedItem);
170
- dismissibleHelper.isDismissed.mockReturnValue(false);
171
-
172
- const result = await service.dismiss('test-item', userId);
173
-
174
- expect(result.previousItem.id).toBe(item.id);
175
- expect(result.previousItem.dismissedAt).toBeUndefined();
176
- expect(result.previousItem.metadata).toEqual({ key: 'value' });
177
- });
178
- });
179
-
180
- describe('restore', () => {
181
- it('should restore a dismissed item', async () => {
182
- const userId = 'user-123';
183
- const dismissedItem = createDismissedTestItem({ id: 'dismissed-item', userId });
184
- const previousItem = createDismissedTestItem({ id: 'dismissed-item', userId });
185
- const restoredItem = createTestItem({ id: 'dismissed-item', userId });
186
-
187
- storage.get.mockResolvedValue(dismissedItem);
188
- storage.update.mockResolvedValue(restoredItem);
189
- itemFactory.clone.mockReturnValue(previousItem);
190
- itemFactory.createRestored.mockReturnValue(restoredItem);
191
- dismissibleHelper.isDismissed.mockReturnValue(true);
192
-
193
- const result = await service.restore('dismissed-item', userId);
194
-
195
- expect(result.item.dismissedAt).toBeUndefined();
196
- expect(result.previousItem.dismissedAt).toBeDefined();
197
- expect(storage.update).toHaveBeenCalledWith(restoredItem);
198
- expect(storage.get).toHaveBeenCalledWith(userId, 'dismissed-item');
199
- });
200
-
201
- it('should throw ItemNotFoundException for non-existent item', async () => {
202
- const userId = 'user-123';
203
- storage.get.mockResolvedValue(null);
204
-
205
- await expect(service.restore('non-existent', userId)).rejects.toThrow(ItemNotFoundException);
206
- });
207
-
208
- it('should throw ItemNotDismissedException for non-dismissed item', async () => {
209
- const userId = 'user-123';
210
- const item = createTestItem({ id: 'active-item', userId });
211
- storage.get.mockResolvedValue(item);
212
- dismissibleHelper.isDismissed.mockReturnValue(false);
213
-
214
- await expect(service.restore('active-item', userId)).rejects.toThrow(
215
- ItemNotDismissedException,
216
- );
217
- });
218
-
219
- it('should return the previous item state', async () => {
220
- const userId = 'user-123';
221
- const dismissedItem = createDismissedTestItem({
222
- id: 'dismissed-item',
223
- userId,
224
- metadata: { key: 'value' },
225
- });
226
- const previousItem = createDismissedTestItem({
227
- id: 'dismissed-item',
228
- userId,
229
- metadata: { key: 'value' },
230
- });
231
- const restoredItem = createTestItem({
232
- id: 'dismissed-item',
233
- userId,
234
- metadata: { key: 'value' },
235
- });
236
-
237
- storage.get.mockResolvedValue(dismissedItem);
238
- storage.update.mockResolvedValue(restoredItem);
239
- itemFactory.clone.mockReturnValue(previousItem);
240
- itemFactory.createRestored.mockReturnValue(restoredItem);
241
- dismissibleHelper.isDismissed.mockReturnValue(true);
242
-
243
- const result = await service.restore('dismissed-item', userId);
244
-
245
- expect(result.previousItem.id).toBe(dismissedItem.id);
246
- expect(result.previousItem.dismissedAt).toBeDefined();
247
- expect(result.previousItem.metadata).toEqual({ key: 'value' });
248
- });
249
- });
250
-
251
- describe('validation', () => {
252
- it('should throw BadRequestException when validation fails on create', async () => {
253
- const userId = 'user-123';
254
- const testDate = new Date('2024-01-15T10:00:00.000Z');
255
- const newItem = createTestItem({ id: 'new-item', userId, createdAt: testDate });
256
-
257
- storage.get.mockResolvedValue(null);
258
- mockDateService.getNow.mockReturnValue(testDate);
259
- itemFactory.create.mockReturnValue(newItem);
260
- validationService.validateInstance.mockRejectedValue(
261
- new BadRequestException('id must be a string'),
262
- );
263
-
264
- await expect(service.getOrCreate('new-item', userId)).rejects.toThrow(BadRequestException);
265
- expect(storage.create).not.toHaveBeenCalled();
266
- });
267
-
268
- it('should throw BadRequestException when validation fails on dismiss', async () => {
269
- const userId = 'user-123';
270
- const item = createTestItem({ id: 'test-item', userId });
271
- const previousItem = createTestItem({ id: 'test-item', userId });
272
- const dismissedItem = createDismissedTestItem({ id: 'test-item', userId });
273
- const testDate = new Date('2024-01-15T12:00:00.000Z');
274
-
275
- storage.get.mockResolvedValue(item);
276
- itemFactory.clone.mockReturnValue(previousItem);
277
- mockDateService.getNow.mockReturnValue(testDate);
278
- itemFactory.createDismissed.mockReturnValue(dismissedItem);
279
- dismissibleHelper.isDismissed.mockReturnValue(false);
280
- validationService.validateInstance.mockRejectedValue(
281
- new BadRequestException('dismissedAt must be a date'),
282
- );
283
-
284
- await expect(service.dismiss('test-item', userId)).rejects.toThrow(BadRequestException);
285
- expect(storage.update).not.toHaveBeenCalled();
286
- });
287
-
288
- it('should throw BadRequestException when validation fails on restore', async () => {
289
- const userId = 'user-123';
290
- const dismissedItem = createDismissedTestItem({ id: 'dismissed-item', userId });
291
- const previousItem = createDismissedTestItem({ id: 'dismissed-item', userId });
292
- const restoredItem = createTestItem({ id: 'dismissed-item', userId });
293
-
294
- storage.get.mockResolvedValue(dismissedItem);
295
- itemFactory.clone.mockReturnValue(previousItem);
296
- itemFactory.createRestored.mockReturnValue(restoredItem);
297
- dismissibleHelper.isDismissed.mockReturnValue(true);
298
- validationService.validateInstance.mockRejectedValue(
299
- new BadRequestException('id must be a string'),
300
- );
301
-
302
- await expect(service.restore('dismissed-item', userId)).rejects.toThrow(BadRequestException);
303
- expect(storage.update).not.toHaveBeenCalled();
304
- });
305
-
306
- it('should validate item when creating', async () => {
307
- const userId = 'user-123';
308
- const testDate = new Date('2024-01-15T10:00:00.000Z');
309
- const newItem = createTestItem({ id: 'new-item', userId, createdAt: testDate });
310
-
311
- storage.get.mockResolvedValue(null);
312
- storage.create.mockResolvedValue(newItem);
313
- mockDateService.getNow.mockReturnValue(testDate);
314
- itemFactory.create.mockReturnValue(newItem);
315
-
316
- await service.getOrCreate('new-item', userId);
317
-
318
- expect(validationService.validateInstance).toHaveBeenCalledWith(newItem);
319
- });
320
-
321
- it('should validate item when dismissing', async () => {
322
- const userId = 'user-123';
323
- const item = createTestItem({ id: 'test-item', userId });
324
- const previousItem = createTestItem({ id: 'test-item', userId });
325
- const dismissedItem = createDismissedTestItem({ id: 'test-item', userId });
326
- const testDate = new Date('2024-01-15T12:00:00.000Z');
327
-
328
- storage.get.mockResolvedValue(item);
329
- storage.update.mockResolvedValue(dismissedItem);
330
- itemFactory.clone.mockReturnValue(previousItem);
331
- mockDateService.getNow.mockReturnValue(testDate);
332
- itemFactory.createDismissed.mockReturnValue(dismissedItem);
333
- dismissibleHelper.isDismissed.mockReturnValue(false);
334
-
335
- await service.dismiss('test-item', userId);
336
-
337
- expect(validationService.validateInstance).toHaveBeenCalledWith(dismissedItem);
338
- });
339
-
340
- it('should validate item when restoring', async () => {
341
- const userId = 'user-123';
342
- const dismissedItem = createDismissedTestItem({ id: 'dismissed-item', userId });
343
- const previousItem = createDismissedTestItem({ id: 'dismissed-item', userId });
344
- const restoredItem = createTestItem({ id: 'dismissed-item', userId });
345
-
346
- storage.get.mockResolvedValue(dismissedItem);
347
- storage.update.mockResolvedValue(restoredItem);
348
- itemFactory.clone.mockReturnValue(previousItem);
349
- itemFactory.createRestored.mockReturnValue(restoredItem);
350
- dismissibleHelper.isDismissed.mockReturnValue(true);
351
-
352
- await service.restore('dismissed-item', userId);
353
-
354
- expect(validationService.validateInstance).toHaveBeenCalledWith(restoredItem);
355
- });
356
- });
357
- });
@@ -1,161 +0,0 @@
1
- import { Injectable, Inject } from '@nestjs/common';
2
- import { DISMISSIBLE_STORAGE_ADAPTER, IDismissibleStorage } from '@dismissible/nestjs-storage';
3
- import {
4
- IGetOrCreateServiceResponse,
5
- IDismissServiceResponse,
6
- IRestoreServiceResponse,
7
- } from './service-responses.interface';
8
- import { DismissibleHelper } from '../utils/dismissible.helper';
9
- import { DateService } from '../utils/date/date.service';
10
- import { DISMISSIBLE_LOGGER, IDismissibleLogger } from '@dismissible/nestjs-logger';
11
- import {
12
- ItemNotFoundException,
13
- ItemAlreadyDismissedException,
14
- ItemNotDismissedException,
15
- } from '../exceptions';
16
- import { ValidationService } from '@dismissible/nestjs-validation';
17
- import { BaseMetadata, DismissibleItemFactory } from '@dismissible/nestjs-dismissible-item';
18
- import { ICreateItemOptions } from './create-options';
19
-
20
- /**
21
- * Core business logic service for dismissible operations.
22
- * Handles pure CRUD operations without side effects (hooks, events).
23
- */
24
- @Injectable()
25
- export class DismissibleCoreService<TMetadata extends BaseMetadata = BaseMetadata> {
26
- constructor(
27
- @Inject(DISMISSIBLE_STORAGE_ADAPTER) private readonly storage: IDismissibleStorage<TMetadata>,
28
- private readonly dateService: DateService,
29
- @Inject(DISMISSIBLE_LOGGER) private readonly logger: IDismissibleLogger,
30
- private readonly itemFactory: DismissibleItemFactory,
31
- private readonly validationService: ValidationService,
32
- private readonly dismissibleHelper: DismissibleHelper,
33
- ) {}
34
-
35
- /**
36
- * Get an existing item or create a new one.
37
- * @param itemId The item identifier
38
- * @param userId The user identifier (required)
39
- * @param options Optional creation options (metadata)
40
- */
41
- async getOrCreate(
42
- itemId: string,
43
- userId: string,
44
- options?: ICreateItemOptions<TMetadata>,
45
- ): Promise<IGetOrCreateServiceResponse<TMetadata>> {
46
- this.logger.debug(`Looking up item in storage`, { itemId, userId });
47
-
48
- const existingItem = await this.storage.get(userId, itemId);
49
-
50
- if (existingItem) {
51
- this.logger.debug(`Found existing item`, { itemId, userId });
52
- return {
53
- item: existingItem,
54
- created: false,
55
- };
56
- }
57
-
58
- this.logger.debug(`Creating new item`, {
59
- itemId,
60
- userId,
61
- hasMetadata: !!options?.metadata,
62
- });
63
-
64
- // Create new item
65
- const now = this.dateService.getNow();
66
- const newItem = this.itemFactory.create<TMetadata>({
67
- id: itemId,
68
- createdAt: now,
69
- userId,
70
- metadata: options?.metadata,
71
- });
72
-
73
- // Validate the item before storage
74
- await this.validationService.validateInstance(newItem);
75
-
76
- const createdItem = await this.storage.create(newItem);
77
-
78
- this.logger.info(`Created new dismissible item`, { itemId, userId });
79
-
80
- return {
81
- item: createdItem,
82
- created: true,
83
- };
84
- }
85
-
86
- /**
87
- * Dismiss an item.
88
- * @param itemId The item identifier
89
- * @param userId The user identifier (required)
90
- * @throws ItemNotFoundException if item doesn't exist
91
- * @throws ItemAlreadyDismissedException if item is already dismissed
92
- */
93
- async dismiss(itemId: string, userId: string): Promise<IDismissServiceResponse<TMetadata>> {
94
- this.logger.debug(`Attempting to dismiss item`, { itemId, userId });
95
-
96
- const existingItem = await this.storage.get(userId, itemId);
97
-
98
- if (!existingItem) {
99
- this.logger.warn(`Cannot dismiss: item not found`, { itemId, userId });
100
- throw new ItemNotFoundException(itemId);
101
- }
102
-
103
- if (this.dismissibleHelper.isDismissed(existingItem)) {
104
- this.logger.warn(`Cannot dismiss: item already dismissed`, { itemId, userId });
105
- throw new ItemAlreadyDismissedException(itemId);
106
- }
107
-
108
- const previousItem = this.itemFactory.clone(existingItem);
109
- const dismissedItem = this.itemFactory.createDismissed(existingItem, this.dateService.getNow());
110
-
111
- // Validate the item before storage
112
- await this.validationService.validateInstance(dismissedItem);
113
-
114
- const updatedItem = await this.storage.update(dismissedItem);
115
-
116
- this.logger.info(`Item dismissed`, { itemId, userId });
117
-
118
- return {
119
- item: updatedItem,
120
- previousItem,
121
- };
122
- }
123
-
124
- /**
125
- * Restore a dismissed item.
126
- * @param itemId The item identifier
127
- * @param userId The user identifier (required)
128
- * @throws ItemNotFoundException if item doesn't exist
129
- * @throws ItemNotDismissedException if item is not dismissed
130
- */
131
- async restore(itemId: string, userId: string): Promise<IRestoreServiceResponse<TMetadata>> {
132
- this.logger.debug(`Attempting to restore item`, { itemId, userId });
133
-
134
- const existingItem = await this.storage.get(userId, itemId);
135
-
136
- if (!existingItem) {
137
- this.logger.warn(`Cannot restore: item not found`, { itemId, userId });
138
- throw new ItemNotFoundException(itemId);
139
- }
140
-
141
- if (!this.dismissibleHelper.isDismissed(existingItem)) {
142
- this.logger.warn(`Cannot restore: item not dismissed`, { itemId, userId });
143
- throw new ItemNotDismissedException(itemId);
144
- }
145
-
146
- const previousItem = this.itemFactory.clone(existingItem);
147
- const restoredItem = this.itemFactory.createRestored(existingItem);
148
-
149
- // Validate the item before storage
150
- await this.validationService.validateInstance(restoredItem);
151
-
152
- const updatedItem = await this.storage.update(restoredItem);
153
-
154
- this.logger.info(`Item restored`, { itemId, userId });
155
-
156
- return {
157
- item: updatedItem,
158
- previousItem,
159
- };
160
- }
161
- }