@intlayer/backend 7.5.9 → 7.5.10

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 (222) hide show
  1. package/README.md +9 -2
  2. package/dist/assets/utils/AI/askDocQuestion/PROMPT.md +1 -1
  3. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_fastify.json +9 -0
  4. package/dist/esm/controllers/ai.controller.mjs +95 -128
  5. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  6. package/dist/esm/controllers/dictionary.controller.mjs +86 -198
  7. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  8. package/dist/esm/controllers/eventListener.controller.mjs +13 -19
  9. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  10. package/dist/esm/controllers/github.controller.mjs +77 -0
  11. package/dist/esm/controllers/github.controller.mjs.map +1 -0
  12. package/dist/esm/controllers/newsletter.controller.mjs +30 -60
  13. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  14. package/dist/esm/controllers/oAuth2.controller.mjs +11 -8
  15. package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
  16. package/dist/esm/controllers/organization.controller.mjs +100 -225
  17. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  18. package/dist/esm/controllers/project.controller.mjs +87 -204
  19. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  20. package/dist/esm/controllers/projectAccessKey.controller.mjs +38 -71
  21. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  22. package/dist/esm/controllers/search.controller.mjs +3 -3
  23. package/dist/esm/controllers/search.controller.mjs.map +1 -1
  24. package/dist/esm/controllers/stripe.controller.mjs +34 -67
  25. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  26. package/dist/esm/controllers/tag.controller.mjs +51 -113
  27. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  28. package/dist/esm/controllers/user.controller.mjs +64 -113
  29. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  30. package/dist/esm/export.mjs +2 -1
  31. package/dist/esm/index.mjs +101 -41
  32. package/dist/esm/index.mjs.map +1 -1
  33. package/dist/esm/middlewares/oAuth2.middleware.mjs +19 -14
  34. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  35. package/dist/esm/middlewares/sessionAuth.middleware.mjs +6 -7
  36. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  37. package/dist/esm/routes/ai.routes.mjs +19 -15
  38. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  39. package/dist/esm/routes/dictionary.routes.mjs +10 -10
  40. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  41. package/dist/esm/routes/eventListener.routes.mjs +3 -3
  42. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  43. package/dist/esm/routes/github.routes.mjs +43 -0
  44. package/dist/esm/routes/github.routes.mjs.map +1 -0
  45. package/dist/esm/routes/newsletter.routes.mjs +5 -5
  46. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  47. package/dist/esm/routes/organization.routes.mjs +11 -11
  48. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  49. package/dist/esm/routes/project.routes.mjs +13 -13
  50. package/dist/esm/routes/project.routes.mjs.map +1 -1
  51. package/dist/esm/routes/search.routes.mjs +3 -3
  52. package/dist/esm/routes/search.routes.mjs.map +1 -1
  53. package/dist/esm/routes/stripe.routes.mjs +5 -5
  54. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  55. package/dist/esm/routes/tags.routes.mjs +6 -6
  56. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  57. package/dist/esm/routes/user.routes.mjs +9 -9
  58. package/dist/esm/routes/user.routes.mjs.map +1 -1
  59. package/dist/esm/schemas/project.schema.mjs +35 -1
  60. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  61. package/dist/esm/services/email.service.mjs +1 -1
  62. package/dist/esm/services/email.service.mjs.map +1 -1
  63. package/dist/esm/services/github.service.mjs +130 -0
  64. package/dist/esm/services/github.service.mjs.map +1 -0
  65. package/dist/esm/services/oAuth2.service.mjs +1 -1
  66. package/dist/esm/services/subscription.service.mjs +1 -1
  67. package/dist/esm/services/subscription.service.mjs.map +1 -1
  68. package/dist/esm/utils/auth/getAuth.mjs +14 -8
  69. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  70. package/dist/esm/utils/cors.mjs +15 -5
  71. package/dist/esm/utils/cors.mjs.map +1 -1
  72. package/dist/esm/utils/errors/ErrorHandler.mjs +32 -4
  73. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  74. package/dist/esm/utils/errors/ErrorsClass.mjs +1 -1
  75. package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
  76. package/dist/esm/utils/errors/errorCodes.mjs +78 -0
  77. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  78. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs +3 -2
  79. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  80. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs +1 -1
  81. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  82. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs +1 -1
  83. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
  84. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs +3 -2
  85. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  86. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs +3 -2
  87. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  88. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs +3 -2
  89. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  90. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs +3 -2
  91. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  92. package/dist/esm/utils/mapper/project.mjs +28 -1
  93. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  94. package/dist/esm/utils/mongoDB/connectDB.mjs +1 -1
  95. package/dist/esm/utils/rateLimiter.mjs +40 -30
  96. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  97. package/dist/esm/webhooks/stripe.webhook.mjs +2 -2
  98. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  99. package/dist/types/controllers/ai.controller.d.ts +29 -12
  100. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  101. package/dist/types/controllers/dictionary.controller.d.ts +23 -13
  102. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  103. package/dist/types/controllers/eventListener.controller.d.ts +4 -2
  104. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  105. package/dist/types/controllers/github.controller.d.ts +63 -0
  106. package/dist/types/controllers/github.controller.d.ts.map +1 -0
  107. package/dist/types/controllers/newsletter.controller.d.ts +8 -7
  108. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  109. package/dist/types/controllers/oAuth2.controller.d.ts +4 -2
  110. package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
  111. package/dist/types/controllers/organization.controller.d.ts +28 -12
  112. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  113. package/dist/types/controllers/project.controller.d.ts +21 -16
  114. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  115. package/dist/types/controllers/projectAccessKey.controller.d.ts +10 -5
  116. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  117. package/dist/types/controllers/search.controller.d.ts +4 -2
  118. package/dist/types/controllers/search.controller.d.ts.map +1 -1
  119. package/dist/types/controllers/stripe.controller.d.ts +11 -12
  120. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  121. package/dist/types/controllers/tag.controller.d.ts +14 -9
  122. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  123. package/dist/types/controllers/user.controller.d.ts +22 -9
  124. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  125. package/dist/types/emails/InviteUserEmail.d.ts +4 -4
  126. package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
  127. package/dist/types/emails/MagicLinkEmail.d.ts.map +1 -1
  128. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
  129. package/dist/types/emails/PasswordChangeConfirmation.d.ts +4 -4
  130. package/dist/types/emails/PasswordChangeConfirmation.d.ts.map +1 -1
  131. package/dist/types/emails/ResetUserPassword.d.ts +4 -4
  132. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
  133. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
  134. package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
  135. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
  136. package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
  137. package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
  138. package/dist/types/emails/Welcome.d.ts +4 -4
  139. package/dist/types/export.d.ts +6 -4
  140. package/dist/types/middlewares/oAuth2.middleware.d.ts +9 -4
  141. package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
  142. package/dist/types/middlewares/sessionAuth.middleware.d.ts +13 -3
  143. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  144. package/dist/types/models/dictionary.model.d.ts +4 -4
  145. package/dist/types/models/dictionary.model.d.ts.map +1 -1
  146. package/dist/types/models/discussion.model.d.ts +2 -2
  147. package/dist/types/models/discussion.model.d.ts.map +1 -1
  148. package/dist/types/models/oAuth2.model.d.ts +3 -3
  149. package/dist/types/models/oAuth2.model.d.ts.map +1 -1
  150. package/dist/types/routes/ai.routes.d.ts +2 -2
  151. package/dist/types/routes/ai.routes.d.ts.map +1 -1
  152. package/dist/types/routes/dictionary.routes.d.ts +2 -2
  153. package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
  154. package/dist/types/routes/eventListener.routes.d.ts +2 -2
  155. package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
  156. package/dist/types/routes/github.routes.d.ts +35 -0
  157. package/dist/types/routes/github.routes.d.ts.map +1 -0
  158. package/dist/types/routes/newsletter.routes.d.ts +2 -2
  159. package/dist/types/routes/newsletter.routes.d.ts.map +1 -1
  160. package/dist/types/routes/organization.routes.d.ts +2 -2
  161. package/dist/types/routes/organization.routes.d.ts.map +1 -1
  162. package/dist/types/routes/project.routes.d.ts +2 -2
  163. package/dist/types/routes/project.routes.d.ts.map +1 -1
  164. package/dist/types/routes/search.routes.d.ts +2 -2
  165. package/dist/types/routes/search.routes.d.ts.map +1 -1
  166. package/dist/types/routes/stripe.routes.d.ts +2 -2
  167. package/dist/types/routes/stripe.routes.d.ts.map +1 -1
  168. package/dist/types/routes/tags.routes.d.ts +2 -2
  169. package/dist/types/routes/tags.routes.d.ts.map +1 -1
  170. package/dist/types/routes/user.routes.d.ts +2 -2
  171. package/dist/types/routes/user.routes.d.ts.map +1 -1
  172. package/dist/types/schemas/dictionary.schema.d.ts +6 -6
  173. package/dist/types/schemas/discussion.schema.d.ts +6 -6
  174. package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
  175. package/dist/types/schemas/organization.schema.d.ts +6 -6
  176. package/dist/types/schemas/plans.schema.d.ts +6 -6
  177. package/dist/types/schemas/plans.schema.d.ts.map +1 -1
  178. package/dist/types/schemas/project.schema.d.ts +6 -6
  179. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  180. package/dist/types/schemas/session.schema.d.ts +6 -6
  181. package/dist/types/schemas/tag.schema.d.ts +6 -6
  182. package/dist/types/schemas/user.schema.d.ts +6 -6
  183. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  184. package/dist/types/services/email.service.d.ts +11 -11
  185. package/dist/types/services/github.service.d.ts +21 -0
  186. package/dist/types/services/github.service.d.ts.map +1 -0
  187. package/dist/types/types/project.types.d.ts +18 -5
  188. package/dist/types/types/project.types.d.ts.map +1 -1
  189. package/dist/types/types/session.types.d.ts +1 -1
  190. package/dist/types/types/user.types.d.ts +1 -1
  191. package/dist/types/utils/AI/auditTag/index.d.ts +1 -1
  192. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  193. package/dist/types/utils/cors.d.ts +2 -2
  194. package/dist/types/utils/errors/ErrorHandler.d.ts +31 -3
  195. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  196. package/dist/types/utils/errors/ErrorsClass.d.ts +1 -1
  197. package/dist/types/utils/errors/errorCodes.d.ts +78 -0
  198. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  199. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +8 -4
  200. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
  201. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +6 -3
  202. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
  203. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts +6 -2
  204. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
  205. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +8 -4
  206. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
  207. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +8 -4
  208. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
  209. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +8 -4
  210. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
  211. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts +6 -2
  212. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
  213. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  214. package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
  215. package/dist/types/utils/permissions.d.ts +1 -1
  216. package/dist/types/utils/rateLimiter.d.ts +4 -2
  217. package/dist/types/utils/rateLimiter.d.ts.map +1 -1
  218. package/package.json +23 -27
  219. package/dist/esm/middlewares/request.middleware.mjs +0 -17
  220. package/dist/esm/middlewares/request.middleware.mjs.map +0 -1
  221. package/dist/types/middlewares/request.middleware.d.ts +0 -7
  222. package/dist/types/middlewares/request.middleware.d.ts.map +0 -1
