@intlayer/backend 8.12.2 → 8.12.4-canary.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 (415) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/bundle_optimization.json +9954 -6953
  2. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/configuration.json +1 -1
  3. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  4. package/dist/esm/controllers/audit.controller.mjs.map +1 -1
  5. package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -1
  6. package/dist/esm/controllers/cliSessionToken.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/demo.controller.mjs +32 -25
  8. package/dist/esm/controllers/demo.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/dictionary.controller.mjs +1 -0
  10. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/environment.controller.mjs.map +1 -1
  12. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  13. package/dist/esm/controllers/github.controller.mjs.map +1 -1
  14. package/dist/esm/controllers/gitlab.controller.mjs.map +1 -1
  15. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  16. package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
  17. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  18. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  19. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  20. package/dist/esm/controllers/projectMemberAccess.controller.mjs.map +1 -1
  21. package/dist/esm/controllers/recursiveAudit.controller.mjs.map +1 -1
  22. package/dist/esm/controllers/reviewer.controller.mjs.map +1 -1
  23. package/dist/esm/controllers/searchDoc.controller.mjs.map +1 -1
  24. package/dist/esm/controllers/showcaseProject.controller.mjs.map +1 -1
  25. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  26. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  27. package/dist/esm/controllers/translation.controller.mjs.map +1 -1
  28. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  29. package/dist/esm/emails/AffiliateActivatedEmail.mjs.map +1 -1
  30. package/dist/esm/emails/AffiliateConversionEmail.mjs.map +1 -1
  31. package/dist/esm/emails/AffiliateInvitationEmail.mjs.map +1 -1
  32. package/dist/esm/emails/AffiliateWelcomeEmail.mjs.map +1 -1
  33. package/dist/esm/emails/InviteUserEmail.mjs.map +1 -1
  34. package/dist/esm/emails/MagicLinkEmail.mjs.map +1 -1
  35. package/dist/esm/emails/MissionRequestedClientEmail.mjs.map +1 -1
  36. package/dist/esm/emails/MissionRequestedReviewerEmail.mjs.map +1 -1
  37. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
  38. package/dist/esm/emails/PasswordChangeConfirmation.mjs.map +1 -1
  39. package/dist/esm/emails/ResetUserPassword.mjs.map +1 -1
  40. package/dist/esm/emails/ReviewerApplicationEmail.mjs.map +1 -1
  41. package/dist/esm/emails/ReviewerApprovedEmail.mjs.map +1 -1
  42. package/dist/esm/emails/ReviewerContactEmail.mjs.map +1 -1
  43. package/dist/esm/emails/SubscriptionPaymentCancellation.mjs.map +1 -1
  44. package/dist/esm/emails/SubscriptionPaymentError.mjs.map +1 -1
  45. package/dist/esm/emails/SubscriptionPaymentSuccess.mjs.map +1 -1
  46. package/dist/esm/emails/ValidateUserEmail.mjs.map +1 -1
  47. package/dist/esm/emails/Welcome.mjs.map +1 -1
  48. package/dist/esm/index.mjs +1 -1
  49. package/dist/esm/index.mjs.map +1 -1
  50. package/dist/esm/logger/index.mjs.map +1 -1
  51. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  52. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  53. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  54. package/dist/esm/routes/audit.routes.mjs.map +1 -1
  55. package/dist/esm/routes/bitbucket.routes.mjs.map +1 -1
  56. package/dist/esm/routes/demo.routes.mjs.map +1 -1
  57. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  58. package/dist/esm/routes/environment.routes.mjs.map +1 -1
  59. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  60. package/dist/esm/routes/github.routes.mjs.map +1 -1
  61. package/dist/esm/routes/gitlab.routes.mjs.map +1 -1
  62. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  63. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  64. package/dist/esm/routes/paramsSchemas.mjs.map +1 -1
  65. package/dist/esm/routes/project.routes.mjs.map +1 -1
  66. package/dist/esm/routes/reviewer.routes.mjs.map +1 -1
  67. package/dist/esm/routes/search.routes.mjs.map +1 -1
  68. package/dist/esm/routes/showcaseProject.routes.mjs.map +1 -1
  69. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  70. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  71. package/dist/esm/routes/translate.routes.mjs.map +1 -1
  72. package/dist/esm/routes/user.routes.mjs.map +1 -1
  73. package/dist/esm/schemas/account.schema.mjs.map +1 -1
  74. package/dist/esm/schemas/affiliate.schema.mjs.map +1 -1
  75. package/dist/esm/schemas/affiliateInvitation.schema.mjs.map +1 -1
  76. package/dist/esm/schemas/audit.schema.mjs.map +1 -1
  77. package/dist/esm/schemas/auditJob.schema.mjs.map +1 -1
  78. package/dist/esm/schemas/auditPage.schema.mjs.map +1 -1
  79. package/dist/esm/schemas/cliSessionToken.schema.mjs.map +1 -1
  80. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  81. package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
  82. package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
  83. package/dist/esm/schemas/organization.schema.mjs.map +1 -1
  84. package/dist/esm/schemas/plans.schema.mjs.map +1 -1
  85. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  86. package/dist/esm/schemas/promoCode.schema.mjs.map +1 -1
  87. package/dist/esm/schemas/reviewer.schema.mjs.map +1 -1
  88. package/dist/esm/schemas/session.schema.mjs.map +1 -1
  89. package/dist/esm/schemas/showcaseProject.schema.mjs.map +1 -1
  90. package/dist/esm/schemas/tag.schema.mjs.map +1 -1
  91. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  92. package/dist/esm/services/affiliate.service.mjs.map +1 -1
  93. package/dist/esm/services/audit/analysis/analyzeBundleContent.mjs.map +1 -1
  94. package/dist/esm/services/audit/analysis/analyzeLinguisticStructure.mjs.map +1 -1
  95. package/dist/esm/services/audit/analysis/analyzeMetadata.mjs.map +1 -1
  96. package/dist/esm/services/audit/analysis/analyzeRobots.mjs.map +1 -1
  97. package/dist/esm/services/audit/analysis/analyzeSitemap.mjs.map +1 -1
  98. package/dist/esm/services/audit/analysis/analyzeUrlStructure.mjs.map +1 -1
  99. package/dist/esm/services/audit/analysis/calculateScore.mjs.map +1 -1
  100. package/dist/esm/services/audit/checkers/bundleChecker.mjs.map +1 -1
  101. package/dist/esm/services/audit/checkers/linguisticChecker.mjs.map +1 -1
  102. package/dist/esm/services/audit/checkers/metadataChecker.mjs.map +1 -1
  103. package/dist/esm/services/audit/checkers/pageChecker.mjs.map +1 -1
  104. package/dist/esm/services/audit/checkers/robotsChecker.mjs.map +1 -1
  105. package/dist/esm/services/audit/checkers/sitemapChecker.mjs.map +1 -1
  106. package/dist/esm/services/audit/checkers/urlChecker.mjs.map +1 -1
  107. package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
  108. package/dist/esm/services/audit/seoAudit.service.mjs.map +1 -1
  109. package/dist/esm/services/bitbucket.service.mjs.map +1 -1
  110. package/dist/esm/services/ci.service.mjs.map +1 -1
  111. package/dist/esm/services/cliSessionToken.service.mjs.map +1 -1
  112. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  113. package/dist/esm/services/email.service.mjs.map +1 -1
  114. package/dist/esm/services/environment.service.mjs.map +1 -1
  115. package/dist/esm/services/github.service.mjs.map +1 -1
  116. package/dist/esm/services/gitlab.service.mjs.map +1 -1
  117. package/dist/esm/services/oAuth2.service.mjs.map +1 -1
  118. package/dist/esm/services/organization.service.mjs.map +1 -1
  119. package/dist/esm/services/project/projectScreenshot.service.mjs.map +1 -1
  120. package/dist/esm/services/project.service.mjs.map +1 -1
  121. package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
  122. package/dist/esm/services/promoCode.service.mjs.map +1 -1
  123. package/dist/esm/services/reviewer/pictureUpload.service.mjs.map +1 -1
  124. package/dist/esm/services/reviewer.service.mjs.map +1 -1
  125. package/dist/esm/services/reviewerMessage.service.mjs.map +1 -1
  126. package/dist/esm/services/reviewerMission.service.mjs.map +1 -1
  127. package/dist/esm/services/session.service.mjs.map +1 -1
  128. package/dist/esm/services/showcase/showcaseProject.service.mjs.map +1 -1
  129. package/dist/esm/services/showcase/showcaseScan.service.mjs.map +1 -1
  130. package/dist/esm/services/showcase/showcaseUploadScreenshot.service.mjs.map +1 -1
  131. package/dist/esm/services/showcase/showcaseVerifyBundle.service.mjs.map +1 -1
  132. package/dist/esm/services/showcase/showcaseVerifyGithub.service.mjs.map +1 -1
  133. package/dist/esm/services/subscription.service.mjs.map +1 -1
  134. package/dist/esm/services/tag.service.mjs.map +1 -1
  135. package/dist/esm/services/translationQueue.service.mjs.map +1 -1
  136. package/dist/esm/services/translationWorker.service.mjs.map +1 -1
  137. package/dist/esm/services/user/avatarUpload.service.mjs.map +1 -1
  138. package/dist/esm/services/user.service.mjs.map +1 -1
  139. package/dist/esm/services/webhook.service.mjs.map +1 -1
  140. package/dist/esm/types/user.types.mjs.map +1 -1
  141. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  142. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/bundle_optimization.json +9954 -6953
  143. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/configuration.json +1 -1
  144. package/dist/esm/utils/AI/askDocQuestion/indexMarkdownFiles.mjs.map +1 -1
  145. package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -1
  146. package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
  147. package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -1
  148. package/dist/esm/utils/AI/auditTag/index.mjs.map +1 -1
  149. package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -1
  150. package/dist/esm/utils/AI/chat/index.mjs.map +1 -1
  151. package/dist/esm/utils/AI/chat/mcpInProcessTools.mjs.map +1 -1
  152. package/dist/esm/utils/AI/chat/sessionTools.mjs.map +1 -1
  153. package/dist/esm/utils/AI/customQuery/index.mjs.map +1 -1
  154. package/dist/esm/utils/AI/getProjectAIOptions.mjs.map +1 -1
  155. package/dist/esm/utils/AI/translateDictionaryDB.mjs.map +1 -1
  156. package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -1
  157. package/dist/esm/utils/accessControl.mjs.map +1 -1
  158. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  159. package/dist/esm/utils/cors.mjs +2 -13
  160. package/dist/esm/utils/cors.mjs.map +1 -1
  161. package/dist/esm/utils/demoDictionaries.mjs.map +1 -1
  162. package/dist/esm/utils/ensureArrayQueryFilter.mjs.map +1 -1
  163. package/dist/esm/utils/ensureMongoDocumentToObject.mjs.map +1 -1
  164. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  165. package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
  166. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  167. package/dist/esm/utils/errors/index.mjs +1 -0
  168. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  169. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  170. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
  171. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  172. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  173. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  174. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  175. package/dist/esm/utils/getFaviconUrl.mjs.map +1 -1
  176. package/dist/esm/utils/github/connectGithub.mjs.map +1 -1
  177. package/dist/esm/utils/httpStatusCodes.mjs.map +1 -1
  178. package/dist/esm/utils/image/resizeImage.mjs.map +1 -1
  179. package/dist/esm/utils/mapper/dictionary.mjs.map +1 -1
  180. package/dist/esm/utils/mapper/organization.mjs.map +1 -1
  181. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  182. package/dist/esm/utils/mapper/session.mjs.map +1 -1
  183. package/dist/esm/utils/mapper/showcaseProject.mjs.map +1 -1
  184. package/dist/esm/utils/mapper/tag.mjs.map +1 -1
  185. package/dist/esm/utils/mapper/user.mjs.map +1 -1
  186. package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
  187. package/dist/esm/utils/oAuth2.mjs.map +1 -1
  188. package/dist/esm/utils/permissions.mjs.map +1 -1
  189. package/dist/esm/utils/plan.mjs.map +1 -1
  190. package/dist/esm/utils/puppeteer/launchBrowser.mjs.map +1 -1
  191. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  192. package/dist/esm/utils/redis/connectRedis.mjs.map +1 -1
  193. package/dist/esm/utils/removeObjectKeys.mjs.map +1 -1
  194. package/dist/esm/utils/responseData.mjs.map +1 -1
  195. package/dist/esm/utils/s3/s3Client.mjs.map +1 -1
  196. package/dist/esm/utils/validation/validateDictionary.mjs.map +1 -1
  197. package/dist/esm/utils/validation/validateOrganization.mjs.map +1 -1
  198. package/dist/esm/utils/validation/validateProject.mjs.map +1 -1
  199. package/dist/esm/utils/validation/validateTag.mjs.map +1 -1
  200. package/dist/esm/utils/validation/validateUser.mjs.map +1 -1
  201. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  202. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  203. package/dist/types/controllers/bitbucket.controller.d.ts.map +1 -1
  204. package/dist/types/controllers/cliSessionToken.controller.d.ts.map +1 -1
  205. package/dist/types/controllers/demo.controller.d.ts.map +1 -1
  206. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  207. package/dist/types/controllers/environment.controller.d.ts.map +1 -1
  208. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  209. package/dist/types/controllers/github.controller.d.ts.map +1 -1
  210. package/dist/types/controllers/gitlab.controller.d.ts.map +1 -1
  211. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  212. package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
  213. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  214. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  215. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  216. package/dist/types/controllers/projectMemberAccess.controller.d.ts.map +1 -1
  217. package/dist/types/controllers/recursiveAudit.controller.d.ts.map +1 -1
  218. package/dist/types/controllers/reviewer.controller.d.ts.map +1 -1
  219. package/dist/types/controllers/searchDoc.controller.d.ts.map +1 -1
  220. package/dist/types/controllers/showcaseProject.controller.d.ts.map +1 -1
  221. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  222. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  223. package/dist/types/controllers/translation.controller.d.ts.map +1 -1
  224. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  225. package/dist/types/emails/AffiliateActivatedEmail.d.ts +20 -18
  226. package/dist/types/emails/AffiliateActivatedEmail.d.ts.map +1 -1
  227. package/dist/types/emails/AffiliateConversionEmail.d.ts +21 -19
  228. package/dist/types/emails/AffiliateConversionEmail.d.ts.map +1 -1
  229. package/dist/types/emails/AffiliateInvitationEmail.d.ts +20 -18
  230. package/dist/types/emails/AffiliateInvitationEmail.d.ts.map +1 -1
  231. package/dist/types/emails/AffiliateWelcomeEmail.d.ts +20 -18
  232. package/dist/types/emails/AffiliateWelcomeEmail.d.ts.map +1 -1
  233. package/dist/types/emails/InviteUserEmail.d.ts +20 -18
  234. package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
  235. package/dist/types/emails/MagicLinkEmail.d.ts +20 -18
  236. package/dist/types/emails/MagicLinkEmail.d.ts.map +1 -1
  237. package/dist/types/emails/MissionRequestedClientEmail.d.ts +20 -18
  238. package/dist/types/emails/MissionRequestedClientEmail.d.ts.map +1 -1
  239. package/dist/types/emails/MissionRequestedReviewerEmail.d.ts +20 -18
  240. package/dist/types/emails/MissionRequestedReviewerEmail.d.ts.map +1 -1
  241. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +20 -18
  242. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
  243. package/dist/types/emails/PasswordChangeConfirmation.d.ts +20 -18
  244. package/dist/types/emails/PasswordChangeConfirmation.d.ts.map +1 -1
  245. package/dist/types/emails/ResetUserPassword.d.ts +20 -18
  246. package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
  247. package/dist/types/emails/ReviewerApplicationEmail.d.ts +20 -18
  248. package/dist/types/emails/ReviewerApplicationEmail.d.ts.map +1 -1
  249. package/dist/types/emails/ReviewerApprovedEmail.d.ts +20 -18
  250. package/dist/types/emails/ReviewerApprovedEmail.d.ts.map +1 -1
  251. package/dist/types/emails/ReviewerContactEmail.d.ts +3 -1
  252. package/dist/types/emails/ReviewerContactEmail.d.ts.map +1 -1
  253. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +20 -18
  254. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
  255. package/dist/types/emails/SubscriptionPaymentError.d.ts +20 -18
  256. package/dist/types/emails/SubscriptionPaymentError.d.ts.map +1 -1
  257. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +20 -18
  258. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts.map +1 -1
  259. package/dist/types/emails/ValidateUserEmail.d.ts +20 -18
  260. package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
  261. package/dist/types/emails/Welcome.d.ts +20 -18
  262. package/dist/types/emails/Welcome.d.ts.map +1 -1
  263. package/dist/types/export.d.ts +1 -1
  264. package/dist/types/logger/index.d.ts +3 -1
  265. package/dist/types/logger/index.d.ts.map +1 -1
  266. package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
  267. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  268. package/dist/types/routes/demo.routes.d.ts.map +1 -1
  269. package/dist/types/schemas/account.schema.d.ts +35 -34
  270. package/dist/types/schemas/account.schema.d.ts.map +1 -1
  271. package/dist/types/schemas/affiliate.schema.d.ts +109 -108
  272. package/dist/types/schemas/affiliate.schema.d.ts.map +1 -1
  273. package/dist/types/schemas/affiliateInvitation.schema.d.ts +49 -48
  274. package/dist/types/schemas/affiliateInvitation.schema.d.ts.map +1 -1
  275. package/dist/types/schemas/audit.schema.d.ts.map +1 -1
  276. package/dist/types/schemas/auditJob.schema.d.ts +6 -6
  277. package/dist/types/schemas/auditPage.schema.d.ts +6 -6
  278. package/dist/types/schemas/cliSessionToken.schema.d.ts +14 -13
  279. package/dist/types/schemas/cliSessionToken.schema.d.ts.map +1 -1
  280. package/dist/types/schemas/dictionary.schema.d.ts +60 -59
  281. package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
  282. package/dist/types/schemas/discussion.schema.d.ts +51 -50
  283. package/dist/types/schemas/discussion.schema.d.ts.map +1 -1
  284. package/dist/types/schemas/oAuth2.schema.d.ts +18 -17
  285. package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
  286. package/dist/types/schemas/organization.schema.d.ts +45 -44
  287. package/dist/types/schemas/organization.schema.d.ts.map +1 -1
  288. package/dist/types/schemas/plans.schema.d.ts +45 -44
  289. package/dist/types/schemas/plans.schema.d.ts.map +1 -1
  290. package/dist/types/schemas/project.schema.d.ts +73 -72
  291. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  292. package/dist/types/schemas/promoCode.schema.d.ts +61 -60
  293. package/dist/types/schemas/promoCode.schema.d.ts.map +1 -1
  294. package/dist/types/schemas/reviewer.schema.d.ts +221 -220
  295. package/dist/types/schemas/reviewer.schema.d.ts.map +1 -1
  296. package/dist/types/schemas/session.schema.d.ts +54 -52
  297. package/dist/types/schemas/session.schema.d.ts.map +1 -1
  298. package/dist/types/schemas/showcaseProject.schema.d.ts +61 -60
  299. package/dist/types/schemas/showcaseProject.schema.d.ts.map +1 -1
  300. package/dist/types/schemas/tag.schema.d.ts +45 -44
  301. package/dist/types/schemas/tag.schema.d.ts.map +1 -1
  302. package/dist/types/schemas/user.schema.d.ts +71 -70
  303. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  304. package/dist/types/services/affiliate.service.d.ts.map +1 -1
  305. package/dist/types/services/audit/analysis/analyzeBundleContent.d.ts.map +1 -1
  306. package/dist/types/services/audit/analysis/analyzeLinguisticStructure.d.ts.map +1 -1
  307. package/dist/types/services/audit/analysis/analyzeRobots.d.ts.map +1 -1
  308. package/dist/types/services/audit/analysis/analyzeSitemap.d.ts.map +1 -1
  309. package/dist/types/services/audit/analysis/calculateScore.d.ts.map +1 -1
  310. package/dist/types/services/audit/checkers/bundleChecker.d.ts.map +1 -1
  311. package/dist/types/services/audit/recursiveAudit.service.d.ts +5 -4
  312. package/dist/types/services/audit/recursiveAudit.service.d.ts.map +1 -1
  313. package/dist/types/services/audit/types.d.ts.map +1 -1
  314. package/dist/types/services/bitbucket.service.d.ts.map +1 -1
  315. package/dist/types/services/cliSessionToken.service.d.ts.map +1 -1
  316. package/dist/types/services/dictionary.service.d.ts.map +1 -1
  317. package/dist/types/services/email.service.d.ts.map +1 -1
  318. package/dist/types/services/github.service.d.ts.map +1 -1
  319. package/dist/types/services/gitlab.service.d.ts.map +1 -1
  320. package/dist/types/services/oAuth2.service.d.ts.map +1 -1
  321. package/dist/types/services/organization.service.d.ts.map +1 -1
  322. package/dist/types/services/project/projectScreenshot.service.d.ts.map +1 -1
  323. package/dist/types/services/project.service.d.ts.map +1 -1
  324. package/dist/types/services/promoCode.service.d.ts.map +1 -1
  325. package/dist/types/services/reviewer/pictureUpload.service.d.ts.map +1 -1
  326. package/dist/types/services/reviewer.service.d.ts.map +1 -1
  327. package/dist/types/services/reviewerMessage.service.d.ts.map +1 -1
  328. package/dist/types/services/reviewerMission.service.d.ts.map +1 -1
  329. package/dist/types/services/session.service.d.ts.map +1 -1
  330. package/dist/types/services/showcase/showcaseProject.service.d.ts.map +1 -1
  331. package/dist/types/services/showcase/showcaseScan.service.d.ts.map +1 -1
  332. package/dist/types/services/showcase/showcaseUploadScreenshot.service.d.ts.map +1 -1
  333. package/dist/types/services/showcase/showcaseVerifyBundle.service.d.ts.map +1 -1
  334. package/dist/types/services/showcase/showcaseVerifyGithub.service.d.ts.map +1 -1
  335. package/dist/types/services/subscription.service.d.ts.map +1 -1
  336. package/dist/types/services/tag.service.d.ts.map +1 -1
  337. package/dist/types/services/translationQueue.service.d.ts +2 -1
  338. package/dist/types/services/translationQueue.service.d.ts.map +1 -1
  339. package/dist/types/services/translationWorker.service.d.ts.map +1 -1
  340. package/dist/types/services/user/avatarUpload.service.d.ts.map +1 -1
  341. package/dist/types/services/user.service.d.ts.map +1 -1
  342. package/dist/types/services/webhook.service.d.ts.map +1 -1
  343. package/dist/types/types/Routes.d.ts.map +1 -1
  344. package/dist/types/types/account.types.d.ts.map +1 -1
  345. package/dist/types/types/affiliate.types.d.ts.map +1 -1
  346. package/dist/types/types/affiliateInvitation.types.d.ts.map +1 -1
  347. package/dist/types/types/dictionary.types.d.ts.map +1 -1
  348. package/dist/types/types/discussion.types.d.ts.map +1 -1
  349. package/dist/types/types/oAuth2.types.d.ts.map +1 -1
  350. package/dist/types/types/organization.types.d.ts.map +1 -1
  351. package/dist/types/types/plan.types.d.ts.map +1 -1
  352. package/dist/types/types/project.types.d.ts.map +1 -1
  353. package/dist/types/types/promoCode.types.d.ts.map +1 -1
  354. package/dist/types/types/reviewer.types.d.ts.map +1 -1
  355. package/dist/types/types/session.types.d.ts.map +1 -1
  356. package/dist/types/types/showcaseProject.types.d.ts.map +1 -1
  357. package/dist/types/types/tag.types.d.ts.map +1 -1
  358. package/dist/types/types/user.types.d.ts.map +1 -1
  359. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
  360. package/dist/types/utils/AI/askDocQuestion/indexMarkdownFiles.d.ts.map +1 -1
  361. package/dist/types/utils/AI/auditDictionary/index.d.ts.map +1 -1
  362. package/dist/types/utils/AI/auditDictionaryField/index.d.ts.map +1 -1
  363. package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts.map +1 -1
  364. package/dist/types/utils/AI/auditTag/index.d.ts.map +1 -1
  365. package/dist/types/utils/AI/autocomplete/index.d.ts.map +1 -1
  366. package/dist/types/utils/AI/chat/index.d.ts.map +1 -1
  367. package/dist/types/utils/AI/chat/mcpInProcessTools.d.ts.map +1 -1
  368. package/dist/types/utils/AI/chat/sessionTools.d.ts +25 -24
  369. package/dist/types/utils/AI/chat/sessionTools.d.ts.map +1 -1
  370. package/dist/types/utils/AI/customQuery/index.d.ts.map +1 -1
  371. package/dist/types/utils/AI/translateDictionaryDB.d.ts.map +1 -1
  372. package/dist/types/utils/AI/translateJSON/index.d.ts.map +1 -1
  373. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  374. package/dist/types/utils/cors.d.ts +1 -7
  375. package/dist/types/utils/cors.d.ts.map +1 -1
  376. package/dist/types/utils/demoDictionaries.d.ts.map +1 -1
  377. package/dist/types/utils/ensureArrayQueryFilter.d.ts.map +1 -1
  378. package/dist/types/utils/errors/ErrorHandler.d.ts +8 -6
  379. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  380. package/dist/types/utils/errors/ErrorsClass.d.ts.map +1 -1
  381. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  382. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
  383. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
  384. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
  385. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
  386. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
  387. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +7 -6
  388. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
  389. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
  390. package/dist/types/utils/getFaviconUrl.d.ts.map +1 -1
  391. package/dist/types/utils/github/connectGithub.d.ts.map +1 -1
  392. package/dist/types/utils/httpStatusCodes.d.ts.map +1 -1
  393. package/dist/types/utils/image/resizeImage.d.ts.map +1 -1
  394. package/dist/types/utils/mapper/dictionary.d.ts.map +1 -1
  395. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  396. package/dist/types/utils/mapper/session.d.ts.map +1 -1
  397. package/dist/types/utils/mapper/showcaseProject.d.ts.map +1 -1
  398. package/dist/types/utils/mapper/tag.d.ts.map +1 -1
  399. package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
  400. package/dist/types/utils/mongoDB/connectDB.d.ts.map +1 -1
  401. package/dist/types/utils/mongoDB/types.d.ts.map +1 -1
  402. package/dist/types/utils/oAuth2.d.ts.map +1 -1
  403. package/dist/types/utils/permissions.d.ts +2 -1
  404. package/dist/types/utils/permissions.d.ts.map +1 -1
  405. package/dist/types/utils/plan.d.ts.map +1 -1
  406. package/dist/types/utils/rateLimiter.d.ts.map +1 -1
  407. package/dist/types/utils/redis/connectRedis.d.ts.map +1 -1
  408. package/dist/types/utils/responseData.d.ts.map +1 -1
  409. package/dist/types/utils/s3/s3Client.d.ts.map +1 -1
  410. package/dist/types/utils/validation/validateDictionary.d.ts.map +1 -1
  411. package/dist/types/utils/validation/validateOrganization.d.ts.map +1 -1
  412. package/dist/types/utils/validation/validateProject.d.ts.map +1 -1
  413. package/dist/types/utils/validation/validateTag.d.ts.map +1 -1
  414. package/dist/types/utils/validation/validateUser.d.ts.map +1 -1
  415. package/package.json +17 -17