@@ -7,7 +7,7 @@ import { sendDictionaryUpdate } from "./eventListener.controller.mjs";
7
7
  import { getDictionaryFiltersAndPagination } from "../utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs";
8
8
  import { mapDictionaryToAPI } from "../utils/mapper/dictionary.mjs";
9
9
  import { hasPermission } from "../utils/permissions.mjs";
10
- import { t } from "express-intlayer";
10
+ import { t } from "fastify-intlayer";
11
11
  import { isDeepStrictEqual } from "node:util";
12
12
 
13
13
  //#region src/controllers/dictionary.controller.ts
@@ -23,29 +23,20 @@ const removeMetadata = (obj) => {
23
23
  /**
24
24
  * Retrieves a list of dictionaries based on filters and pagination.
25
25
  */
26
- const getDictionaries = async (req, res, _next) => {
27
- const { user, project, roles } = res.locals;
28
- const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getDictionaryFiltersAndPagination(req, res);
29
- if (!project) {
30
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
31
- return;
32
- }
33
- if (!user) {
34
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
35
- return;
36
- }
26
+ const getDictionaries = async (request, reply) => {
27
+ const { user, project, roles } = request.locals || {};
28
+ const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getDictionaryFiltersAndPagination(request);
29
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
30
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
37
31
  try {
38
32
  const dictionaries = await findDictionaries({
39
33
  ...filters,
40
34
  projectIds: project.id
41
35
  }, skip, pageSize, sortOptions);
42
- if (!hasPermission(roles, "dictionary:read")({
43
- ...res.locals,
36
+ if (!hasPermission(roles || [], "dictionary:read")({
37
+ ...request.locals,
44
38
  targetDictionaries: dictionaries
45
- })) {
46
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
47
- return;
48
- }
39
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
49
40
  const totalItems = await countDictionaries(filters);
50
41
  const responseData = formatPaginatedResponse({
51
42
  data: dictionaries.map((el) => mapDictionaryToAPI(el)),
@@ -54,129 +45,84 @@ const getDictionaries = async (req, res, _next) => {
54
45
  totalPages: getNumberOfPages(totalItems),
55
46
  totalItems
56
47
  });
57
- res.json(responseData);
58
- return;
48
+ return reply.send(responseData);
59
49
  } catch (error) {
60
- ErrorHandler.handleAppErrorResponse(res, error);
61
- return;
50
+ return ErrorHandler.handleAppErrorResponse(reply, error);
62
51
  }
63
52
  };
64
53
  /**
65
54
  * Retrieves a list of dictionaries keys based on filters and pagination.
66
55
  */
67
- const getDictionariesKeys = async (_req, res, _next) => {
68
- const { project, roles } = res.locals;
69
- if (!project) {
70
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
71
- return;
72
- }
56
+ const getDictionariesKeys = async (_request, reply) => {
57
+ const { project, roles } = _request.locals || {};
58
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
73
59
  try {
74
60
  const dictionaries = await findDictionaries({ projectIds: project.id });
75
- if (!hasPermission(roles, "dictionary:read")({
76
- ...res.locals,
61
+ if (!hasPermission(roles || [], "dictionary:read")({
62
+ ..._request.locals,
77
63
  targetDictionaries: dictionaries
78
- })) {
79
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
80
- return;
81
- }
64
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
82
65
  const responseData = formatResponse({ data: dictionaries.map((dictionary) => dictionary.key) });
83
- res.json(responseData);
84
- return;
66
+ return reply.send(responseData);
85
67
  } catch (error) {
86
- ErrorHandler.handleAppErrorResponse(res, error);
87
- return;
68
+ return ErrorHandler.handleAppErrorResponse(reply, error);
88
69
  }
89
70
  };
90
71
  /**
91
72
  * Retrieves a list of dictionaries keys based on filters and pagination.
92
73
  */
93
- const getDictionariesUpdateTimestamp = async (_req, res, _next) => {
94
- const { project, roles } = res.locals;
95
- if (!project) {
96
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
97
- return;
98
- }
74
+ const getDictionariesUpdateTimestamp = async (_request, reply) => {
75
+ const { project, roles } = _request.locals || {};
76
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
99
77
  try {
100
78
  const dictionaries = await findDictionaries({ projectIds: project.id });
101
- if (!hasPermission(roles, "dictionary:read")({
102
- ...res.locals,
79
+ if (!hasPermission(roles || [], "dictionary:read")({
80
+ ..._request.locals,
103
81
  targetDictionaries: dictionaries
104
- })) {
105
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
106
- return;
107
- }
108
- const responseData = formatResponse({ data: dictionaries.reduce((acc, dictionary) => ({
109
- ...acc,
110
- [dictionary.id]: {
111
- key: dictionary.key,
112
- updatedAt: new Date(dictionary.updatedAt).getTime()
113
- }
114
- }), {}) });
115
- res.json(responseData);
116
- return;
82
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
83
+ const dictionariesUpdateTimestamp = {};
84
+ for (const dictionary of dictionaries) dictionariesUpdateTimestamp[dictionary.id] = {
85
+ key: dictionary.key,
86
+ updatedAt: new Date(dictionary.updatedAt).getTime()
87
+ };
88
+ const responseData = formatResponse({ data: dictionariesUpdateTimestamp });
89
+ return reply.send(responseData);
117
90
  } catch (error) {
118
- ErrorHandler.handleAppErrorResponse(res, error);
119
- return;
91
+ return ErrorHandler.handleAppErrorResponse(reply, error);
120
92
  }
121
93
  };
122
94
  /**
123
95
  * Retrieves a list of dictionaries based on filters and pagination.
124
96
  */
125
- const getDictionaryByKey = async (req, res, _next) => {
126
- const { project, user, roles } = res.locals;
127
- const { dictionaryKey } = req.params;
128
- const version = req.query.version;
129
- if (!project) {
130
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
131
- return;
132
- }
133
- if (!user) {
134
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
135
- return;
136
- }
97
+ const getDictionaryByKey = async (request, reply) => {
98
+ const { project, user, roles } = request.locals || {};
99
+ const { dictionaryKey } = request.params;
100
+ const version = request.query.version;
101
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
102
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
137
103
  try {
138
104
  const dictionary = await getDictionaryByKey$1(dictionaryKey, project.id);
139
- if (!hasPermission(roles, "dictionary:read")({
140
- ...res.locals,
105
+ if (!hasPermission(roles || [], "dictionary:read")({
106
+ ...request.locals,
141
107
  targetDictionaries: [dictionary]
142
- })) {
143
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
144
- return;
145
- }
146
- if (!dictionary.projectIds.map(String).includes(String(project.id))) {
147
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_PROJECT_MISMATCH");
148
- return;
149
- }
108
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
109
+ if (!dictionary.projectIds.map(String).includes(String(project.id))) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_PROJECT_MISMATCH");
150
110
  const responseData = formatResponse({ data: mapDictionaryToAPI(dictionary, version) });
151
- res.json(responseData);
152
- return;
111
+ return reply.send(responseData);
153
112
  } catch (error) {
154
- ErrorHandler.handleAppErrorResponse(res, error);
155
- return;
113
+ return ErrorHandler.handleAppErrorResponse(reply, error);
156
114
  }
157
115
  };
158
116
  /**
159
117
  * Adds a new dictionary to the database.
160
118
  */
161
- const addDictionary = async (req, res, _next) => {
162
- const { project, user, roles } = res.locals;
163
- const dictionaryData = req.body.dictionary;
164
- if (!dictionaryData) {
165
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_DATA_NOT_FOUND");
166
- return;
167
- }
168
- if (!project) {
169
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
170
- return;
171
- }
172
- if (!user) {
173
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
174
- return;
175
- }
176
- if (!dictionaryData.projectIds?.includes(String(project.id))) {
177
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_PROJECT_MISMATCH");
178
- return;
179
- }
119
+ const addDictionary = async (request, reply) => {
120
+ const { project, user, roles } = request.locals || {};
121
+ const dictionaryData = request.body.dictionary;
122
+ if (!dictionaryData) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_DATA_NOT_FOUND");
123
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
124
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
125
+ if (!dictionaryData.projectIds?.includes(String(project.id))) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_PROJECT_MISMATCH");
180
126
  const dictionary = {
181
127
  key: dictionaryData.key,
182
128
  title: dictionaryData.title,
@@ -185,10 +131,7 @@ const addDictionary = async (req, res, _next) => {
185
131
  creatorId: user.id,
186
132
  projectIds: dictionaryData.projectIds ?? [String(project.id)]
187
133
  };
188
- if (!hasPermission(roles, "dictionary:write")(res.locals)) {
189
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
190
- return;
191
- }
134
+ if (!hasPermission(roles || [], "dictionary:write")(request.locals)) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
192
135
  try {
193
136
  const newDictionary = await createDictionary(dictionary);
194
137
  const apiResult = mapDictionaryToAPI(newDictionary);
@@ -205,46 +148,27 @@ const addDictionary = async (req, res, _next) => {
205
148
  }),
206
149
  data: apiResult
207
150
  });
208
- res.json(responseData);
209
151
  sendDictionaryUpdate([{
210
152
  dictionary: mapDictionaryToAPI(newDictionary),
211
153
  status: "ADDED"
212
154
  }]);
213
- return;
155
+ return reply.send(responseData);
214
156
  } catch (error) {
215
- ErrorHandler.handleAppErrorResponse(res, error);
216
- return;
157
+ return ErrorHandler.handleAppErrorResponse(reply, error);
217
158
  }
218
159
  };
219
160
  /**
220
161
  * Check each dictionaries, add the new ones and update the existing ones.
221
- * @param req - Express request object.
222
- * @param res - Express response object.
223
- * @returns Response containing the created dictionary.
224
162
  */
225
- const pushDictionaries = async (req, res, _next) => {
226
- const { project, user, roles } = res.locals;
227
- let dictionaryData = req.body.dictionaries;
163
+ const pushDictionaries = async (request, reply) => {
164
+ const { project, user, roles } = request.locals || {};
165
+ let dictionaryData = request.body.dictionaries;
228
166
  if (dictionaryData && !Array.isArray(dictionaryData) && typeof dictionaryData === "object" && "dictionaries" in dictionaryData && Array.isArray(dictionaryData.dictionaries)) dictionaryData = dictionaryData.dictionaries;
229
- if (typeof dictionaryData === "object" && Array.isArray(dictionaryData) && dictionaryData.length === 0) {
230
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARIES_NOT_PROVIDED");
231
- return;
232
- } else if (!dictionaryData) {
233
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_DATA_NOT_FOUND");
234
- return;
235
- }
236
- if (!project) {
237
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
238
- return;
239
- }
240
- if (!user) {
241
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
242
- return;
243
- }
244
- if (!hasPermission(roles, "dictionary:write")(res.locals)) {
245
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
246
- return;
247
- }
167
+ if (typeof dictionaryData === "object" && Array.isArray(dictionaryData) && dictionaryData.length === 0) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARIES_NOT_PROVIDED");
168
+ else if (!dictionaryData) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_DATA_NOT_FOUND");
169
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
170
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
171
+ if (!hasPermission(roles || [], "dictionary:write")(request.locals)) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
248
172
  try {
249
173
  const existingDictionaries = dictionaryData.filter((dictionary) => dictionary.id !== void 0);
250
174
  const newDictionaries = dictionaryData.filter((dictionary) => dictionary.id === void 0);
@@ -336,40 +260,23 @@ const pushDictionaries = async (req, res, _next) => {
336
260
  dictionary,
337
261
  status: "UPDATED"
338
262
  }))]);
339
- res.json(responseData);
340
- return;
263
+ return reply.send(responseData);
341
264
  } catch (error) {
342
- ErrorHandler.handleAppErrorResponse(res, error);
343
- return;
265
+ return ErrorHandler.handleAppErrorResponse(reply, error);
344
266
  }
345
267
  };
346
268
  /**
347
269
  * Updates an existing dictionary in the database.
348
270
  */
349
- const updateDictionary = async (req, res, _next) => {
350
- const { dictionaryId } = req.params;
351
- const { project, roles } = res.locals;
352
- const dictionaryData = req.body;
353
- if (!dictionaryData) {
354
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_DATA_NOT_FOUND");
355
- return;
356
- }
357
- if (!project) {
358
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
359
- return;
360
- }
361
- if (!dictionaryData.projectIds?.includes(String(project.id))) {
362
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_PROJECT_MISMATCH");
363
- return;
364
- }
365
- if (typeof dictionaryId === "undefined") {
366
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_ID_NOT_FOUND");
367
- return;
368
- }
369
- if (!hasPermission(roles, "dictionary:write")(res.locals)) {
370
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
371
- return;
372
- }
271
+ const updateDictionary = async (request, reply) => {
272
+ const { dictionaryId } = request.params;
273
+ const { project, roles } = request.locals || {};
274
+ const dictionaryData = request.body;
275
+ if (!dictionaryData) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_DATA_NOT_FOUND");
276
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
277
+ if (!dictionaryData.projectIds?.includes(String(project.id))) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_PROJECT_MISMATCH");
278
+ if (typeof dictionaryId === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_ID_NOT_FOUND");
279
+ if (!hasPermission(roles || [], "dictionary:write")(request.locals)) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
373
280
  try {
374
281
  const apiResult = mapDictionaryToAPI(await updateDictionaryById(dictionaryId, dictionaryData));
375
282
  const responseData = formatResponse({
@@ -389,41 +296,24 @@ const updateDictionary = async (req, res, _next) => {
389
296
  dictionary: apiResult,
390
297
  status: "UPDATED"
391
298
  }]);
392
- res.json(responseData);
393
- return;
299
+ return reply.send(responseData);
394
300
  } catch (error) {
395
- ErrorHandler.handleAppErrorResponse(res, error);
396
- return;
301
+ return ErrorHandler.handleAppErrorResponse(reply, error);
397
302
  }
398
303
  };
399
304
  /**
400
305
  * Deletes a dictionary from the database by its ID.
401
306
  */
402
- const deleteDictionary = async (req, res, _next) => {
403
- const { project, roles } = res.locals;
404
- const { dictionaryId } = req.params;
405
- if (!dictionaryId) {
406
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_ID_NOT_FOUND");
407
- return;
408
- }
409
- if (!project) {
410
- ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
411
- return;
412
- }
413
- if (!hasPermission(roles, "dictionary:admin")(res.locals)) {
414
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
415
- return;
416
- }
307
+ const deleteDictionary = async (request, reply) => {
308
+ const { project, roles } = request.locals || {};
309
+ const { dictionaryId } = request.params;
310
+ if (!dictionaryId) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_ID_NOT_FOUND");
311
+ if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
312
+ if (!hasPermission(roles || [], "dictionary:admin")(request.locals)) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
417
313
  try {
418
- if (!(await getDictionaryById(dictionaryId)).projectIds.includes(project.id)) {
419
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_PROJECT_MISMATCH");
420
- return;
421
- }
314
+ if (!(await getDictionaryById(dictionaryId)).projectIds.includes(project.id)) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_PROJECT_MISMATCH");
422
315
  const deletedDictionary = await deleteDictionaryById(dictionaryId);
423
- if (!deletedDictionary) {
424
- ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_NOT_FOUND", { dictionaryId });
425
- return;
426
- }
316
+ if (!deletedDictionary) return ErrorHandler.handleGenericErrorResponse(reply, "DICTIONARY_NOT_FOUND", { dictionaryId });
427
317
  logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);
428
318
  const apiResult = mapDictionaryToAPI(deletedDictionary);
429
319
  const responseData = formatResponse({
@@ -439,15 +329,13 @@ const deleteDictionary = async (req, res, _next) => {
439
329
  }),
440
330
  data: apiResult
441
331
  });
442
- res.json(responseData);
443
332
  sendDictionaryUpdate([{
444
333
  dictionary: apiResult,
445
334
  status: "DELETED"
446
335
  }]);
447
- return;
336
+ return reply.send(responseData);
448
337
  } catch (error) {
449
- ErrorHandler.handleAppErrorResponse(res, error);
450
- return;
338
+ return ErrorHandler.handleAppErrorResponse(reply, error);
451
339
  }
452
340
  };
453
341
 
@@ -1 +1 @@
1
- {"version":3,"file":"dictionary.controller.mjs","names":["clone: T","dictionaryService.findDictionaries","dictionaryService.countDictionaries","dictionaryService.getDictionaryByKey","dictionary: DictionaryData","dictionaryService.createDictionary","newDictionariesResult: PushDictionariesResultData['newDictionaries']","updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries']","errorResult: PushDictionariesResultData['error']","dictionaryService.getDictionaryById","newContent: VersionedContent","dictionaryService.incrementVersion","dictionaryService.updateDictionaryByKey","result: PushDictionariesResultData","dictionaryService.updateDictionaryById","dictionaryService.deleteDictionaryById"],"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import { isDeepStrictEqual } from 'node:util';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n DictionaryId,\n Dictionary as LocalDictionary,\n LocalDictionaryId,\n} from '@intlayer/types';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as dictionaryService from '@services/dictionary.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\nimport type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\nconst removeMetadata = <T extends Record<string, any>>(obj: T): T => {\n if (Array.isArray(obj)) {\n return obj.map(removeMetadata) as unknown as T;\n }\n\n if (obj && typeof obj === 'object') {\n const clone: T = {} as T;\n for (const key in obj) {\n if (key !== 'metadata') {\n clone[key] = removeMetadata(obj[key]);\n }\n }\n return clone as T;\n }\n\n return obj as T;\n};\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n req: Request<GetDictionariesParams>,\n res: ResponseWithSession<GetDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(req, res);\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) => mapDictionaryToAPI(el));\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _req: Request,\n res: ResponseWithSession<GetDictionariesKeysResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesUpdateTimestampResult = ResponseData<\n Record<DictionaryId, { key: string; updatedAt: number }>\n>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesUpdateTimestamp = async (\n _req: Request,\n res: ResponseWithSession<GetDictionariesUpdateTimestampResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionariesUpdateTimestamp = dictionaries.reduce(\n (acc, dictionary) => ({\n ...acc,\n [dictionary.id]: {\n key: dictionary.key,\n updatedAt: new Date(dictionary.updatedAt).getTime(),\n },\n }),\n {}\n );\n\n const responseData = formatResponse<\n Record<string, { key: string; updatedAt: number }>\n >({\n data: dictionariesUpdateTimestamp,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n req: Request<GetDictionaryParams, any, any, GetDictionaryQuery>,\n res: ResponseWithSession<GetDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const { dictionaryKey } = req.params;\n const version = req.query.version;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n req: Request<any, any, AddDictionaryBody>,\n res: ResponseWithSession<AddDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionary;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n [\n 'v1',\n {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: removeMetadata(dictionaryData.content ?? {}) as ContentNode,\n },\n ],\n ]),\n creatorId: user.id,\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n updatedDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n error: {\n id: string | undefined;\n key: string;\n localId: LocalDictionaryId | undefined;\n message: string;\n }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response containing the created dictionary.\n */\nexport const pushDictionaries = async (\n req: Request<any, any, PushDictionariesBody>,\n res: ResponseWithSession<PushDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n\n // Normalize the input: handle both { dictionaries: [...] } and { dictionaries: { dictionaries: [...] } }\n // The latter can happen due to client-side double-wrapping issues\n let dictionaryData = req.body.dictionaries;\n if (\n dictionaryData &&\n !Array.isArray(dictionaryData) &&\n typeof dictionaryData === 'object' &&\n 'dictionaries' in dictionaryData &&\n Array.isArray(\n (dictionaryData as unknown as PushDictionariesBody).dictionaries\n )\n ) {\n dictionaryData = (dictionaryData as unknown as PushDictionariesBody)\n .dictionaries;\n }\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARIES_NOT_PROVIDED');\n return;\n } else if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const existingDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id !== undefined\n );\n const newDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id === undefined\n );\n\n const newDictionariesResult: PushDictionariesResultData['newDictionaries'] =\n [];\n const updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries'] =\n [];\n const errorResult: PushDictionariesResultData['error'] = [];\n\n for (const dictionaryDataEl of newDictionaries) {\n const dictionary: DictionaryData = {\n title: dictionaryDataEl.title,\n description: dictionaryDataEl.description,\n projectIds: [String(project.id)],\n creatorId: user.id,\n content: new Map([\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n\n [\n 'v1',\n {\n content:\n removeMetadata(dictionaryDataEl.content) ?? ({} as ContentNode),\n },\n ],\n ]),\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push({\n key: newDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: newDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n for (const dictionaryDataEl of existingDictionaries) {\n const remoteDictionary = await dictionaryService.getDictionaryById(\n dictionaryDataEl.id!\n );\n\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n const cleanedContent = removeMetadata(dictionaryDataEl.content);\n\n const versionList = [...(remoteDictionary.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (remoteDictionary.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent = isDeepStrictEqual(lastContent, cleanedContent);\n\n const newContent: VersionedContent = new Map(remoteDictionary.content);\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(remoteDictionary);\n\n newContent.set(newContentVersion, {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: cleanedContent,\n });\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(remoteDictionary),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n key: remoteDictionary.key,\n };\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryByKey(\n remoteDictionary.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push({\n key: updatedDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: updatedDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult,\n updatedDictionaries: updatedDictionariesResult,\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n req: Request<UpdateDictionaryParam, any, UpdateDictionaryBody>,\n res: ResponseWithSession<UpdateDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { dictionaryId } = req.params;\n const { project, roles } = res.locals;\n const dictionaryData = req.body;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n if (typeof dictionaryId === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n req: Request<DeleteDictionaryParam>,\n res: ResponseWithSession<DeleteDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, roles } = res.locals;\n const { dictionaryId } = req.params as Partial<DeleteDictionaryParam>;\n\n if (!dictionaryId) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:admin')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_NOT_FOUND', {\n dictionaryId,\n });\n return;\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dictionary.controller.mjs","names":["clone: T","dictionaryService.findDictionaries","dictionaryService.countDictionaries","dictionariesUpdateTimestamp: Record<\n string,\n { key: string; updatedAt: number }\n >","dictionaryService.getDictionaryByKey","dictionary: DictionaryData","dictionaryService.createDictionary","newDictionariesResult: PushDictionariesResultData['newDictionaries']","updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries']","errorResult: PushDictionariesResultData['error']","dictionaryService.getDictionaryById","newContent: VersionedContent","dictionaryService.incrementVersion","dictionaryService.updateDictionaryByKey","result: PushDictionariesResultData","dictionaryService.updateDictionaryById","dictionaryService.deleteDictionaryById"],"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import { isDeepStrictEqual } from 'node:util';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n DictionaryId,\n Dictionary as LocalDictionary,\n LocalDictionaryId,\n} from '@intlayer/types';\nimport { logger } from '@logger';\nimport * as dictionaryService from '@services/dictionary.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\nconst removeMetadata = <T extends Record<string, any>>(obj: T): T => {\n if (Array.isArray(obj)) {\n return obj.map(removeMetadata) as unknown as T;\n }\n\n if (obj && typeof obj === 'object') {\n const clone: T = {} as T;\n for (const key in obj) {\n if (key !== 'metadata') {\n clone[key] = removeMetadata(obj[key]);\n }\n }\n return clone as T;\n }\n\n return obj as T;\n};\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n request: FastifyRequest<{ Querystring: GetDictionariesParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(request);\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) => mapDictionaryToAPI(el));\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { project, roles } = _request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ..._request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionariesUpdateTimestampResult = ResponseData<\n Record<DictionaryId, { key: string; updatedAt: number }>\n>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesUpdateTimestamp = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { project, roles } = _request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ..._request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const dictionariesUpdateTimestamp: Record<\n string,\n { key: string; updatedAt: number }\n > = {};\n for (const dictionary of dictionaries) {\n dictionariesUpdateTimestamp[dictionary.id] = {\n key: dictionary.key,\n updatedAt: new Date(dictionary.updatedAt).getTime(),\n };\n }\n\n const responseData = formatResponse<\n Record<string, { key: string; updatedAt: number }>\n >({\n data: dictionariesUpdateTimestamp,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n request: FastifyRequest<{\n Params: GetDictionaryParams;\n Querystring: GetDictionaryQuery;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n const { dictionaryKey } = request.params;\n const version = request.query.version;\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n request: FastifyRequest<{ Body: AddDictionaryBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n const dictionaryData = request.body.dictionary;\n\n if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n [\n 'v1',\n {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: removeMetadata(dictionaryData.content ?? {}) as ContentNode,\n },\n ],\n ]),\n creatorId: user.id,\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n updatedDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n error: {\n id: string | undefined;\n key: string;\n localId: LocalDictionaryId | undefined;\n message: string;\n }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n */\nexport const pushDictionaries = async (\n request: FastifyRequest<{ Body: PushDictionariesBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n\n // Normalize the input: handle both { dictionaries: [...] } and { dictionaries: { dictionaries: [...] } }\n // The latter can happen due to client-side double-wrapping issues\n let dictionaryData = request.body.dictionaries;\n if (\n dictionaryData &&\n !Array.isArray(dictionaryData) &&\n typeof dictionaryData === 'object' &&\n 'dictionaries' in dictionaryData &&\n Array.isArray(\n (dictionaryData as unknown as PushDictionariesBody).dictionaries\n )\n ) {\n dictionaryData = (dictionaryData as unknown as PushDictionariesBody)\n .dictionaries;\n }\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARIES_NOT_PROVIDED'\n );\n } else if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id !== undefined\n );\n const newDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id === undefined\n );\n\n const newDictionariesResult: PushDictionariesResultData['newDictionaries'] =\n [];\n const updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries'] =\n [];\n const errorResult: PushDictionariesResultData['error'] = [];\n\n for (const dictionaryDataEl of newDictionaries) {\n const dictionary: DictionaryData = {\n title: dictionaryDataEl.title,\n description: dictionaryDataEl.description,\n projectIds: [String(project.id)],\n creatorId: user.id,\n content: new Map([\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n\n [\n 'v1',\n {\n content:\n removeMetadata(dictionaryDataEl.content) ?? ({} as ContentNode),\n },\n ],\n ]),\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push({\n key: newDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: newDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n for (const dictionaryDataEl of existingDictionaries) {\n const remoteDictionary = await dictionaryService.getDictionaryById(\n dictionaryDataEl.id!\n );\n\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n const cleanedContent = removeMetadata(dictionaryDataEl.content);\n\n const versionList = [...(remoteDictionary.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (remoteDictionary.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent = isDeepStrictEqual(lastContent, cleanedContent);\n\n const newContent: VersionedContent = new Map(remoteDictionary.content);\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(remoteDictionary);\n\n newContent.set(newContentVersion, {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: cleanedContent,\n });\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(remoteDictionary),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n key: remoteDictionary.key,\n };\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryByKey(\n remoteDictionary.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push({\n key: updatedDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: updatedDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult,\n updatedDictionaries: updatedDictionariesResult,\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n request: FastifyRequest<{\n Params: UpdateDictionaryParam;\n Body: UpdateDictionaryBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { dictionaryId } = request.params;\n const { project, roles } = request.locals || {};\n const dictionaryData = request.body;\n\n if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n if (typeof dictionaryId === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_ID_NOT_FOUND'\n );\n }\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n request: FastifyRequest<{ Params: DeleteDictionaryParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n const { dictionaryId } = request.params;\n\n if (!dictionaryId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_ID_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!hasPermission(roles || [], 'dictionary:admin')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_NOT_FOUND',\n {\n dictionaryId,\n }\n );\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
@@ -13,36 +13,30 @@ const sendDictionaryUpdate = (args) => {
13
13
  data: arg.dictionary
14
14
  }));
15
15
  process.nextTick(() => {
16
- for (const client of filteredClients) {
17
- client.res.write(`data: ${JSON.stringify(data)}\n\n`);
18
- client.res.flush?.();
19
- }
16
+ for (const client of filteredClients) client.res.raw.write(`data: ${JSON.stringify(data)}\n\n`);
20
17
  });
21
18
  };
22
19
  /**
23
20
  * SSE to check the email verification status
24
21
  */
25
- const listenChangeSSE = async (req, res) => {
26
- const { project } = res.locals;
27
- if (clients.length >= MAX_SSE_CONNECTIONS) {
28
- ErrorHandler.handleGenericErrorResponse(res, "TOO_MANY_CONNECTIONS");
29
- return;
30
- }
31
- res.setHeader("Content-Type", "text/event-stream;charset=utf-8");
32
- res.setHeader("Cache-Control", "no-cache, no-transform");
33
- res.setHeader("Connection", "keep-alive");
34
- res.setHeader("X-Accel-Buffering", "no");
35
- res.write(":\n\n");
36
- res.flushHeaders?.();
22
+ const listenChangeSSE = async (request, reply) => {
23
+ const { project } = request.locals || {};
24
+ if (clients.length >= MAX_SSE_CONNECTIONS) return ErrorHandler.handleGenericErrorResponse(reply, "TOO_MANY_CONNECTIONS");
25
+ reply.raw.setHeader("Content-Type", "text/event-stream;charset=utf-8");
26
+ reply.raw.setHeader("Cache-Control", "no-cache, no-transform");
27
+ reply.raw.setHeader("Connection", "keep-alive");
28
+ reply.raw.setHeader("X-Accel-Buffering", "no");
29
+ reply.raw.write(":\n\n");
30
+ reply.raw.flushHeaders?.();
37
31
  const clientId = Date.now();
38
32
  const newClient = {
39
33
  id: clientId,
40
- projectId: String(project.id),
41
- res
34
+ projectId: String(project?.id),
35
+ res: { raw: reply.raw }
42
36
  };
43
37
  clients.push(newClient);
44
38
  logger.info(`New client connected to SSE. Total clients: ${clients.length ?? 0}`);
45
- req.on("close", () => {
39
+ request.raw.on("close", () => {
46
40
  clients = clients.filter((client) => client.id !== clientId);
47
41
  });
48
42
  };
@@ -1 +1 @@
1
- {"version":3,"file":"eventListener.controller.mjs","names":["clients: Array<{ id: number; projectId: string; res: Response }>","data: MessageEventData[]"],"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { ErrorHandler } from '@utils/errors';\nimport type { Request, Response } from 'express';\nimport type { DictionaryAPI } from '@/types/dictionary.types';\n\nexport type Object = 'DICTIONARY';\nexport type Status = 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n\nexport type MessageEventData = {\n object: Object;\n status: Status;\n data: any;\n};\n\nlet clients: Array<{ id: number; projectId: string; res: Response }> = [];\nconst MAX_SSE_CONNECTIONS = 10;\n\nexport type SendDictionaryUpdateArg = {\n dictionary: DictionaryAPI;\n status: 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n};\n\nexport const sendDictionaryUpdate = (args: SendDictionaryUpdateArg[]) => {\n const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);\n\n const filteredClients = clients.filter((client) =>\n projectIds.map((id) => String(id)).includes(String(client.projectId))\n );\n\n const data: MessageEventData[] = args.map((arg) => ({\n object: 'DICTIONARY',\n status: arg.status,\n data: arg.dictionary,\n }));\n\n process.nextTick(() => {\n for (const client of filteredClients) {\n client.res.write(`data: ${JSON.stringify(data)}\\n\\n`);\n client.res.flush?.(); // Ensure the data is sent immediately\n }\n });\n};\n\nexport type CheckDictionaryChangeSSEParams = { accessToken: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const listenChangeSSE = async (\n req: Request<CheckDictionaryChangeSSEParams, any, any>,\n res: Response\n) => {\n const { project } = res.locals;\n\n if (clients.length >= MAX_SSE_CONNECTIONS) {\n ErrorHandler.handleGenericErrorResponse(res, 'TOO_MANY_CONNECTIONS');\n return;\n }\n\n // Set headers for SSE\n res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n res.write(':\\n\\n'); // Comment to keep connection alive\n res.flushHeaders?.();\n\n const clientId = Date.now();\n\n // Add client to the list\n const newClient = {\n id: clientId,\n projectId: String(project.id),\n res,\n };\n clients.push(newClient);\n\n logger.info(\n `New client connected to SSE. Total clients: ${clients.length ?? 0}`\n );\n\n // Remove client on connection close\n req.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":";;;;AAcA,IAAIA,UAAmE,EAAE;AACzE,MAAM,sBAAsB;AAO5B,MAAa,wBAAwB,SAAoC;CACvE,MAAM,aAAa,KAAK,SAAS,QAAQ,IAAI,WAAW,WAAW;CAEnE,MAAM,kBAAkB,QAAQ,QAAQ,WACtC,WAAW,KAAK,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,OAAO,OAAO,UAAU,CAAC,CACtE;CAED,MAAMC,OAA2B,KAAK,KAAK,SAAS;EAClD,QAAQ;EACR,QAAQ,IAAI;EACZ,MAAM,IAAI;EACX,EAAE;AAEH,SAAQ,eAAe;AACrB,OAAK,MAAM,UAAU,iBAAiB;AACpC,UAAO,IAAI,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC,MAAM;AACrD,UAAO,IAAI,SAAS;;GAEtB;;;;;AAQJ,MAAa,kBAAkB,OAC7B,KACA,QACG;CACH,MAAM,EAAE,YAAY,IAAI;AAExB,KAAI,QAAQ,UAAU,qBAAqB;AACzC,eAAa,2BAA2B,KAAK,uBAAuB;AACpE;;AAIF,KAAI,UAAU,gBAAgB,kCAAkC;AAChE,KAAI,UAAU,iBAAiB,yBAAyB;AACxD,KAAI,UAAU,cAAc,aAAa;AACzC,KAAI,UAAU,qBAAqB,KAAK;AAGxC,KAAI,MAAM,QAAQ;AAClB,KAAI,gBAAgB;CAEpB,MAAM,WAAW,KAAK,KAAK;CAG3B,MAAM,YAAY;EAChB,IAAI;EACJ,WAAW,OAAO,QAAQ,GAAG;EAC7B;EACD;AACD,SAAQ,KAAK,UAAU;AAEvB,QAAO,KACL,+CAA+C,QAAQ,UAAU,IAClE;AAGD,KAAI,GAAG,eAAe;AACpB,YAAU,QAAQ,QAAQ,WAAW,OAAO,OAAO,SAAS;GAC5D"}
1
+ {"version":3,"file":"eventListener.controller.mjs","names":["clients: Array<{\n id: number;\n projectId: string;\n res: { raw: FastifyReply['raw'] };\n}>","data: MessageEventData[]"],"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { ErrorHandler } from '@utils/errors';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport type { DictionaryAPI } from '@/types/dictionary.types';\n\nexport type Object = 'DICTIONARY';\nexport type Status = 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n\nexport type MessageEventData = {\n object: Object;\n status: Status;\n data: any;\n};\n\nlet clients: Array<{\n id: number;\n projectId: string;\n res: { raw: FastifyReply['raw'] };\n}> = [];\nconst MAX_SSE_CONNECTIONS = 10;\n\nexport type SendDictionaryUpdateArg = {\n dictionary: DictionaryAPI;\n status: 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n};\n\nexport const sendDictionaryUpdate = (args: SendDictionaryUpdateArg[]) => {\n const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);\n\n const filteredClients = clients.filter((client) =>\n projectIds.map((id) => String(id)).includes(String(client.projectId))\n );\n\n const data: MessageEventData[] = args.map((arg) => ({\n object: 'DICTIONARY',\n status: arg.status,\n data: arg.dictionary,\n }));\n\n process.nextTick(() => {\n for (const client of filteredClients) {\n client.res.raw.write(`data: ${JSON.stringify(data)}\\n\\n`);\n }\n });\n};\n\nexport type CheckDictionaryChangeSSEParams = { accessToken: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const listenChangeSSE = async (\n request: FastifyRequest<{ Params: CheckDictionaryChangeSSEParams }>,\n reply: FastifyReply\n) => {\n const { project } = request.locals || {};\n\n if (clients.length >= MAX_SSE_CONNECTIONS) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TOO_MANY_CONNECTIONS'\n );\n }\n\n // Set headers for SSE\n reply.raw.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n reply.raw.write(':\\n\\n'); // Comment to keep connection alive\n reply.raw.flushHeaders?.();\n\n const clientId = Date.now();\n\n // Add client to the list\n const newClient = {\n id: clientId,\n projectId: String(project?.id),\n res: { raw: reply.raw },\n };\n clients.push(newClient);\n\n logger.info(\n `New client connected to SSE. Total clients: ${clients.length ?? 0}`\n );\n\n // Remove client on connection close\n request.raw.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":";;;;AAcA,IAAIA,UAIC,EAAE;AACP,MAAM,sBAAsB;AAO5B,MAAa,wBAAwB,SAAoC;CACvE,MAAM,aAAa,KAAK,SAAS,QAAQ,IAAI,WAAW,WAAW;CAEnE,MAAM,kBAAkB,QAAQ,QAAQ,WACtC,WAAW,KAAK,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,OAAO,OAAO,UAAU,CAAC,CACtE;CAED,MAAMC,OAA2B,KAAK,KAAK,SAAS;EAClD,QAAQ;EACR,QAAQ,IAAI;EACZ,MAAM,IAAI;EACX,EAAE;AAEH,SAAQ,eAAe;AACrB,OAAK,MAAM,UAAU,gBACnB,QAAO,IAAI,IAAI,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC,MAAM;GAE3D;;;;;AAQJ,MAAa,kBAAkB,OAC7B,SACA,UACG;CACH,MAAM,EAAE,YAAY,QAAQ,UAAU,EAAE;AAExC,KAAI,QAAQ,UAAU,oBACpB,QAAO,aAAa,2BAClB,OACA,uBACD;AAIH,OAAM,IAAI,UAAU,gBAAgB,kCAAkC;AACtE,OAAM,IAAI,UAAU,iBAAiB,yBAAyB;AAC9D,OAAM,IAAI,UAAU,cAAc,aAAa;AAC/C,OAAM,IAAI,UAAU,qBAAqB,KAAK;AAG9C,OAAM,IAAI,MAAM,QAAQ;AACxB,OAAM,IAAI,gBAAgB;CAE1B,MAAM,WAAW,KAAK,KAAK;CAG3B,MAAM,YAAY;EAChB,IAAI;EACJ,WAAW,OAAO,SAAS,GAAG;EAC9B,KAAK,EAAE,KAAK,MAAM,KAAK;EACxB;AACD,SAAQ,KAAK,UAAU;AAEvB,QAAO,KACL,+CAA+C,QAAQ,UAAU,IAClE;AAGD,SAAQ,IAAI,GAAG,eAAe;AAC5B,YAAU,QAAQ,QAAQ,WAAW,OAAO,OAAO,SAAS;GAC5D"}