@@ -1 +1 @@
1
- {"version":3,"file":"dictionary.controller.mjs","names":["dictionaryService.findDictionaries","dictionaryService.countDictionaries","dictionaryService.getDictionaryByKey","dictionaryService.createDictionary","projectService.getProjectById","webhooksService.triggerAll","dictionaryService.getDictionaryById","dictionaryService.incrementVersion","dictionaryService.updateDictionaryByKey","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 { Locale } from '@intlayer/types/allLocales';\nimport type {\n ContentNode,\n DictionaryId,\n Dictionary as LocalDictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport { logger } from '@logger';\nimport * as dictionaryService from '@services/dictionary.service';\nimport * as projectService from '@services/project.service';\nimport { addTranslationJob } from '@services/translationQueue.service';\nimport * as webhooksService from '@services/webhook.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.session || {};\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.session,\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.session || {};\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.session,\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.session || {};\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.session,\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[String(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.session || {};\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.session,\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.session || {};\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.session ?? {})) {\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 'en-GB': 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n ru: 'Словарь успешно создан',\n ja: '辞書が正常に作成されました',\n ko: '사전이 성공적으로 생성되었습니다',\n zh: '字典已成功创建',\n de: 'Wörterbuch erfolgreich erstellt',\n ar: 'تم إنشاء القاموس بنجاح',\n it: 'Dizionario creato con successo',\n pt: 'Dicionário criado com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक बनाया गया',\n tr: 'Sözlük başarıyla oluşturuldu',\n pl: 'Słownik został pomyślnie utworzony',\n id: 'Kamus berhasil dibuat',\n vi: 'Từ điển đã được tạo thành công',\n uk: 'Словник успішно створено',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n 'en-GB': '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 ru: 'Ваш словарь был успешно создан',\n ja: '辞書は正常に作成されました',\n ko: '사전이 성공적으로 생성되었습니다',\n zh: '您的字典已成功创建',\n de: 'Ihr Wörterbuch wurde erfolgreich erstellt',\n ar: 'لقد تم إنشاء قاموسك بنجاح',\n it: 'Il tuo dizionario è stato creato con successo',\n pt: 'Seu dicionário foi criado com sucesso',\n hi: 'आपका शब्दकोश सफलतापूर्वक बना लिया गया है',\n tr: 'Sözlüğünüz başarıyla oluşturuldu',\n pl: 'Twój słownik został pomyślnie utworzony',\n id: 'Kamus Anda telah berhasil dibuat',\n vi: 'Từ điển của bạn đã được tạo thành công',\n uk: 'Ваш словник успішно створено',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary creation\n logger.error(\n 'Failed to trigger CI builds after dictionary creation',\n error\n );\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 upToDateDictionaries: {\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.session || {};\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.session ?? {})) {\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 upToDateDictionariesResult: PushDictionariesResultData['upToDateDictionaries'] =\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: String(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 if (isSameContent) {\n upToDateDictionariesResult.push({\n key: remoteDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: String(remoteDictionary.id),\n });\n continue;\n }\n\n const newContent: VersionedContent = new Map(remoteDictionary.content);\n const newContentVersion =\n dictionaryService.incrementVersion(remoteDictionary);\n\n newContent.set(newContentVersion, {\n content: cleanedContent,\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: String(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 upToDateDictionaries: upToDateDictionariesResult,\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n 'en-GB': 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n ru: 'Словари успешно обновлены',\n ja: '辞書が正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '字典已成功更新',\n de: 'Wörterbücher erfolgreich aktualisiert',\n ar: 'تم تحديث القواميس بنجاح',\n it: 'Dizionari aggiornati con successo',\n pt: 'Dicionários atualizados com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक अपडेट किए गए',\n tr: 'Sözlükler başarıyla güncellendi',\n pl: 'Słowniki zostały pomyślnie zaktualizowane',\n id: 'Kamus berhasil diperbarui',\n vi: 'Từ điển đã được cập nhật thành công',\n uk: 'Словники успішно оновлені',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n 'en-GB': '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 ru: 'Ваши словари были успешно обновлены',\n ja: '辞書は正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '您的字典已成功更新',\n de: 'Ihre Wörterbücher wurden erfolgreich aktualisiert',\n ar: 'لقد تم تحديث قواميسك بنجاح',\n it: 'I tuoi dizionari sono stati aggiornati con successo',\n pt: 'Seus dicionários foram atualizados com sucesso',\n hi: 'आपके शब्दकोश सफलतापूर्वक अपडेट कर दिए गए हैं',\n tr: 'Sözlükleriniz başarıyla güncellendi',\n pl: 'Twoje słowniki zostały pomyślnie zaktualizowane',\n id: 'Kamus Anda telah berhasil diperbarui',\n vi: 'Từ điển của bạn đã được cập nhật thành công',\n uk: 'Ваші словники успішно оновлені',\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 const hasChanges =\n newDictionariesResult.length > 0 || updatedDictionariesResult.length > 0;\n\n // Trigger CI builds if configured (only if there were actual changes)\n if (project && hasChanges) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n\n // Auto-fill: queue a translation job for the pushed dictionaries when enabled\n if (fullProject.autoFill) {\n const projectLocales =\n fullProject.configuration?.internationalization?.locales ?? [];\n const defaultLocale =\n fullProject.configuration?.internationalization?.defaultLocale;\n const targetLocales = projectLocales.filter(\n (l) => l !== defaultLocale\n );\n\n if (targetLocales.length > 0) {\n // Collect the IDs of all newly created and updated dictionaries\n const affectedIds = [\n ...newDictionariesResult\n .map((d) => d.id)\n .filter((id): id is string => !!id),\n ...updatedDictionariesResult\n .map((d) => d.id)\n .filter((id): id is string => !!id),\n ];\n\n if (affectedIds.length > 0) {\n await addTranslationJob({\n dictionaryTargets: affectedIds.map((id) => ({\n dictionaryId: id,\n locales: targetLocales as Locale[],\n })),\n projectId: String(project.id),\n userId: String(user.id),\n mode: 'complete',\n });\n logger.info(\n `Auto-fill triggered for ${affectedIds.length} dictionaries after push`\n );\n }\n }\n }\n } catch (error) {\n // Log error but don't fail the dictionary push\n logger.error(\n 'Failed to trigger CI builds after dictionary push',\n error\n );\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.session || {};\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.session ?? {})) {\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 'en-GB': 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n ru: 'Словарь успешно обновлен',\n ja: '辞書が正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '字典已成功更新',\n de: 'Wörterbuch erfolgreich aktualisiert',\n ar: 'تم تحديث القاموس بنجاح',\n it: 'Dizionario aggiornato con successo',\n pt: 'Dicionário atualizado com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक अपडेट किया गया',\n tr: 'Sözlük başarıyla güncellendi',\n pl: 'Słownik został pomyślnie zaktualizowany',\n id: 'Kamus berhasil diperbarui',\n vi: 'Từ điển đã được cập nhật thành công',\n uk: 'Словарь успішно оновлено',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n 'en-GB': '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 ru: 'Ваш словарь был успешно обновлен',\n ja: '辞書は正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '您的字典已成功更新',\n de: 'Ihr Wörterbuch wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث قاموسك بنجاح',\n it: 'Il tuo dizionario è stato aggiornato con successo',\n pt: 'Seu dicionário foi atualizado com sucesso',\n hi: 'आपका शब्दकोश सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Sözlüğünüz başarıyla güncellendi',\n pl: 'Twój słownik został pomyślnie zaktualizowany',\n id: 'Kamus Anda telah berhasil diperbarui',\n vi: 'Từ điển của bạn đã được cập nhật thành công',\n uk: 'Ваш словник успішно оновлено',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary update\n logger.error(\n 'Failed to trigger CI builds after dictionary update',\n error\n );\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.session || {};\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.session ?? {})) {\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 'en-GB': 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n ru: 'Словарь успешно удален',\n ja: '辞書が正常に削除されました',\n ko: '사전이 성공적으로 삭제되었습니다',\n zh: '字典已成功删除',\n de: 'Wörterbuch erfolgreich gelöscht',\n ar: 'تم حذف القاموس بنجاح',\n it: 'Dizionario eliminato con successo',\n pt: 'Dicionário excluído com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक हटा दिया गया',\n tr: 'Sözlük başarıyla silindi',\n pl: 'Słownik został pomyślnie usunięty',\n id: 'Kamus berhasil dihapus',\n vi: 'Từ điển đã được xóa thành công',\n uk: 'Словарь успішно видалено',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n 'en-GB': '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 ru: 'Ваш словарь был успешно удален',\n ja: '辞書は正常に削除されました',\n ko: '사전이 성공적으로 삭제되었습니다',\n zh: '您的字典已成功删除',\n de: 'Ihr Wörterbuch wurde erfolgreich gelöscht',\n ar: 'لقد تم حذف قاموسك بنجاح',\n it: 'Il tuo dizionario è stato eliminato con successo',\n pt: 'Seu dicionário foi excluído com sucesso',\n hi: 'आपका शब्दकोश सफलतापूर्वक हटा दिया गया है',\n tr: 'Sözlüğünüz başarıyla silindi',\n pl: 'Twój słownik został pomyślnie usunięty',\n id: 'Kamus Anda telah berhasil dihapus',\n vi: 'Từ điển của bạn đã được xóa thành công',\n uk: 'Ваш словник успішно видалено',\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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"}
1
+ {"version":3,"file":"dictionary.controller.mjs","names":["dictionaryService.findDictionaries","dictionaryService.countDictionaries","dictionaryService.getDictionaryByKey","dictionaryService.createDictionary","projectService.getProjectById","webhooksService.triggerAll","dictionaryService.getDictionaryById","dictionaryService.incrementVersion","dictionaryService.updateDictionaryByKey","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 { Locale } from '@intlayer/types/allLocales';\nimport type {\n ContentNode,\n DictionaryId,\n Dictionary as LocalDictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport { logger } from '@logger';\nimport * as dictionaryService from '@services/dictionary.service';\nimport * as projectService from '@services/project.service';\nimport { addTranslationJob } from '@services/translationQueue.service';\nimport * as webhooksService from '@services/webhook.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.session || {};\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.session,\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.session || {};\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.session,\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.session || {};\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.session,\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[String(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.session || {};\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 (!dictionary) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_NOT_FOUND'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.session,\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.session || {};\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.session ?? {})) {\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 'en-GB': 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n ru: 'Словарь успешно создан',\n ja: '辞書が正常に作成されました',\n ko: '사전이 성공적으로 생성되었습니다',\n zh: '字典已成功创建',\n de: 'Wörterbuch erfolgreich erstellt',\n ar: 'تم إنشاء القاموس بنجاح',\n it: 'Dizionario creato con successo',\n pt: 'Dicionário criado com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक बनाया गया',\n tr: 'Sözlük başarıyla oluşturuldu',\n pl: 'Słownik został pomyślnie utworzony',\n id: 'Kamus berhasil dibuat',\n vi: 'Từ điển đã được tạo thành công',\n uk: 'Словник успішно створено',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n 'en-GB': '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 ru: 'Ваш словарь был успешно создан',\n ja: '辞書は正常に作成されました',\n ko: '사전이 성공적으로 생성되었습니다',\n zh: '您的字典已成功创建',\n de: 'Ihr Wörterbuch wurde erfolgreich erstellt',\n ar: 'لقد تم إنشاء قاموسك بنجاح',\n it: 'Il tuo dizionario è stato creato con successo',\n pt: 'Seu dicionário foi criado com sucesso',\n hi: 'आपका शब्दकोश सफलतापूर्वक बना लिया गया है',\n tr: 'Sözlüğünüz başarıyla oluşturuldu',\n pl: 'Twój słownik został pomyślnie utworzony',\n id: 'Kamus Anda telah berhasil dibuat',\n vi: 'Từ điển của bạn đã được tạo thành công',\n uk: 'Ваш словник успішно створено',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary creation\n logger.error(\n 'Failed to trigger CI builds after dictionary creation',\n error\n );\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 upToDateDictionaries: {\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.session || {};\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.session ?? {})) {\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 upToDateDictionariesResult: PushDictionariesResultData['upToDateDictionaries'] =\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: String(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 if (isSameContent) {\n upToDateDictionariesResult.push({\n key: remoteDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: String(remoteDictionary.id),\n });\n continue;\n }\n\n const newContent: VersionedContent = new Map(remoteDictionary.content);\n const newContentVersion =\n dictionaryService.incrementVersion(remoteDictionary);\n\n newContent.set(newContentVersion, {\n content: cleanedContent,\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: String(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 upToDateDictionaries: upToDateDictionariesResult,\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n 'en-GB': 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n ru: 'Словари успешно обновлены',\n ja: '辞書が正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '字典已成功更新',\n de: 'Wörterbücher erfolgreich aktualisiert',\n ar: 'تم تحديث القواميس بنجاح',\n it: 'Dizionari aggiornati con successo',\n pt: 'Dicionários atualizados com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक अपडेट किए गए',\n tr: 'Sözlükler başarıyla güncellendi',\n pl: 'Słowniki zostały pomyślnie zaktualizowane',\n id: 'Kamus berhasil diperbarui',\n vi: 'Từ điển đã được cập nhật thành công',\n uk: 'Словники успішно оновлені',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n 'en-GB': '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 ru: 'Ваши словари были успешно обновлены',\n ja: '辞書は正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '您的字典已成功更新',\n de: 'Ihre Wörterbücher wurden erfolgreich aktualisiert',\n ar: 'لقد تم تحديث قواميسك بنجاح',\n it: 'I tuoi dizionari sono stati aggiornati con successo',\n pt: 'Seus dicionários foram atualizados com sucesso',\n hi: 'आपके शब्दकोश सफलतापूर्वक अपडेट कर दिए गए हैं',\n tr: 'Sözlükleriniz başarıyla güncellendi',\n pl: 'Twoje słowniki zostały pomyślnie zaktualizowane',\n id: 'Kamus Anda telah berhasil diperbarui',\n vi: 'Từ điển của bạn đã được cập nhật thành công',\n uk: 'Ваші словники успішно оновлені',\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 const hasChanges =\n newDictionariesResult.length > 0 || updatedDictionariesResult.length > 0;\n\n // Trigger CI builds if configured (only if there were actual changes)\n if (project && hasChanges) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n\n // Auto-fill: queue a translation job for the pushed dictionaries when enabled\n if (fullProject.autoFill) {\n const projectLocales =\n fullProject.configuration?.internationalization?.locales ?? [];\n const defaultLocale =\n fullProject.configuration?.internationalization?.defaultLocale;\n const targetLocales = projectLocales.filter(\n (l) => l !== defaultLocale\n );\n\n if (targetLocales.length > 0) {\n // Collect the IDs of all newly created and updated dictionaries\n const affectedIds = [\n ...newDictionariesResult\n .map((d) => d.id)\n .filter((id): id is string => !!id),\n ...updatedDictionariesResult\n .map((d) => d.id)\n .filter((id): id is string => !!id),\n ];\n\n if (affectedIds.length > 0) {\n await addTranslationJob({\n dictionaryTargets: affectedIds.map((id) => ({\n dictionaryId: id,\n locales: targetLocales as Locale[],\n })),\n projectId: String(project.id),\n userId: String(user.id),\n mode: 'complete',\n });\n logger.info(\n `Auto-fill triggered for ${affectedIds.length} dictionaries after push`\n );\n }\n }\n }\n } catch (error) {\n // Log error but don't fail the dictionary push\n logger.error(\n 'Failed to trigger CI builds after dictionary push',\n error\n );\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.session || {};\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.session ?? {})) {\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 'en-GB': 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n ru: 'Словарь успешно обновлен',\n ja: '辞書が正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '字典已成功更新',\n de: 'Wörterbuch erfolgreich aktualisiert',\n ar: 'تم تحديث القاموس بنجاح',\n it: 'Dizionario aggiornato con successo',\n pt: 'Dicionário atualizado com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक अपडेट किया गया',\n tr: 'Sözlük başarıyla güncellendi',\n pl: 'Słownik został pomyślnie zaktualizowany',\n id: 'Kamus berhasil diperbarui',\n vi: 'Từ điển đã được cập nhật thành công',\n uk: 'Словарь успішно оновлено',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n 'en-GB': '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 ru: 'Ваш словарь был успешно обновлен',\n ja: '辞書は正常に更新されました',\n ko: '사전이 성공적으로 업데이트되었습니다',\n zh: '您的字典已成功更新',\n de: 'Ihr Wörterbuch wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث قاموسك بنجاح',\n it: 'Il tuo dizionario è stato aggiornato con successo',\n pt: 'Seu dicionário foi atualizado com sucesso',\n hi: 'आपका शब्दकोश सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Sözlüğünüz başarıyla güncellendi',\n pl: 'Twój słownik został pomyślnie zaktualizowany',\n id: 'Kamus Anda telah berhasil diperbarui',\n vi: 'Từ điển của bạn đã được cập nhật thành công',\n uk: 'Ваш словник успішно оновлено',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary update\n logger.error(\n 'Failed to trigger CI builds after dictionary update',\n error\n );\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.session || {};\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.session ?? {})) {\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 'en-GB': 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n ru: 'Словарь успешно удален',\n ja: '辞書が正常に削除されました',\n ko: '사전이 성공적으로 삭제되었습니다',\n zh: '字典已成功删除',\n de: 'Wörterbuch erfolgreich gelöscht',\n ar: 'تم حذف القاموس بنجاح',\n it: 'Dizionario eliminato con successo',\n pt: 'Dicionário excluído com sucesso',\n hi: 'शब्दकोश सफलतापूर्वक हटा दिया गया',\n tr: 'Sözlük başarıyla silindi',\n pl: 'Słownik został pomyślnie usunięty',\n id: 'Kamus berhasil dihapus',\n vi: 'Từ điển đã được xóa thành công',\n uk: 'Словарь успішно видалено',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n 'en-GB': '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 ru: 'Ваш словарь был успешно удален',\n ja: '辞書は正常に削除されました',\n ko: '사전이 성공적으로 삭제되었습니다',\n zh: '您的字典已成功删除',\n de: 'Ihr Wörterbuch wurde erfolgreich gelöscht',\n ar: 'لقد تم حذف قاموسك بنجاح',\n it: 'Il tuo dizionario è stato eliminato con successo',\n pt: 'Seu dicionário foi excluído com sucesso',\n hi: 'आपका शब्दकोश सफलतापूर्वक हटा दिया गया है',\n tr: 'Sözlüğünüz başarıyla silindi',\n pl: 'Twój słownik został pomyślnie usunięty',\n id: 'Kamus Anda telah berhasil dihapus',\n vi: 'Từ điển của bạn đã được xóa thành công',\n uk: 'Ваш словник успішно видалено',\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"environment.controller.mjs","names":["projectService.getProjectById","dictionaryService.findDictionaries","dictionaryService.deleteDictionaryById","dictionaryService.createDictionary"],"sources":["../../../src/controllers/environment.controller.ts"],"sourcesContent":["import { SessionModel } from '@schemas/session.schema';\nimport * as dictionaryService from '@services/dictionary.service';\nimport * as projectService from '@services/project.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n mapProjectToAPI,\n PRODUCTION_ENV_SENTINEL_ID,\n} from '@utils/mapper/project';\nimport { hasPermission } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { EnvironmentAPI, EnvironmentData } from '@/types/project.types';\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Returns the value to store in `session.activeEnvironmentId`.\n * Convention: null = production (default env); ObjectId string = any other env.\n */\nconst toSessionEnvironmentId = (\n isDefault: boolean,\n environmentId: string\n): string | null => (isDefault ? null : environmentId);\n\n/**\n * Returns the MongoDB filter for a dictionary's `environmentId` field.\n * Production dictionaries are stored with environmentId = null.\n */\nconst toDictionaryEnvironmentFilter = (\n isDefaultEnvironment: boolean,\n environmentId: string\n) =>\n isDefaultEnvironment || environmentId === PRODUCTION_ENV_SENTINEL_ID\n ? { $or: [{ environmentId: null }, { environmentId: { $exists: false } }] }\n : { environmentId };\n\n/**\n * The environmentId to store on a newly created dictionary.\n * null = production (default); real ObjectId = specific env.\n */\nconst toDictionaryEnvironmentId = (\n isDefaultEnvironment: boolean,\n environmentId: string\n): string | null =>\n isDefaultEnvironment || environmentId === PRODUCTION_ENV_SENTINEL_ID\n ? null\n : environmentId;\n\n// ─── Add ──────────────────────────────────────────────────────────────────────\n\nexport type AddEnvironmentBody = Pick<\n EnvironmentData,\n 'name' | 'configuration'\n>;\nexport type AddEnvironmentResult = ResponseData<EnvironmentAPI[]>;\n\nexport const addEnvironment = async (\n request: FastifyRequest<{ Body: AddEnvironmentBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const { name, configuration } = request.body;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (!name?.trim())\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n\n const sanitizedName = name.trim().replace(/\\s+/g, '_');\n\n const existingNames = (projectDoc.environments ?? []).map((environment) =>\n environment.name.toLowerCase()\n );\n if (existingNames.includes(sanitizedName.toLowerCase()))\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_ALREADY_EXISTS'\n );\n\n (projectDoc.environments as any[]).push({\n name: sanitizedName,\n isDefault: false,\n ...(configuration ? { configuration } : {}),\n });\n\n await projectDoc.save();\n\n return reply.send(\n formatResponse<EnvironmentAPI[]>({\n message: t({\n en: 'Environment created',\n 'en-GB': 'Environment created',\n fr: 'Environnement créé',\n es: 'Entorno creado',\n ru: 'Среда создана',\n ja: '環境が作成されました',\n ko: '환경이 생성되었습니다',\n zh: '环境已创建',\n de: 'Umgebung erstellt',\n ar: 'تم إنشاء البيئة',\n it: 'Ambiente creato',\n pt: 'Ambiente criado',\n hi: 'वातावरण बनाया गया',\n tr: 'Ortam oluşturuldu',\n pl: 'Środowisko zostało utworzone',\n id: 'Lingkungan dibuat',\n vi: 'Môi trường đã được tạo',\n uk: 'Середовище створено',\n }),\n data: mapProjectToAPI(projectDoc).environments as EnvironmentAPI[],\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Update ───────────────────────────────────────────────────────────────────\n\nexport type UpdateEnvironmentParams = { environmentId: string };\nexport type UpdateEnvironmentBody = Partial<\n Pick<EnvironmentData, 'name' | 'configuration'>\n>;\nexport type UpdateEnvironmentResult = ResponseData<EnvironmentAPI[]>;\n\nexport const updateEnvironment = async (\n request: FastifyRequest<{\n Params: UpdateEnvironmentParams;\n Body: UpdateEnvironmentBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const { environmentId } = request.params;\n const { name, configuration } = request.body;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const environment = (projectDoc.environments ?? []).find(\n (env) => String(env.id) === environmentId\n );\n\n if (!environment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n if (name) (environment as any).name = name.trim().replace(/\\s+/g, '_');\n if (configuration !== undefined)\n (environment as any).configuration = configuration;\n\n await projectDoc.save();\n\n return reply.send(\n formatResponse<EnvironmentAPI[]>({\n message: t({\n en: 'Environment updated',\n 'en-GB': 'Environment updated',\n fr: 'Environnement mis à jour',\n es: 'Entorno actualizado',\n ru: 'Среда обновлена',\n ja: '環境が更新されました',\n ko: '환경이 업데이트되었습니다',\n zh: '环境已更新',\n de: 'Umgebung aktualisiert',\n ar: 'تم تحديث البيئة',\n it: 'Ambiente aggiornato',\n pt: 'Ambiente atualizado',\n hi: 'वातावरण अपडेट किया गया',\n tr: 'Ortam güncellendi',\n pl: 'Środowisko zaktualizowane',\n id: 'Lingkungan diperbarui',\n vi: 'Môi trường đã được cập nhật',\n uk: 'Середовище оновлено',\n }),\n data: mapProjectToAPI(projectDoc).environments as EnvironmentAPI[],\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Delete ───────────────────────────────────────────────────────────────────\n\nexport type DeleteEnvironmentParams = { environmentId: string };\nexport type DeleteEnvironmentResult = ResponseData<EnvironmentAPI[]>;\n\nexport const deleteEnvironment = async (\n request: FastifyRequest<{ Params: DeleteEnvironmentParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, session, roles } = request.session || {};\n const { environmentId } = request.params;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (typeof session === 'undefined')\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:admin'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const environmentIndex = (projectDoc.environments ?? []).findIndex(\n (env) => String(env.id) === environmentId\n );\n\n if (environmentIndex === -1)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n if (projectDoc.environments![environmentIndex]?.isDefault)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'CANNOT_DELETE_DEFAULT_ENVIRONMENT'\n );\n\n projectDoc.environments!.splice(environmentIndex, 1);\n await projectDoc.save();\n\n // If the deleted env was active in this session, reset to production (null)\n if (\n session &&\n String((session as any).activeEnvironmentId) === environmentId\n ) {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeEnvironmentId: null } }\n );\n }\n\n return reply.send(\n formatResponse<EnvironmentAPI[]>({\n message: t({\n en: 'Environment deleted',\n 'en-GB': 'Environment deleted',\n fr: 'Environnement supprimé',\n es: 'Entorno eliminado',\n ru: 'Среда удалена',\n ja: '環境が削除されました',\n ko: '환경이 삭제되었습니다',\n zh: '环境已删除',\n de: 'Umgebung gelöscht',\n ar: 'تم حذف البيئة',\n it: 'Ambiente eliminato',\n pt: 'Ambiente excluído',\n hi: 'वातावरण हटा दिया गया',\n tr: 'Ortam silindi',\n pl: 'Środowisko usunięte',\n id: 'Lingkungan dihapus',\n vi: 'Môi trường đã bị xóa',\n uk: 'Середовище видалено',\n }),\n data: mapProjectToAPI(projectDoc).environments as EnvironmentAPI[],\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Select ───────────────────────────────────────────────────────────────────\n\nexport type SelectEnvironmentParams = { environmentId: string };\nexport type SelectEnvironmentResult = ResponseData<EnvironmentAPI>;\n\nexport const selectEnvironment = async (\n request: FastifyRequest<{ Params: SelectEnvironmentParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, session } = request.session || {};\n const { environmentId } = request.params;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (typeof session === 'undefined')\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const environment = (projectDoc.environments ?? []).find(\n (env) => String(env.id) === environmentId\n );\n\n if (!environment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n // Store null for the default (production) env; the real ObjectId for others\n const sessionEnvironmentId = toSessionEnvironmentId(\n environment.isDefault,\n environmentId\n );\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeEnvironmentId: sessionEnvironmentId } }\n );\n\n const environmentAPI: EnvironmentAPI = {\n ...environment,\n id: String(environment.id),\n } as any;\n\n return reply.send(\n formatResponse<EnvironmentAPI>({\n message: t({\n en: 'Environment selected',\n 'en-GB': 'Environment selected',\n fr: 'Environnement sélectionné',\n es: 'Entorno seleccionado',\n ru: 'Среда выбрана',\n ja: '環境が選択されました',\n ko: '환경이 선택되었습니다',\n zh: '环境已选择',\n de: 'Umgebung ausgewählt',\n ar: 'تم تحديد البيئة',\n it: 'Ambiente selezionato',\n pt: 'Ambiente selecionado',\n hi: 'वातावरण चुना गया',\n tr: 'Ortam seçildi',\n pl: 'Środowisko wybrane',\n id: 'Lingkungan dipilih',\n vi: 'Môi trường đã được chọn',\n uk: 'Середовище вибрано',\n }),\n data: environmentAPI,\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Reset to production ──────────────────────────────────────────────────────\n\nexport type ResetToProductionEnvironmentResult =\n ResponseData<EnvironmentAPI | null>;\n\n/**\n * Resets the active environment to production (the default).\n * Stores null in `session.activeEnvironmentId` per the null = production convention.\n */\nexport const resetToProductionEnvironment = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, session } = request.session || {};\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (typeof session === 'undefined')\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeEnvironmentId: null } }\n );\n\n // Return the default env for display if one exists in DB\n const projectDoc = await projectService.getProjectById(project.id);\n const defaultEnvironment =\n (projectDoc.environments ?? []).find((env) => env.isDefault) ?? null;\n\n const environmentAPI: EnvironmentAPI | null = defaultEnvironment\n ? ({ ...defaultEnvironment, id: String(defaultEnvironment.id) } as any)\n : null;\n\n return reply.send(\n formatResponse<EnvironmentAPI | null>({\n message: t({\n en: 'Switched to production',\n 'en-GB': 'Switched to production',\n fr: 'Basculé sur production',\n es: 'Cambiado a producción',\n ru: 'Переключено на production',\n ja: 'productionに切り替えました',\n ko: 'production으로 전환되었습니다',\n zh: '已切换到 production',\n de: 'Zu production gewechselt',\n ar: 'تم التبديل إلى production',\n it: 'Passato a production',\n pt: 'Mudado para production',\n hi: 'production में बदल दिया गया',\n tr: \"production'a geçildi\",\n pl: 'Przełączono na production',\n id: 'Beralih ke production',\n vi: 'Đã chuyển sang production',\n uk: 'Перемкнуто на production',\n }),\n data: environmentAPI,\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Migrate ──────────────────────────────────────────────────────────────────\n\nexport type MigrateEnvironmentBody = {\n sourceEnvironmentId: string;\n targetEnvironmentId: string;\n strategy: 'overwrite' | 'fill-missing';\n migrateContent?: boolean;\n migrateConfiguration?: boolean;\n};\nexport type MigrateEnvironmentResult = ResponseData<{\n migratedDictionaries: number;\n skippedDictionaries: number;\n configurationMigrated: boolean;\n}>;\n\nexport const migrateEnvironment = async (\n request: FastifyRequest<{ Body: MigrateEnvironmentBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const {\n sourceEnvironmentId,\n targetEnvironmentId,\n strategy,\n migrateContent = true,\n migrateConfiguration = false,\n } = request.body;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n if (!sourceEnvironmentId || !targetEnvironmentId)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n if (sourceEnvironmentId === targetEnvironmentId)\n return ErrorHandler.handleGenericErrorResponse(reply, 'INVALID_MIGRATION');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const projectEnvironments = projectDoc.environments ?? [];\n\n const isProductionSource =\n sourceEnvironmentId === PRODUCTION_ENV_SENTINEL_ID;\n const isProductionTarget =\n targetEnvironmentId === PRODUCTION_ENV_SENTINEL_ID;\n\n const sourceEnvironment = isProductionSource\n ? (projectEnvironments.find((env) => env.isDefault) ?? {\n isDefault: true,\n id: null,\n })\n : projectEnvironments.find(\n (env) => String(env.id) === sourceEnvironmentId\n );\n\n const targetEnvironment = isProductionTarget\n ? (projectEnvironments.find((env) => env.isDefault) ?? {\n isDefault: true,\n id: null,\n })\n : projectEnvironments.find(\n (env) => String(env.id) === targetEnvironmentId\n );\n\n if (!sourceEnvironment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n if (!targetEnvironment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n let migratedDictionaries = 0;\n let skippedDictionaries = 0;\n let configurationMigrated = false;\n\n if (migrateContent) {\n // Use null filter for production (default) env, ObjectId for others\n const sourceDictionaryFilter = {\n projectIds: project.id,\n ...toDictionaryEnvironmentFilter(\n sourceEnvironment.isDefault,\n sourceEnvironmentId\n ),\n };\n\n const targetDictionaryFilter = {\n projectIds: project.id,\n ...toDictionaryEnvironmentFilter(\n targetEnvironment.isDefault,\n targetEnvironmentId\n ),\n };\n\n const [sourceDictionaries, targetDictionaries] = await Promise.all([\n dictionaryService.findDictionaries(sourceDictionaryFilter, 0, 10000),\n dictionaryService.findDictionaries(targetDictionaryFilter, 0, 10000),\n ]);\n\n const targetKeySet = new Set(\n targetDictionaries.map((dictionary) => dictionary.key)\n );\n\n const newDictionaryEnvironmentId = toDictionaryEnvironmentId(\n targetEnvironment.isDefault,\n targetEnvironmentId\n );\n\n for (const sourceDictionary of sourceDictionaries) {\n const existsInTarget = targetKeySet.has(sourceDictionary.key);\n\n if (existsInTarget && strategy === 'fill-missing') {\n skippedDictionaries++;\n continue;\n }\n\n if (existsInTarget && strategy === 'overwrite') {\n const existingDictionary = targetDictionaries.find(\n (dictionary) => dictionary.key === sourceDictionary.key\n );\n if (existingDictionary) {\n await dictionaryService.deleteDictionaryById(\n String(existingDictionary.id)\n );\n }\n }\n\n await dictionaryService.createDictionary({\n key: sourceDictionary.key,\n title: sourceDictionary.title,\n description: sourceDictionary.description,\n tags: sourceDictionary.tags,\n content: sourceDictionary.content as any,\n projectIds: sourceDictionary.projectIds as any[],\n creatorId: user.id,\n environmentId: newDictionaryEnvironmentId,\n });\n\n migratedDictionaries++;\n }\n }\n\n if (migrateConfiguration && (sourceEnvironment as any).configuration) {\n (targetEnvironment as any).configuration = (\n sourceEnvironment as any\n ).configuration;\n await projectDoc.save();\n configurationMigrated = true;\n }\n\n return reply.send(\n formatResponse<{\n migratedDictionaries: number;\n skippedDictionaries: number;\n configurationMigrated: boolean;\n }>({\n message: t({\n en: 'Migration completed',\n 'en-GB': 'Migration completed',\n fr: 'Migration terminée',\n es: 'Migración completada',\n ru: 'Миграция завершена',\n ja: '移行が完了しました',\n ko: '마이그레이션 완료',\n zh: '迁移完成',\n de: 'Migration abgeschlossen',\n ar: 'اكتملت الهجرة',\n it: 'Migrazione completata',\n pt: 'Migração concluída',\n hi: 'माइग्रेशन पूर्ण हुआ',\n tr: 'Geçiş tamamlandı',\n pl: 'Migracja zakończona',\n id: 'Migrasi selesai',\n vi: 'Di chuyển hoàn tất',\n uk: 'Міграцію завершено',\n }),\n data: {\n migratedDictionaries,\n skippedDictionaries,\n configurationMigrated,\n },\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"environment.controller.mjs","names":["projectService.getProjectById","dictionaryService.findDictionaries","dictionaryService.deleteDictionaryById","dictionaryService.createDictionary"],"sources":["../../../src/controllers/environment.controller.ts"],"sourcesContent":["import { SessionModel } from '@schemas/session.schema';\nimport * as dictionaryService from '@services/dictionary.service';\nimport * as projectService from '@services/project.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n mapProjectToAPI,\n PRODUCTION_ENV_SENTINEL_ID,\n} from '@utils/mapper/project';\nimport { hasPermission } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { EnvironmentAPI, EnvironmentData } from '@/types/project.types';\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Returns the value to store in `session.activeEnvironmentId`.\n * Convention: null = production (default env); ObjectId string = any other env.\n */\nconst toSessionEnvironmentId = (\n isDefault: boolean,\n environmentId: string\n): string | null => (isDefault ? null : environmentId);\n\n/**\n * Returns the MongoDB filter for a dictionary's `environmentId` field.\n * Production dictionaries are stored with environmentId = null.\n */\nconst toDictionaryEnvironmentFilter = (\n isDefaultEnvironment: boolean,\n environmentId: string\n) =>\n isDefaultEnvironment || environmentId === PRODUCTION_ENV_SENTINEL_ID\n ? { $or: [{ environmentId: null }, { environmentId: { $exists: false } }] }\n : { environmentId };\n\n/**\n * The environmentId to store on a newly created dictionary.\n * null = production (default); real ObjectId = specific env.\n */\nconst toDictionaryEnvironmentId = (\n isDefaultEnvironment: boolean,\n environmentId: string\n): string | null =>\n isDefaultEnvironment || environmentId === PRODUCTION_ENV_SENTINEL_ID\n ? null\n : environmentId;\n\n// ─── Add ──────────────────────────────────────────────────────────────────────\n\nexport type AddEnvironmentBody = Pick<\n EnvironmentData,\n 'name' | 'configuration'\n>;\nexport type AddEnvironmentResult = ResponseData<EnvironmentAPI[]>;\n\nexport const addEnvironment = async (\n request: FastifyRequest<{ Body: AddEnvironmentBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const { name, configuration } = request.body;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (!name?.trim())\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n\n const sanitizedName = name.trim().replace(/\\s+/g, '_');\n\n const existingNames = (projectDoc.environments ?? []).map((environment) =>\n environment.name.toLowerCase()\n );\n if (existingNames.includes(sanitizedName.toLowerCase()))\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_ALREADY_EXISTS'\n );\n\n (projectDoc.environments as any[]).push({\n name: sanitizedName,\n isDefault: false,\n ...(configuration ? { configuration } : {}),\n });\n\n await projectDoc.save();\n\n return reply.send(\n formatResponse<EnvironmentAPI[]>({\n message: t({\n en: 'Environment created',\n 'en-GB': 'Environment created',\n fr: 'Environnement créé',\n es: 'Entorno creado',\n ru: 'Среда создана',\n ja: '環境が作成されました',\n ko: '환경이 생성되었습니다',\n zh: '环境已创建',\n de: 'Umgebung erstellt',\n ar: 'تم إنشاء البيئة',\n it: 'Ambiente creato',\n pt: 'Ambiente criado',\n hi: 'वातावरण बनाया गया',\n tr: 'Ortam oluşturuldu',\n pl: 'Środowisko zostało utworzone',\n id: 'Lingkungan dibuat',\n vi: 'Môi trường đã được tạo',\n uk: 'Середовище створено',\n }),\n data: mapProjectToAPI(projectDoc).environments as EnvironmentAPI[],\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Update ───────────────────────────────────────────────────────────────────\n\nexport type UpdateEnvironmentParams = { environmentId: string };\nexport type UpdateEnvironmentBody = Partial<\n Pick<EnvironmentData, 'name' | 'configuration'>\n>;\nexport type UpdateEnvironmentResult = ResponseData<EnvironmentAPI[]>;\n\nexport const updateEnvironment = async (\n request: FastifyRequest<{\n Params: UpdateEnvironmentParams;\n Body: UpdateEnvironmentBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const { environmentId } = request.params;\n const { name, configuration } = request.body;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const environment = (projectDoc.environments ?? []).find(\n (env) => String(env.id) === environmentId\n );\n\n if (!environment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n if (name) (environment as any).name = name.trim().replace(/\\s+/g, '_');\n if (configuration !== undefined)\n (environment as any).configuration = configuration;\n\n await projectDoc.save();\n\n return reply.send(\n formatResponse<EnvironmentAPI[]>({\n message: t({\n en: 'Environment updated',\n 'en-GB': 'Environment updated',\n fr: 'Environnement mis à jour',\n es: 'Entorno actualizado',\n ru: 'Среда обновлена',\n ja: '環境が更新されました',\n ko: '환경이 업데이트되었습니다',\n zh: '环境已更新',\n de: 'Umgebung aktualisiert',\n ar: 'تم تحديث البيئة',\n it: 'Ambiente aggiornato',\n pt: 'Ambiente atualizado',\n hi: 'वातावरण अपडेट किया गया',\n tr: 'Ortam güncellendi',\n pl: 'Środowisko zaktualizowane',\n id: 'Lingkungan diperbarui',\n vi: 'Môi trường đã được cập nhật',\n uk: 'Середовище оновлено',\n }),\n data: mapProjectToAPI(projectDoc).environments as EnvironmentAPI[],\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Delete ───────────────────────────────────────────────────────────────────\n\nexport type DeleteEnvironmentParams = { environmentId: string };\nexport type DeleteEnvironmentResult = ResponseData<EnvironmentAPI[]>;\n\nexport const deleteEnvironment = async (\n request: FastifyRequest<{ Params: DeleteEnvironmentParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, session, roles } = request.session || {};\n const { environmentId } = request.params;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (typeof session === 'undefined')\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:admin'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const environmentIndex = (projectDoc.environments ?? []).findIndex(\n (env) => String(env.id) === environmentId\n );\n\n if (environmentIndex === -1)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n if (projectDoc.environments![environmentIndex]?.isDefault)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'CANNOT_DELETE_DEFAULT_ENVIRONMENT'\n );\n\n projectDoc.environments!.splice(environmentIndex, 1);\n await projectDoc.save();\n\n // If the deleted env was active in this session, reset to production (null)\n if (\n session &&\n String((session as any).activeEnvironmentId) === environmentId\n ) {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeEnvironmentId: null } }\n );\n }\n\n return reply.send(\n formatResponse<EnvironmentAPI[]>({\n message: t({\n en: 'Environment deleted',\n 'en-GB': 'Environment deleted',\n fr: 'Environnement supprimé',\n es: 'Entorno eliminado',\n ru: 'Среда удалена',\n ja: '環境が削除されました',\n ko: '환경이 삭제되었습니다',\n zh: '环境已删除',\n de: 'Umgebung gelöscht',\n ar: 'تم حذف البيئة',\n it: 'Ambiente eliminato',\n pt: 'Ambiente excluído',\n hi: 'वातावरण हटा दिया गया',\n tr: 'Ortam silindi',\n pl: 'Środowisko usunięte',\n id: 'Lingkungan dihapus',\n vi: 'Môi trường đã bị xóa',\n uk: 'Середовище видалено',\n }),\n data: mapProjectToAPI(projectDoc).environments as EnvironmentAPI[],\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Select ───────────────────────────────────────────────────────────────────\n\nexport type SelectEnvironmentParams = { environmentId: string };\nexport type SelectEnvironmentResult = ResponseData<EnvironmentAPI>;\n\nexport const selectEnvironment = async (\n request: FastifyRequest<{ Params: SelectEnvironmentParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, session } = request.session || {};\n const { environmentId } = request.params;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (typeof session === 'undefined')\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const environment = (projectDoc.environments ?? []).find(\n (env) => String(env.id) === environmentId\n );\n\n if (!environment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n // Store null for the default (production) env; the real ObjectId for others\n const sessionEnvironmentId = toSessionEnvironmentId(\n environment.isDefault,\n environmentId\n );\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeEnvironmentId: sessionEnvironmentId } }\n );\n\n const environmentAPI: EnvironmentAPI = {\n ...environment,\n id: String(environment.id),\n } as any;\n\n return reply.send(\n formatResponse<EnvironmentAPI>({\n message: t({\n en: 'Environment selected',\n 'en-GB': 'Environment selected',\n fr: 'Environnement sélectionné',\n es: 'Entorno seleccionado',\n ru: 'Среда выбрана',\n ja: '環境が選択されました',\n ko: '환경이 선택되었습니다',\n zh: '环境已选择',\n de: 'Umgebung ausgewählt',\n ar: 'تم تحديد البيئة',\n it: 'Ambiente selezionato',\n pt: 'Ambiente selecionado',\n hi: 'वातावरण चुना गया',\n tr: 'Ortam seçildi',\n pl: 'Środowisko wybrane',\n id: 'Lingkungan dipilih',\n vi: 'Môi trường đã được chọn',\n uk: 'Середовище вибрано',\n }),\n data: environmentAPI,\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Reset to production ──────────────────────────────────────────────────────\n\nexport type ResetToProductionEnvironmentResult =\n ResponseData<EnvironmentAPI | null>;\n\n/**\n * Resets the active environment to production (the default).\n * Stores null in `session.activeEnvironmentId` per the null = production convention.\n */\nexport const resetToProductionEnvironment = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, session } = request.session || {};\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (typeof session === 'undefined')\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeEnvironmentId: null } }\n );\n\n // Return the default env for display if one exists in DB\n const projectDoc = await projectService.getProjectById(project.id);\n const defaultEnvironment =\n (projectDoc.environments ?? []).find((env) => env.isDefault) ?? null;\n\n const environmentAPI: EnvironmentAPI | null = defaultEnvironment\n ? ({ ...defaultEnvironment, id: String(defaultEnvironment.id) } as any)\n : null;\n\n return reply.send(\n formatResponse<EnvironmentAPI | null>({\n message: t({\n en: 'Switched to production',\n 'en-GB': 'Switched to production',\n fr: 'Basculé sur production',\n es: 'Cambiado a producción',\n ru: 'Переключено на production',\n ja: 'productionに切り替えました',\n ko: 'production으로 전환되었습니다',\n zh: '已切换到 production',\n de: 'Zu production gewechselt',\n ar: 'تم التبديل إلى production',\n it: 'Passato a production',\n pt: 'Mudado para production',\n hi: 'production में बदल दिया गया',\n tr: \"production'a geçildi\",\n pl: 'Przełączono na production',\n id: 'Beralih ke production',\n vi: 'Đã chuyển sang production',\n uk: 'Перемкнуто на production',\n }),\n data: environmentAPI,\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─── Migrate ──────────────────────────────────────────────────────────────────\n\nexport type MigrateEnvironmentBody = {\n sourceEnvironmentId: string;\n targetEnvironmentId: string;\n strategy: 'overwrite' | 'fill-missing';\n migrateContent?: boolean;\n migrateConfiguration?: boolean;\n};\nexport type MigrateEnvironmentResult = ResponseData<{\n migratedDictionaries: number;\n skippedDictionaries: number;\n configurationMigrated: boolean;\n}>;\n\nexport const migrateEnvironment = async (\n request: FastifyRequest<{ Body: MigrateEnvironmentBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const {\n sourceEnvironmentId,\n targetEnvironmentId,\n strategy,\n migrateContent = true,\n migrateConfiguration = false,\n } = request.body;\n\n if (!user)\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n if (!project)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n )\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n if (!sourceEnvironmentId || !targetEnvironmentId)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n if (sourceEnvironmentId === targetEnvironmentId)\n return ErrorHandler.handleGenericErrorResponse(reply, 'INVALID_MIGRATION');\n\n try {\n const projectDoc = await projectService.getProjectById(project.id);\n const projectEnvironments = projectDoc.environments ?? [];\n\n const isProductionSource =\n sourceEnvironmentId === PRODUCTION_ENV_SENTINEL_ID;\n const isProductionTarget =\n targetEnvironmentId === PRODUCTION_ENV_SENTINEL_ID;\n\n const sourceEnvironment = isProductionSource\n ? (projectEnvironments.find((env) => env.isDefault) ?? {\n isDefault: true,\n id: null,\n })\n : projectEnvironments.find(\n (env) => String(env.id) === sourceEnvironmentId\n );\n\n const targetEnvironment = isProductionTarget\n ? (projectEnvironments.find((env) => env.isDefault) ?? {\n isDefault: true,\n id: null,\n })\n : projectEnvironments.find(\n (env) => String(env.id) === targetEnvironmentId\n );\n\n if (!sourceEnvironment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n if (!targetEnvironment)\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ENVIRONMENT_NOT_FOUND'\n );\n\n let migratedDictionaries = 0;\n let skippedDictionaries = 0;\n let configurationMigrated = false;\n\n if (migrateContent) {\n // Use null filter for production (default) env, ObjectId for others\n const sourceDictionaryFilter = {\n projectIds: project.id,\n ...toDictionaryEnvironmentFilter(\n sourceEnvironment.isDefault,\n sourceEnvironmentId\n ),\n };\n\n const targetDictionaryFilter = {\n projectIds: project.id,\n ...toDictionaryEnvironmentFilter(\n targetEnvironment.isDefault,\n targetEnvironmentId\n ),\n };\n\n const [sourceDictionaries, targetDictionaries] = await Promise.all([\n dictionaryService.findDictionaries(sourceDictionaryFilter, 0, 10000),\n dictionaryService.findDictionaries(targetDictionaryFilter, 0, 10000),\n ]);\n\n const targetKeySet = new Set(\n targetDictionaries.map((dictionary) => dictionary.key)\n );\n\n const newDictionaryEnvironmentId = toDictionaryEnvironmentId(\n targetEnvironment.isDefault,\n targetEnvironmentId\n );\n\n for (const sourceDictionary of sourceDictionaries) {\n const existsInTarget = targetKeySet.has(sourceDictionary.key);\n\n if (existsInTarget && strategy === 'fill-missing') {\n skippedDictionaries++;\n continue;\n }\n\n if (existsInTarget && strategy === 'overwrite') {\n const existingDictionary = targetDictionaries.find(\n (dictionary) => dictionary.key === sourceDictionary.key\n );\n if (existingDictionary) {\n await dictionaryService.deleteDictionaryById(\n String(existingDictionary.id)\n );\n }\n }\n\n await dictionaryService.createDictionary({\n key: sourceDictionary.key,\n title: sourceDictionary.title,\n description: sourceDictionary.description,\n tags: sourceDictionary.tags,\n content: sourceDictionary.content as any,\n projectIds: sourceDictionary.projectIds as any[],\n creatorId: user.id,\n environmentId: newDictionaryEnvironmentId,\n });\n\n migratedDictionaries++;\n }\n }\n\n if (migrateConfiguration && (sourceEnvironment as any).configuration) {\n (targetEnvironment as any).configuration = (\n sourceEnvironment as any\n ).configuration;\n await projectDoc.save();\n configurationMigrated = true;\n }\n\n return reply.send(\n formatResponse<{\n migratedDictionaries: number;\n skippedDictionaries: number;\n configurationMigrated: boolean;\n }>({\n message: t({\n en: 'Migration completed',\n 'en-GB': 'Migration completed',\n fr: 'Migration terminée',\n es: 'Migración completada',\n ru: 'Миграция завершена',\n ja: '移行が完了しました',\n ko: '마이그레이션 완료',\n zh: '迁移完成',\n de: 'Migration abgeschlossen',\n ar: 'اكتملت الهجرة',\n it: 'Migrazione completata',\n pt: 'Migração concluída',\n hi: 'माइग्रेशन पूर्ण हुआ',\n tr: 'Geçiş tamamlandı',\n pl: 'Migracja zakończona',\n id: 'Migrasi selesai',\n vi: 'Di chuyển hoàn tất',\n uk: 'Міграцію завершено',\n }),\n data: {\n migratedDictionaries,\n skippedDictionaries,\n configurationMigrated,\n },\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"eventListener.controller.mjs","names":[],"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.session || {};\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,IAAI,UAIC,CAAC;AACN,MAAM,sBAAsB;AAO5B,MAAa,wBAAwB,SAAoC;CACvE,MAAM,aAAa,KAAK,SAAS,QAAQ,IAAI,WAAW,UAAU;CAElE,MAAM,kBAAkB,QAAQ,QAAQ,WACtC,WAAW,KAAK,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,OAAO,OAAO,SAAS,CAAC,CACtE;CAEA,MAAM,OAA2B,KAAK,KAAK,SAAS;EAClD,QAAQ;EACR,QAAQ,IAAI;EACZ,MAAM,IAAI;CACZ,EAAE;CAEF,QAAQ,eAAe;EACrB,KAAK,MAAM,UAAU,iBACnB,OAAO,IAAI,IAAI,MAAM,SAAS,KAAK,UAAU,IAAI,EAAE,KAAK;CAE5D,CAAC;AACH;;;;AAOA,MAAa,kBAAkB,OAC7B,SACA,UACG;CACH,MAAM,EAAE,YAAY,QAAQ,WAAW,CAAC;CAExC,IAAI,QAAQ,UAAU,qBACpB,OAAO,aAAa,2BAClB,OACA,sBACF;CAIF,MAAM,IAAI,UAAU,gBAAgB,iCAAiC;CACrE,MAAM,IAAI,UAAU,iBAAiB,wBAAwB;CAC7D,MAAM,IAAI,UAAU,cAAc,YAAY;CAC9C,MAAM,IAAI,UAAU,qBAAqB,IAAI;CAG7C,MAAM,IAAI,MAAM,OAAO;CACvB,MAAM,IAAI,eAAe;CAEzB,MAAM,WAAW,KAAK,IAAI;CAG1B,MAAM,YAAY;EAChB,IAAI;EACJ,WAAW,OAAO,SAAS,EAAE;EAC7B,KAAK,EAAE,KAAK,MAAM,IAAI;CACxB;CACA,QAAQ,KAAK,SAAS;CAEtB,OAAO,KACL,+CAA+C,QAAQ,UAAU,GACnE;CAGA,QAAQ,IAAI,GAAG,eAAe;EAC5B,UAAU,QAAQ,QAAQ,WAAW,OAAO,OAAO,QAAQ;CAC7D,CAAC;AACH"}
1
+ {"version":3,"file":"eventListener.controller.mjs","names":[],"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.session || {};\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,IAAI,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,MAAM,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,WAAW,EAAE;AAEzC,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"github.controller.mjs","names":["githubService.getAuthorizationUrl","githubService.exchangeCodeForToken","githubService.getGitHubTokenFromUser","githubService.getUserRepos","githubService.checkIntlayerConfig","githubService.getRepositoryFileContents"],"sources":["../../../src/controllers/github.controller.ts"],"sourcesContent":["import * as githubService from '@services/github.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitHubGetAuthUrlQuerystring = {\n redirectUri: string;\n login?: string;\n};\n\nexport type GitHubGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitHubGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = githubService.getAuthorizationUrl(redirectUri, login);\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubAuthCallbackBody = {\n code: string;\n};\n\nexport type GitHubAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitHubAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code } = request.body;\n\n if (!code) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CODE_MISSING'\n );\n }\n\n try {\n const token = await githubService.exchangeCodeForToken(code);\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubListReposQuerystring = {\n token?: string;\n};\n\nexport type GitHubListReposResult = ResponseData<\n githubService.GitHubRepository[]\n>;\n\nexport const listRepos = async (\n request: FastifyRequest<{ Querystring: GitHubListReposQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token } = request.query;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await githubService.getGitHubTokenFromUser(String(userId))) ??\n undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_TOKEN_MISSING'\n );\n }\n\n const repos = await githubService.getUserRepos(accessToken);\n const responseData = formatResponse<githubService.GitHubRepository[]>({\n data: repos,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubCheckConfigBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n};\n\nexport type GitHubCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[]; // Changed from single path to array\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitHubCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, owner, repository, branch = 'main' } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await githubService.getGitHubTokenFromUser(String(userId))) ??\n undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n // Returns array of strings\n const configPaths = await githubService.checkIntlayerConfig(\n accessToken,\n owner,\n repository,\n branch\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubGetConfigFileBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n path?: string;\n};\n\nexport type GitHubGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitHubGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n owner,\n repository,\n branch = 'main',\n path = 'intlayer.config.ts',\n } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await githubService.getGitHubTokenFromUser(String(userId))) ??\n undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await githubService.getRepositoryFileContents(\n accessToken,\n owner,\n repository,\n path,\n branch\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport const getToken = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n try {\n const userId = request.session?.user?.id;\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const token = await githubService.getGitHubTokenFromUser(String(userId));\n\n if (!token) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_TOKEN_MISSING'\n );\n }\n\n const responseData = formatResponse<{ token: string }>({\n data: { token },\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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"}
1
+ {"version":3,"file":"github.controller.mjs","names":["githubService.getAuthorizationUrl","githubService.exchangeCodeForToken","githubService.getGitHubTokenFromUser","githubService.getUserRepos","githubService.checkIntlayerConfig","githubService.getRepositoryFileContents"],"sources":["../../../src/controllers/github.controller.ts"],"sourcesContent":["import * as githubService from '@services/github.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitHubGetAuthUrlQuerystring = {\n redirectUri: string;\n login?: string;\n};\n\nexport type GitHubGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitHubGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = githubService.getAuthorizationUrl(redirectUri, login);\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubAuthCallbackBody = {\n code: string;\n};\n\nexport type GitHubAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitHubAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code } = request.body;\n\n if (!code) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CODE_MISSING'\n );\n }\n\n try {\n const token = await githubService.exchangeCodeForToken(code);\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubListReposQuerystring = {\n token?: string;\n};\n\nexport type GitHubListReposResult = ResponseData<\n githubService.GitHubRepository[]\n>;\n\nexport const listRepos = async (\n request: FastifyRequest<{ Querystring: GitHubListReposQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token } = request.query;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await githubService.getGitHubTokenFromUser(String(userId))) ??\n undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_TOKEN_MISSING'\n );\n }\n\n const repos = await githubService.getUserRepos(accessToken);\n const responseData = formatResponse<githubService.GitHubRepository[]>({\n data: repos,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubCheckConfigBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n};\n\nexport type GitHubCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[]; // Changed from single path to array\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitHubCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, owner, repository, branch = 'main' } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await githubService.getGitHubTokenFromUser(String(userId))) ??\n undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n // Returns array of strings\n const configPaths = await githubService.checkIntlayerConfig(\n accessToken,\n owner,\n repository,\n branch\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubGetConfigFileBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n path?: string;\n};\n\nexport type GitHubGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitHubGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n owner,\n repository,\n branch = 'main',\n path = 'intlayer.config.ts',\n } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await githubService.getGitHubTokenFromUser(String(userId))) ??\n undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await githubService.getRepositoryFileContents(\n accessToken,\n owner,\n repository,\n path,\n branch\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport const getToken = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n try {\n const userId = request.session?.user?.id;\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const token = await githubService.getGitHubTokenFromUser(String(userId));\n\n if (!token) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_TOKEN_MISSING'\n );\n }\n\n const responseData = formatResponse<{ token: string }>({\n data: { token },\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gitlab.controller.mjs","names":["gitlabService.getAuthorizationUrl","gitlabService.exchangeCodeForToken","gitlabService.getUserProjects","gitlabService.checkIntlayerConfig","gitlabService.getRepositoryFileContents"],"sources":["../../../src/controllers/gitlab.controller.ts"],"sourcesContent":["import * as gitlabService from '@services/gitlab.service';\nimport { getGitLabTokenFromUser } from '@services/gitlab.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitLabGetAuthUrlQuerystring = {\n redirectUri: string;\n instanceUrl?: string;\n login?: string;\n};\n\nexport type GitLabGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitLabGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, instanceUrl, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = gitlabService.getAuthorizationUrl(\n redirectUri,\n instanceUrl,\n login\n );\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabAuthCallbackBody = {\n code: string;\n redirectUri: string;\n instanceUrl?: string;\n};\n\nexport type GitLabAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitLabAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code, redirectUri, instanceUrl } = request.body;\n\n if (!code || !redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CODE_MISSING'\n );\n }\n\n try {\n const token = await gitlabService.exchangeCodeForToken(\n code,\n redirectUri,\n instanceUrl\n );\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabListProjectsQuerystring = {\n token?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabListProjectsResult = ResponseData<\n gitlabService.GitLabProject[]\n>;\n\nexport const listProjects = async (\n request: FastifyRequest<{ Querystring: GitLabListProjectsQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, instanceUrl } = request.query;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_TOKEN_MISSING'\n );\n }\n\n const projects = await gitlabService.getUserProjects(\n accessToken,\n instanceUrl\n );\n const responseData = formatResponse<gitlabService.GitLabProject[]>({\n data: projects,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabCheckConfigBody = {\n token?: string;\n projectId: number;\n branch?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[];\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a GitLab repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitLabCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, projectId, branch = 'main', instanceUrl } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n const configPaths = await gitlabService.checkIntlayerConfig(\n accessToken,\n projectId,\n branch,\n instanceUrl\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabGetConfigFileBody = {\n token?: string;\n projectId: number;\n branch?: string;\n path?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitLabGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n projectId,\n branch = 'main',\n path = 'intlayer.config.ts',\n instanceUrl,\n } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await gitlabService.getRepositoryFileContents(\n accessToken,\n projectId,\n path,\n branch,\n instanceUrl\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAgBA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,aAAa,aAAa,UAAU,QAAQ;CAEpD,IAAI,CAAC,aACH,OAAO,aAAa,2BAClB,OACA,6BACF;CAGF,IAAI;EAMF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SANMA,oBACd,aACA,aACA,KAGc,EAAE,EAClB,CAAC;EACD,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;AAYA,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,MAAM,aAAa,gBAAgB,QAAQ;CAEnD,IAAI,CAAC,QAAQ,CAAC,aACZ,OAAO,aAAa,2BAClB,OACA,qBACF;CAGF,IAAI;EAMF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,aANUC,qBAClB,MACA,aACA,WACF,EAEgB,EAChB,CAAC;EACD,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;AAWA,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,gBAAgB,QAAQ;CACvC,MAAM,SAAS,QAAQ,SAAS,MAAM;CAEtC,IAAI;EACF,IAAI,cAAkC;EAEtC,IAAI,CAAC,eAAe,QAClB,cAAe,MAAM,uBAAuB,OAAO,MAAM,CAAC,KAAM;EAGlE,IAAI,CAAC,aACH,OAAO,aAAa,2BAClB,OACA,sBACF;EAOF,MAAM,eAAe,eAA8C,EACjE,MAAM,MALeC,gBACrB,aACA,WACF,EAGA,CAAC;EACD,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;;;;AAiBA,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,WAAW,SAAS,QAAQ,gBAAgB,QAAQ;CACnE,MAAM,SAAS,QAAQ,SAAS,MAAM;CAEtC,IAAI;EACF,IAAI,cAAkC;EAEtC,IAAI,CAAC,eAAe,QAClB,cAAe,MAAM,uBAAuB,OAAO,MAAM,CAAC,KAAM;EAGlE,IAAI,CAAC,eAAe,CAAC,WACnB,OAAO,aAAa,2BAClB,OACA,oCACF;EAGF,MAAM,cAAc,MAAMC,oBACxB,aACA,WACA,QACA,WACF;EAEA,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;EACf,EACF,CAAC;EAED,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;AAcA,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,WACA,SAAS,QACT,OAAO,sBACP,gBACE,QAAQ;CACZ,MAAM,SAAS,QAAQ,SAAS,MAAM;CAEtC,IAAI;EACF,IAAI,cAAkC;EAEtC,IAAI,CAAC,eAAe,QAClB,cAAe,MAAM,uBAAuB,OAAO,MAAM,CAAC,KAAM;EAGlE,IAAI,CAAC,eAAe,CAAC,WACnB,OAAO,aAAa,2BAClB,OACA,uCACF;EAGF,MAAM,UAAU,MAAMC,0BACpB,aACA,WACA,MACA,QACA,WACF;EAEA,IAAI,CAAC,SACH,OAAO,aAAa,2BAClB,OACA,8BACF;EAGF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,QAAQ,EAClB,CAAC;EAED,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF"}
1
+ {"version":3,"file":"gitlab.controller.mjs","names":["gitlabService.getAuthorizationUrl","gitlabService.exchangeCodeForToken","gitlabService.getUserProjects","gitlabService.checkIntlayerConfig","gitlabService.getRepositoryFileContents"],"sources":["../../../src/controllers/gitlab.controller.ts"],"sourcesContent":["import * as gitlabService from '@services/gitlab.service';\nimport { getGitLabTokenFromUser } from '@services/gitlab.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitLabGetAuthUrlQuerystring = {\n redirectUri: string;\n instanceUrl?: string;\n login?: string;\n};\n\nexport type GitLabGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitLabGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, instanceUrl, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = gitlabService.getAuthorizationUrl(\n redirectUri,\n instanceUrl,\n login\n );\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabAuthCallbackBody = {\n code: string;\n redirectUri: string;\n instanceUrl?: string;\n};\n\nexport type GitLabAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitLabAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code, redirectUri, instanceUrl } = request.body;\n\n if (!code || !redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CODE_MISSING'\n );\n }\n\n try {\n const token = await gitlabService.exchangeCodeForToken(\n code,\n redirectUri,\n instanceUrl\n );\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabListProjectsQuerystring = {\n token?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabListProjectsResult = ResponseData<\n gitlabService.GitLabProject[]\n>;\n\nexport const listProjects = async (\n request: FastifyRequest<{ Querystring: GitLabListProjectsQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, instanceUrl } = request.query;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_TOKEN_MISSING'\n );\n }\n\n const projects = await gitlabService.getUserProjects(\n accessToken,\n instanceUrl\n );\n const responseData = formatResponse<gitlabService.GitLabProject[]>({\n data: projects,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabCheckConfigBody = {\n token?: string;\n projectId: number;\n branch?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[];\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a GitLab repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitLabCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, projectId, branch = 'main', instanceUrl } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n const configPaths = await gitlabService.checkIntlayerConfig(\n accessToken,\n projectId,\n branch,\n instanceUrl\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabGetConfigFileBody = {\n token?: string;\n projectId: number;\n branch?: string;\n path?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitLabGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n projectId,\n branch = 'main',\n path = 'intlayer.config.ts',\n instanceUrl,\n } = request.body;\n const userId = request.session?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await gitlabService.getRepositoryFileContents(\n accessToken,\n projectId,\n path,\n branch,\n instanceUrl\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAgBA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,aAAa,aAAa,UAAU,QAAQ;AAEpD,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,8BACD;AAGH,KAAI;EAMF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SANMA,oBACd,aACA,aACA,MAGe,EAAE,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAcxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,MAAM,aAAa,gBAAgB,QAAQ;AAEnD,KAAI,CAAC,QAAQ,CAAC,YACZ,QAAO,aAAa,2BAClB,OACA,sBACD;AAGH,KAAI;EAMF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,aANUC,qBAClB,MACA,aACA,YACD,EAEgB,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAaxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,gBAAgB,QAAQ;CACvC,MAAM,SAAS,QAAQ,SAAS,MAAM;AAEtC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,uBACD;EAOH,MAAM,eAAe,eAA8C,EACjE,MAAM,MALeC,gBACrB,aACA,YACD,EAGA,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,WAAW,SAAS,QAAQ,gBAAgB,QAAQ;CACnE,MAAM,SAAS,QAAQ,SAAS,MAAM;AAEtC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,UACnB,QAAO,aAAa,2BAClB,OACA,qCACD;EAGH,MAAM,cAAc,MAAMC,oBACxB,aACA,WACA,QACA,YACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,WACA,SAAS,QACT,OAAO,sBACP,gBACE,QAAQ;CACZ,MAAM,SAAS,QAAQ,SAAS,MAAM;AAEtC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,UACnB,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,UAAU,MAAMC,0BACpB,aACA,WACA,MACA,QACA,YACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,+BACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"newsletter.controller.mjs","names":["userService.getUserByEmail","userService.createUser","userService.updateUserById","userService.getUserById"],"sources":["../../../src/controllers/newsletter.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { hasPermission } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { EmailsList, UserAPI } from '@/types/user.types';\n\nexport type NewsletterSubscriptionBody = {\n email: string;\n emailList: EmailsList | EmailsList[];\n};\nexport type NewsletterSubscriptionResult = ResponseData<UserAPI>;\n\n/**\n * Subscribes a user to the newsletter.\n * If the user doesn't exist, creates a new user.\n * If the user exists, updates their newsletter subscription to true.\n */\nexport const subscribeToNewsletter = async (\n request: FastifyRequest<{ Body: NewsletterSubscriptionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { roles } = request.session || {};\n const { email, emailList } = request.body;\n\n if (!email) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, true])\n ) as Record<EmailsList, boolean>;\n\n try {\n // Check if user exists\n let user = await userService.getUserByEmail(email);\n\n if (!user) {\n user = await userService.createUser({\n email,\n emailsList: emailsListObject,\n });\n\n logger.info(`New user created and subscribed to newsletter: ${email}`);\n } else {\n if (\n !hasPermission(\n roles || [],\n 'user:write'\n )({\n ...request.session,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n // Update existing user's newsletter subscription\n user = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User subscribed to newsletter: ${email}`);\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully subscribed to newsletter',\n 'en-GB': 'Successfully subscribed to newsletter',\n fr: 'Abonnement à la newsletter réussi',\n es: 'Suscripción al boletín exitosa',\n ru: 'Вы успешно подписались на рассылку',\n ja: 'ニュースレターの購読に成功しました',\n ko: '뉴스레터 구독에 성공했습니다',\n zh: '成功订阅通讯',\n de: 'Erfolgreich zum Newsletter angemeldet',\n ar: 'تم الاشتراك في النشرة الإخبارية بنجاح',\n it: 'Iscrizione alla newsletter riuscita',\n pt: 'Inscrição na newsletter realizada com sucesso',\n hi: 'न्यूज़लेटर की सदस्यता सफलतापूर्वक ली गई',\n tr: 'Bültene başarıyla abone olundu',\n pl: 'Pomyślnie zasubskrybowano newsletter',\n id: 'Berhasil berlangganan buletin',\n vi: 'Đăng ký nhận bản tin thành công',\n uk: 'Ви успішно підписалися на розсилку',\n }),\n description: t({\n en: 'You have been successfully subscribed to our newsletter',\n 'en-GB': 'You have been successfully subscribed to our newsletter',\n fr: 'Vous avez été abonné avec succès à notre newsletter',\n es: 'Te has suscrito exitosamente a nuestro boletín',\n ru: 'Вы были успешно подписаны на нашу рассылку',\n ja: '当社のニュースレターに正常に登録されました',\n ko: '뉴스레터에 성공적으로 구독되었습니다',\n zh: '您已成功订阅我们的通讯',\n de: 'Sie wurden erfolgreich für unseren Newsletter angemeldet',\n ar: 'لقد تم اشتراكك بنجاح في نشرتنا الإخبارية',\n it: 'Ti sei iscritto con successo alla nostra newsletter',\n pt: 'Você foi inscrito com sucesso em nossa newsletter',\n hi: 'आप हमारे न्यूज़लेटर के लिए सफलतापूर्वक पंजीकृत हो गए हैं',\n tr: 'Bültenimize başarıyla abone oldunuz',\n pl: 'Zostałeś pomyślnie zapisany do naszego newslettera',\n id: 'Anda telah berhasil berlangganan buletin kami',\n vi: 'Bạn đã đăng ký nhận bản tin của chúng tôi thành công',\n uk: 'Ви успішно підписалися на нашу розсилку',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type NewsletterUnsubscriptionBody = {\n userId: string;\n emailList: EmailsList | EmailsList[];\n};\n\n/**\n * Unsubscribes a user from the newsletter.\n * Only works if the user exists.\n */\nexport const unsubscribeFromNewsletter = async (\n request: FastifyRequest<{ Body: NewsletterUnsubscriptionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { userId, emailList } = request.body;\n const { roles } = request.session || {};\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n try {\n // Check if user exists\n const user = await userService.getUserById(userId);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:write'\n )({\n ...request.session,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, false])\n ) as Record<EmailsList, boolean>;\n\n // Update user's newsletter subscription to false\n const updatedUser = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User unsubscribed from newsletter: ${updatedUser.email}`);\n\n const formattedUser = mapUserToAPI(updatedUser);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully unsubscribed from newsletter',\n 'en-GB': 'Successfully unsubscribed from newsletter',\n fr: 'Désabonnement de la newsletter réussi',\n es: 'Cancelación de suscripción al boletín exitosa',\n ru: 'Вы успешно отписались от рассылки',\n ja: 'ニュースレターの解約に成功しました',\n ko: '뉴스레터 구독 해지에 성공했습니다',\n zh: '成功取消订阅通讯',\n de: 'Erfolgreich vom Newsletter abgemeldet',\n ar: 'تم إلغاء الاشتراك في النشرة الإخبارية بنجاح',\n it: 'Disiscrizione dalla newsletter riuscita',\n pt: 'Cancelamento da inscrição na newsletter realizado com sucesso',\n hi: 'न्यूज़लेटर की सदस्यता सफलतापूर्वक समाप्त की गई',\n tr: 'Bülten aboneliğinden başarıyla çıkıldı',\n pl: 'Pomyślnie wypisano się z newslettera',\n id: 'Berhasil membatalkan langganan buletin',\n vi: 'Hủy đăng ký nhận bản tin thành công',\n uk: 'Ви успішно відписалися від розсилки',\n }),\n description: t({\n en: 'You have been successfully unsubscribed from our newsletter',\n 'en-GB': 'You have been successfully unsubscribed from our newsletter',\n fr: 'Vous avez été désabonné avec succès de notre newsletter',\n es: 'Te has desuscrito exitosamente de nuestro boletín',\n ru: 'Вы успешно отписались от нашей рассылки',\n ja: '当社のニュースレターから正常に登録解除されました',\n ko: '뉴스레터에서 성공적으로 해지되었습니다',\n zh: '您已成功取消订阅我们的通讯',\n de: 'Sie wurden erfolgreich von unserem Newsletter abgemeldet',\n ar: 'لقد تم إلغاء اشتراكك بنجاح من نشرتنا الإخبارية',\n it: 'Ti sei disiscritto con successo dalla nostra newsletter',\n pt: 'Você foi cancelado com sucesso da nossa newsletter',\n hi: 'आपने हमारे न्यूज़लेटर से सफलतापूर्वक अपनी सदस्यता समाप्त कर ली है',\n tr: 'Bülten aboneliğinden başarıyla ayrıldınız',\n pl: 'Zostałeś pomyślnie wypisany z naszego newslettera',\n id: 'Anda telah berhasil membatalkan langganan buletin kami',\n vi: 'Bạn đã hủy đăng ký nhận bản tin của chúng tôi thành công',\n uk: 'Ви успішно відписалися від нашої розсилки',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n/**\n * Gets the newsletter subscription status for a user.\n */\nexport const getNewsletterStatus = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const email = _request.session?.user?.email;\n const { roles } = _request.session || {};\n\n if (!email) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n try {\n const user = await userService.getUserByEmail(email);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:read'\n )({\n ..._request.session,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Newsletter subscription status retrieved',\n 'en-GB': 'Newsletter subscription status retrieved',\n fr: \"Statut d'abonnement à la newsletter récupéré\",\n es: 'Estado de suscripción al boletín obtenido',\n ru: 'Статус подписки на рассылку получен',\n ja: 'ニュースレターの購読ステータスが取得されました',\n ko: '뉴스레터 구독 상태가 조회되었습니다',\n zh: '通讯订阅状态已检索',\n de: 'Status des Newsletter-Abonnements abgerufen',\n ar: 'تم استرداد حالة الاشتراك في النشرة الإخبارية',\n it: 'Stato iscrizione newsletter recuperato',\n pt: 'Status da inscrição na newsletter recuperado',\n hi: 'न्यूज़लेटर सदस्यता स्थिति प्राप्त की गई',\n tr: 'Bülten abonelik durumu alındı',\n pl: 'Pobrano status subskrypcji newslettera',\n id: 'Status langganan buletin diambil',\n vi: 'Đã truy xuất trạng thái đăng ký nhận bản tin',\n uk: 'Статус підписки на розсилку отримано',\n }),\n data: formattedUser,\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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"}
1
+ {"version":3,"file":"newsletter.controller.mjs","names":["userService.getUserByEmail","userService.createUser","userService.updateUserById","userService.getUserById"],"sources":["../../../src/controllers/newsletter.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { hasPermission } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { EmailsList, UserAPI } from '@/types/user.types';\n\nexport type NewsletterSubscriptionBody = {\n email: string;\n emailList: EmailsList | EmailsList[];\n};\nexport type NewsletterSubscriptionResult = ResponseData<UserAPI>;\n\n/**\n * Subscribes a user to the newsletter.\n * If the user doesn't exist, creates a new user.\n * If the user exists, updates their newsletter subscription to true.\n */\nexport const subscribeToNewsletter = async (\n request: FastifyRequest<{ Body: NewsletterSubscriptionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { roles } = request.session || {};\n const { email, emailList } = request.body;\n\n if (!email) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, true])\n ) as Record<EmailsList, boolean>;\n\n try {\n // Check if user exists\n let user = await userService.getUserByEmail(email);\n\n if (!user) {\n user = await userService.createUser({\n email,\n emailsList: emailsListObject,\n });\n\n logger.info(`New user created and subscribed to newsletter: ${email}`);\n } else {\n if (\n !hasPermission(\n roles || [],\n 'user:write'\n )({\n ...request.session,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n // Update existing user's newsletter subscription\n user = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User subscribed to newsletter: ${email}`);\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully subscribed to newsletter',\n 'en-GB': 'Successfully subscribed to newsletter',\n fr: 'Abonnement à la newsletter réussi',\n es: 'Suscripción al boletín exitosa',\n ru: 'Вы успешно подписались на рассылку',\n ja: 'ニュースレターの購読に成功しました',\n ko: '뉴스레터 구독에 성공했습니다',\n zh: '成功订阅通讯',\n de: 'Erfolgreich zum Newsletter angemeldet',\n ar: 'تم الاشتراك في النشرة الإخبارية بنجاح',\n it: 'Iscrizione alla newsletter riuscita',\n pt: 'Inscrição na newsletter realizada com sucesso',\n hi: 'न्यूज़लेटर की सदस्यता सफलतापूर्वक ली गई',\n tr: 'Bültene başarıyla abone olundu',\n pl: 'Pomyślnie zasubskrybowano newsletter',\n id: 'Berhasil berlangganan buletin',\n vi: 'Đăng ký nhận bản tin thành công',\n uk: 'Ви успішно підписалися на розсилку',\n }),\n description: t({\n en: 'You have been successfully subscribed to our newsletter',\n 'en-GB': 'You have been successfully subscribed to our newsletter',\n fr: 'Vous avez été abonné avec succès à notre newsletter',\n es: 'Te has suscrito exitosamente a nuestro boletín',\n ru: 'Вы были успешно подписаны на нашу рассылку',\n ja: '当社のニュースレターに正常に登録されました',\n ko: '뉴스레터에 성공적으로 구독되었습니다',\n zh: '您已成功订阅我们的通讯',\n de: 'Sie wurden erfolgreich für unseren Newsletter angemeldet',\n ar: 'لقد تم اشتراكك بنجاح في نشرتنا الإخبارية',\n it: 'Ti sei iscritto con successo alla nostra newsletter',\n pt: 'Você foi inscrito com sucesso em nossa newsletter',\n hi: 'आप हमारे न्यूज़लेटर के लिए सफलतापूर्वक पंजीकृत हो गए हैं',\n tr: 'Bültenimize başarıyla abone oldunuz',\n pl: 'Zostałeś pomyślnie zapisany do naszego newslettera',\n id: 'Anda telah berhasil berlangganan buletin kami',\n vi: 'Bạn đã đăng ký nhận bản tin của chúng tôi thành công',\n uk: 'Ви успішно підписалися на нашу розсилку',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type NewsletterUnsubscriptionBody = {\n userId: string;\n emailList: EmailsList | EmailsList[];\n};\n\n/**\n * Unsubscribes a user from the newsletter.\n * Only works if the user exists.\n */\nexport const unsubscribeFromNewsletter = async (\n request: FastifyRequest<{ Body: NewsletterUnsubscriptionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { userId, emailList } = request.body;\n const { roles } = request.session || {};\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n try {\n // Check if user exists\n const user = await userService.getUserById(userId);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:write'\n )({\n ...request.session,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, false])\n ) as Record<EmailsList, boolean>;\n\n // Update user's newsletter subscription to false\n const updatedUser = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User unsubscribed from newsletter: ${updatedUser.email}`);\n\n const formattedUser = mapUserToAPI(updatedUser);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully unsubscribed from newsletter',\n 'en-GB': 'Successfully unsubscribed from newsletter',\n fr: 'Désabonnement de la newsletter réussi',\n es: 'Cancelación de suscripción al boletín exitosa',\n ru: 'Вы успешно отписались от рассылки',\n ja: 'ニュースレターの解約に成功しました',\n ko: '뉴스레터 구독 해지에 성공했습니다',\n zh: '成功取消订阅通讯',\n de: 'Erfolgreich vom Newsletter abgemeldet',\n ar: 'تم إلغاء الاشتراك في النشرة الإخبارية بنجاح',\n it: 'Disiscrizione dalla newsletter riuscita',\n pt: 'Cancelamento da inscrição na newsletter realizado com sucesso',\n hi: 'न्यूज़लेटर की सदस्यता सफलतापूर्वक समाप्त की गई',\n tr: 'Bülten aboneliğinden başarıyla çıkıldı',\n pl: 'Pomyślnie wypisano się z newslettera',\n id: 'Berhasil membatalkan langganan buletin',\n vi: 'Hủy đăng ký nhận bản tin thành công',\n uk: 'Ви успішно відписалися від розсилки',\n }),\n description: t({\n en: 'You have been successfully unsubscribed from our newsletter',\n 'en-GB': 'You have been successfully unsubscribed from our newsletter',\n fr: 'Vous avez été désabonné avec succès de notre newsletter',\n es: 'Te has desuscrito exitosamente de nuestro boletín',\n ru: 'Вы успешно отписались от нашей рассылки',\n ja: '当社のニュースレターから正常に登録解除されました',\n ko: '뉴스레터에서 성공적으로 해지되었습니다',\n zh: '您已成功取消订阅我们的通讯',\n de: 'Sie wurden erfolgreich von unserem Newsletter abgemeldet',\n ar: 'لقد تم إلغاء اشتراكك بنجاح من نشرتنا الإخبارية',\n it: 'Ti sei disiscritto con successo dalla nostra newsletter',\n pt: 'Você foi cancelado com sucesso da nossa newsletter',\n hi: 'आपने हमारे न्यूज़लेटर से सफलतापूर्वक अपनी सदस्यता समाप्त कर ली है',\n tr: 'Bülten aboneliğinden başarıyla ayrıldınız',\n pl: 'Zostałeś pomyślnie wypisany z naszego newslettera',\n id: 'Anda telah berhasil membatalkan langganan buletin kami',\n vi: 'Bạn đã hủy đăng ký nhận bản tin của chúng tôi thành công',\n uk: 'Ви успішно відписалися від нашої розсилки',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n/**\n * Gets the newsletter subscription status for a user.\n */\nexport const getNewsletterStatus = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const email = _request.session?.user?.email;\n const { roles } = _request.session || {};\n\n if (!email) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n try {\n const user = await userService.getUserByEmail(email);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:read'\n )({\n ..._request.session,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Newsletter subscription status retrieved',\n 'en-GB': 'Newsletter subscription status retrieved',\n fr: \"Statut d'abonnement à la newsletter récupéré\",\n es: 'Estado de suscripción al boletín obtenido',\n ru: 'Статус подписки на рассылку получен',\n ja: 'ニュースレターの購読ステータスが取得されました',\n ko: '뉴스레터 구독 상태가 조회되었습니다',\n zh: '通讯订阅状态已检索',\n de: 'Status des Newsletter-Abonnements abgerufen',\n ar: 'تم استرداد حالة الاشتراك في النشرة الإخبارية',\n it: 'Stato iscrizione newsletter recuperato',\n pt: 'Status da inscrição na newsletter recuperado',\n hi: 'न्यूज़लेटर सदस्यता स्थिति प्राप्त की गई',\n tr: 'Bülten abonelik durumu alındı',\n pl: 'Pobrano status subskrypcji newslettera',\n id: 'Status langganan buletin diambil',\n vi: 'Đã truy xuất trạng thái đăng ký nhận bản tin',\n uk: 'Статус підписки на розсилку отримано',\n }),\n data: formattedUser,\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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"}