@intlayer/backend 5.8.1 → 6.0.0-canary.1

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 (109) hide show
  1. package/dist/cjs/controllers/dictionary.controller.cjs +47 -23
  2. package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
  3. package/dist/cjs/controllers/eventListener.controller.cjs +2 -6
  4. package/dist/cjs/controllers/eventListener.controller.cjs.map +1 -1
  5. package/dist/cjs/controllers/project.controller.cjs.map +1 -1
  6. package/dist/cjs/controllers/user.controller.cjs +21 -4
  7. package/dist/cjs/controllers/user.controller.cjs.map +1 -1
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/middlewares/oAuth2.middleware.cjs +1 -1
  10. package/dist/cjs/middlewares/oAuth2.middleware.cjs.map +1 -1
  11. package/dist/cjs/routes/dictionary.routes.cjs +9 -0
  12. package/dist/cjs/routes/dictionary.routes.cjs.map +1 -1
  13. package/dist/cjs/routes/eventListener.routes.cjs +2 -2
  14. package/dist/cjs/routes/eventListener.routes.cjs.map +1 -1
  15. package/dist/cjs/schemas/dictionary.schema.cjs +0 -5
  16. package/dist/cjs/schemas/dictionary.schema.cjs.map +1 -1
  17. package/dist/cjs/services/dictionary.service.cjs +9 -9
  18. package/dist/cjs/services/dictionary.service.cjs.map +1 -1
  19. package/dist/cjs/services/project.service.cjs.map +1 -1
  20. package/dist/cjs/services/user.service.cjs.map +1 -1
  21. package/dist/cjs/types/dictionary.types.cjs.map +1 -1
  22. package/dist/cjs/types/plan.types.cjs.map +1 -1
  23. package/dist/cjs/types/project.types.cjs.map +1 -1
  24. package/dist/cjs/utils/AI/aiSdk.cjs.map +1 -1
  25. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs +2 -1
  26. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs.map +1 -1
  27. package/dist/cjs/utils/AI/askDocQuestion/embeddings.json +91239 -47121
  28. package/dist/cjs/utils/AI/translateJSON/index.cjs +1 -1
  29. package/dist/cjs/utils/AI/translateJSON/index.cjs.map +1 -1
  30. package/dist/cjs/utils/mapper/dictionary.cjs +4 -3
  31. package/dist/cjs/utils/mapper/dictionary.cjs.map +1 -1
  32. package/dist/cjs/utils/rateLimiter.cjs +2 -2
  33. package/dist/cjs/utils/rateLimiter.cjs.map +1 -1
  34. package/dist/cjs/utils/validation/validateDictionary.cjs +2 -1
  35. package/dist/cjs/utils/validation/validateDictionary.cjs.map +1 -1
  36. package/dist/cjs/utils/validation/validateProject.cjs +2 -1
  37. package/dist/cjs/utils/validation/validateProject.cjs.map +1 -1
  38. package/dist/cjs/utils/validation/validateUser.cjs +3 -2
  39. package/dist/cjs/utils/validation/validateUser.cjs.map +1 -1
  40. package/dist/esm/controllers/dictionary.controller.mjs +46 -23
  41. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  42. package/dist/esm/controllers/eventListener.controller.mjs +2 -6
  43. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  44. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  45. package/dist/esm/controllers/user.controller.mjs +21 -4
  46. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  47. package/dist/esm/index.mjs.map +1 -1
  48. package/dist/esm/middlewares/oAuth2.middleware.mjs +1 -1
  49. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  50. package/dist/esm/routes/dictionary.routes.mjs +10 -0
  51. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  52. package/dist/esm/routes/eventListener.routes.mjs +2 -2
  53. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  54. package/dist/esm/schemas/dictionary.schema.mjs +0 -5
  55. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  56. package/dist/esm/services/dictionary.service.mjs +9 -9
  57. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  58. package/dist/esm/services/project.service.mjs.map +1 -1
  59. package/dist/esm/services/user.service.mjs.map +1 -1
  60. package/dist/esm/utils/AI/aiSdk.mjs.map +1 -1
  61. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs +2 -1
  62. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  63. package/dist/esm/utils/AI/askDocQuestion/embeddings.json +91239 -47121
  64. package/dist/esm/utils/AI/translateJSON/index.mjs +1 -1
  65. package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -1
  66. package/dist/esm/utils/mapper/dictionary.mjs +4 -3
  67. package/dist/esm/utils/mapper/dictionary.mjs.map +1 -1
  68. package/dist/esm/utils/rateLimiter.mjs +3 -3
  69. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  70. package/dist/esm/utils/validation/validateDictionary.mjs +2 -1
  71. package/dist/esm/utils/validation/validateDictionary.mjs.map +1 -1
  72. package/dist/esm/utils/validation/validateProject.mjs +2 -1
  73. package/dist/esm/utils/validation/validateProject.mjs.map +1 -1
  74. package/dist/esm/utils/validation/validateUser.mjs +3 -2
  75. package/dist/esm/utils/validation/validateUser.mjs.map +1 -1
  76. package/dist/types/controllers/dictionary.controller.d.ts +5 -0
  77. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  78. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  79. package/dist/types/controllers/project.controller.d.ts +2 -2
  80. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  81. package/dist/types/controllers/user.controller.d.ts +2 -2
  82. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  83. package/dist/types/routes/dictionary.routes.d.ts +5 -0
  84. package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
  85. package/dist/types/routes/eventListener.routes.d.ts +1 -3
  86. package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
  87. package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
  88. package/dist/types/services/dictionary.service.d.ts +1 -1
  89. package/dist/types/services/project.service.d.ts +2 -2
  90. package/dist/types/services/project.service.d.ts.map +1 -1
  91. package/dist/types/services/user.service.d.ts +2 -2
  92. package/dist/types/services/user.service.d.ts.map +1 -1
  93. package/dist/types/types/dictionary.types.d.ts +4 -2
  94. package/dist/types/types/dictionary.types.d.ts.map +1 -1
  95. package/dist/types/types/plan.types.d.ts +2 -1
  96. package/dist/types/types/plan.types.d.ts.map +1 -1
  97. package/dist/types/types/project.types.d.ts +1 -0
  98. package/dist/types/types/project.types.d.ts.map +1 -1
  99. package/dist/types/utils/AI/aiSdk.d.ts +1 -1
  100. package/dist/types/utils/AI/aiSdk.d.ts.map +1 -1
  101. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
  102. package/dist/types/utils/mapper/dictionary.d.ts +1 -2
  103. package/dist/types/utils/mapper/dictionary.d.ts.map +1 -1
  104. package/dist/types/utils/validation/validateDictionary.d.ts.map +1 -1
  105. package/dist/types/utils/validation/validateProject.d.ts +2 -2
  106. package/dist/types/utils/validation/validateProject.d.ts.map +1 -1
  107. package/dist/types/utils/validation/validateUser.d.ts +2 -2
  108. package/dist/types/utils/validation/validateUser.d.ts.map +1 -1
  109. package/package.json +30 -33
@@ -37,7 +37,7 @@ const getFileContent = (filePath) => (0, import_fs.readFileSync)((0, import_path
37
37
  const CHAT_GPT_PROMPT = getFileContent("./PROMPT.md");
38
38
  const aiDefaultOptions = {
39
39
  provider: import_aiSdk.AIProvider.OPENAI,
40
- model: "gpt-5-mini"
40
+ model: "gpt-5-nano"
41
41
  };
42
42
  const formatLocaleWithName = (locale) => `${locale}: ${(0, import_core.getLocaleName)(locale, import_intlayer.Locales.ENGLISH)}`;
43
43
  const formatTagInstructions = (tags) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/utils/AI/translateJSON/index.ts"],"sourcesContent":["import type { Tag } from '@/types/tag.types';\nimport { getLocaleName } from '@intlayer/core';\nimport { logger } from '@logger';\nimport { extractJson } from '@utils/extractJSON';\nimport { generateText } from 'ai';\nimport { readFileSync } from 'fs';\nimport { Locales } from 'intlayer';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { AIConfig, AIOptions, AIProvider } from '../aiSdk';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Get the content of a file at the specified path\nconst getFileContent = (filePath: string) =>\n readFileSync(join(__dirname, filePath), { encoding: 'utf-8' });\n\nexport type TranslateJSONOptions = {\n entryFileContent: JSON;\n presetOutputContent: JSON;\n dictionaryDescription: string;\n entryLocale: Locales;\n outputLocale: Locales;\n tags: Tag[];\n aiConfig: AIConfig;\n mode: 'complete' | 'review';\n applicationContext?: string;\n};\n\nexport type TranslateJSONResultData = {\n fileContent: string;\n tokenUsed: number;\n};\n\n// The prompt template to send to the AI model\nconst CHAT_GPT_PROMPT = getFileContent('./PROMPT.md');\n\nexport const aiDefaultOptions: AIOptions = {\n provider: AIProvider.OPENAI,\n model: 'gpt-5-mini',\n};\n\n/**\n * Format a locale with its name.\n *\n * @param locale - The locale to format.\n * @returns A string in the format \"locale: name\", e.g. \"en: English\".\n */\nconst formatLocaleWithName = (locale: Locales): string =>\n `${locale}: ${getLocaleName(locale, Locales.ENGLISH)}`;\n\n/**\n * Formats tag instructions for the AI prompt.\n * Creates a string with all available tags and their descriptions.\n *\n * @param tags - The list of tags to format.\n * @returns A formatted string with tag instructions.\n */\nconst formatTagInstructions = (tags: Tag[]): string => {\n if (!tags || tags.length === 0) {\n return '';\n }\n\n // Prepare the tag instructions.\n return `Based on the dictionary content, identify specific tags from the list below that would be relevant:\n \n${tags.map(({ key, description }) => `- ${key}: ${description}`).join('\\n\\n')}`;\n};\n\nconst getModeInstructions = (mode: 'complete' | 'review'): string => {\n if (mode === 'complete') {\n return 'Mode: \"Complete\" - Enrich the preset content with the missing keys and values in the output locale. Do not update existing keys. Everything should be returned in the output.';\n }\n\n return 'Mode: \"Review\" - Fill missing content and review existing keys from the preset content. If a key from the entry is missing in the output, it must be translated to the target language and added. If you detect misspelled content, or content that should be reformulated, correct it. If a translation is not coherent with the desired language, translate it.';\n};\n\n/**\n * TranslateJSONs a content declaration file by constructing a prompt for AI models.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies.\n */\nexport const translateJSON = async ({\n entryFileContent,\n presetOutputContent,\n dictionaryDescription,\n aiConfig,\n entryLocale,\n outputLocale,\n tags,\n mode,\n applicationContext,\n}: TranslateJSONOptions): Promise<TranslateJSONResultData | undefined> => {\n // Prepare the prompt for AI by replacing placeholders with actual values.\n const prompt = CHAT_GPT_PROMPT.replace(\n '{{entryLocale}}',\n formatLocaleWithName(entryLocale)\n )\n .replace('{{outputLocale}}', formatLocaleWithName(outputLocale))\n .replace('{{entryFileContent}}', JSON.stringify(entryFileContent))\n .replace('{{presetOutputContent}}', JSON.stringify(presetOutputContent))\n .replace('{{dictionaryDescription}}', dictionaryDescription)\n .replace('{{applicationContext}}', applicationContext ?? '')\n .replace('{{tagsInstructions}}', formatTagInstructions(tags))\n .replace('{{modeInstructions}}', getModeInstructions(mode));\n\n // Use the AI SDK to generate the completion\n const { text: newContent, usage } = await generateText({\n ...aiConfig,\n messages: [{ role: 'system', content: prompt }],\n });\n\n logger.info(`${usage?.totalTokens ?? 0} tokens used in the request`);\n\n return {\n fileContent: extractJson(newContent),\n tokenUsed: usage?.totalTokens ?? 0,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAA8B;AAC9B,oBAAuB;AACvB,yBAA4B;AAC5B,gBAA6B;AAC7B,gBAA6B;AAC7B,sBAAwB;AACxB,kBAA8B;AAC9B,iBAA8B;AAC9B,mBAAgD;AAThD;AAWA,MAAM,gBAAY,yBAAQ,0BAAc,YAAY,GAAG,CAAC;AAGxD,MAAM,iBAAiB,CAAC,iBACtB,4BAAa,kBAAK,WAAW,QAAQ,GAAG,EAAE,UAAU,QAAQ,CAAC;AAoB/D,MAAM,kBAAkB,eAAe,aAAa;AAE7C,MAAM,mBAA8B;AAAA,EACzC,UAAU,wBAAW;AAAA,EACrB,OAAO;AACT;AAQA,MAAM,uBAAuB,CAAC,WAC5B,GAAG,MAAM,SAAK,2BAAc,QAAQ,wBAAQ,OAAO,CAAC;AAStD,MAAM,wBAAwB,CAAC,SAAwB;AACrD,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,SAAO;AAAA;AAAA,EAEP,KAAK,IAAI,CAAC,EAAE,KAAK,YAAY,MAAM,KAAK,GAAG,KAAK,WAAW,EAAE,EAAE,KAAK,MAAM,CAAC;AAC7E;AAEA,MAAM,sBAAsB,CAAC,SAAwC;AACnE,MAAI,SAAS,YAAY;AACvB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAOO,MAAM,gBAAgB,OAAO;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA0E;AAExE,QAAM,SAAS,gBAAgB;AAAA,IAC7B;AAAA,IACA,qBAAqB,WAAW;AAAA,EAClC,EACG,QAAQ,oBAAoB,qBAAqB,YAAY,CAAC,EAC9D,QAAQ,wBAAwB,KAAK,UAAU,gBAAgB,CAAC,EAChE,QAAQ,2BAA2B,KAAK,UAAU,mBAAmB,CAAC,EACtE,QAAQ,6BAA6B,qBAAqB,EAC1D,QAAQ,0BAA0B,sBAAsB,EAAE,EAC1D,QAAQ,wBAAwB,sBAAsB,IAAI,CAAC,EAC3D,QAAQ,wBAAwB,oBAAoB,IAAI,CAAC;AAG5D,QAAM,EAAE,MAAM,YAAY,MAAM,IAAI,UAAM,wBAAa;AAAA,IACrD,GAAG;AAAA,IACH,UAAU,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,CAAC;AAAA,EAChD,CAAC;AAED,uBAAO,KAAK,GAAG,OAAO,eAAe,CAAC,6BAA6B;AAEnE,SAAO;AAAA,IACL,iBAAa,gCAAY,UAAU;AAAA,IACnC,WAAW,OAAO,eAAe;AAAA,EACnC;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/utils/AI/translateJSON/index.ts"],"sourcesContent":["import type { Tag } from '@/types/tag.types';\nimport { getLocaleName } from '@intlayer/core';\nimport { logger } from '@logger';\nimport { extractJson } from '@utils/extractJSON';\nimport { generateText } from 'ai';\nimport { readFileSync } from 'fs';\nimport { Locales } from 'intlayer';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { AIConfig, AIOptions, AIProvider } from '../aiSdk';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Get the content of a file at the specified path\nconst getFileContent = (filePath: string) =>\n readFileSync(join(__dirname, filePath), { encoding: 'utf-8' });\n\nexport type TranslateJSONOptions = {\n entryFileContent: JSON;\n presetOutputContent: JSON;\n dictionaryDescription: string;\n entryLocale: Locales;\n outputLocale: Locales;\n tags: Tag[];\n aiConfig: AIConfig;\n mode: 'complete' | 'review';\n applicationContext?: string;\n};\n\nexport type TranslateJSONResultData = {\n fileContent: string;\n tokenUsed: number;\n};\n\n// The prompt template to send to the AI model\nconst CHAT_GPT_PROMPT = getFileContent('./PROMPT.md');\n\nexport const aiDefaultOptions: AIOptions = {\n provider: AIProvider.OPENAI,\n model: 'gpt-5-nano',\n};\n\n/**\n * Format a locale with its name.\n *\n * @param locale - The locale to format.\n * @returns A string in the format \"locale: name\", e.g. \"en: English\".\n */\nconst formatLocaleWithName = (locale: Locales): string =>\n `${locale}: ${getLocaleName(locale, Locales.ENGLISH)}`;\n\n/**\n * Formats tag instructions for the AI prompt.\n * Creates a string with all available tags and their descriptions.\n *\n * @param tags - The list of tags to format.\n * @returns A formatted string with tag instructions.\n */\nconst formatTagInstructions = (tags: Tag[]): string => {\n if (!tags || tags.length === 0) {\n return '';\n }\n\n // Prepare the tag instructions.\n return `Based on the dictionary content, identify specific tags from the list below that would be relevant:\n \n${tags.map(({ key, description }) => `- ${key}: ${description}`).join('\\n\\n')}`;\n};\n\nconst getModeInstructions = (mode: 'complete' | 'review'): string => {\n if (mode === 'complete') {\n return 'Mode: \"Complete\" - Enrich the preset content with the missing keys and values in the output locale. Do not update existing keys. Everything should be returned in the output.';\n }\n\n return 'Mode: \"Review\" - Fill missing content and review existing keys from the preset content. If a key from the entry is missing in the output, it must be translated to the target language and added. If you detect misspelled content, or content that should be reformulated, correct it. If a translation is not coherent with the desired language, translate it.';\n};\n\n/**\n * TranslateJSONs a content declaration file by constructing a prompt for AI models.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies.\n */\nexport const translateJSON = async ({\n entryFileContent,\n presetOutputContent,\n dictionaryDescription,\n aiConfig,\n entryLocale,\n outputLocale,\n tags,\n mode,\n applicationContext,\n}: TranslateJSONOptions): Promise<TranslateJSONResultData | undefined> => {\n // Prepare the prompt for AI by replacing placeholders with actual values.\n const prompt = CHAT_GPT_PROMPT.replace(\n '{{entryLocale}}',\n formatLocaleWithName(entryLocale)\n )\n .replace('{{outputLocale}}', formatLocaleWithName(outputLocale))\n .replace('{{entryFileContent}}', JSON.stringify(entryFileContent))\n .replace('{{presetOutputContent}}', JSON.stringify(presetOutputContent))\n .replace('{{dictionaryDescription}}', dictionaryDescription)\n .replace('{{applicationContext}}', applicationContext ?? '')\n .replace('{{tagsInstructions}}', formatTagInstructions(tags))\n .replace('{{modeInstructions}}', getModeInstructions(mode));\n\n // Use the AI SDK to generate the completion\n const { text: newContent, usage } = await generateText({\n ...aiConfig,\n messages: [{ role: 'system', content: prompt }],\n });\n\n logger.info(`${usage?.totalTokens ?? 0} tokens used in the request`);\n\n return {\n fileContent: extractJson(newContent),\n tokenUsed: usage?.totalTokens ?? 0,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAA8B;AAC9B,oBAAuB;AACvB,yBAA4B;AAC5B,gBAA6B;AAC7B,gBAA6B;AAC7B,sBAAwB;AACxB,kBAA8B;AAC9B,iBAA8B;AAC9B,mBAAgD;AAThD;AAWA,MAAM,gBAAY,yBAAQ,0BAAc,YAAY,GAAG,CAAC;AAGxD,MAAM,iBAAiB,CAAC,iBACtB,4BAAa,kBAAK,WAAW,QAAQ,GAAG,EAAE,UAAU,QAAQ,CAAC;AAoB/D,MAAM,kBAAkB,eAAe,aAAa;AAE7C,MAAM,mBAA8B;AAAA,EACzC,UAAU,wBAAW;AAAA,EACrB,OAAO;AACT;AAQA,MAAM,uBAAuB,CAAC,WAC5B,GAAG,MAAM,SAAK,2BAAc,QAAQ,wBAAQ,OAAO,CAAC;AAStD,MAAM,wBAAwB,CAAC,SAAwB;AACrD,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,SAAO;AAAA;AAAA,EAEP,KAAK,IAAI,CAAC,EAAE,KAAK,YAAY,MAAM,KAAK,GAAG,KAAK,WAAW,EAAE,EAAE,KAAK,MAAM,CAAC;AAC7E;AAEA,MAAM,sBAAsB,CAAC,SAAwC;AACnE,MAAI,SAAS,YAAY;AACvB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAOO,MAAM,gBAAgB,OAAO;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA0E;AAExE,QAAM,SAAS,gBAAgB;AAAA,IAC7B;AAAA,IACA,qBAAqB,WAAW;AAAA,EAClC,EACG,QAAQ,oBAAoB,qBAAqB,YAAY,CAAC,EAC9D,QAAQ,wBAAwB,KAAK,UAAU,gBAAgB,CAAC,EAChE,QAAQ,2BAA2B,KAAK,UAAU,mBAAmB,CAAC,EACtE,QAAQ,6BAA6B,qBAAqB,EAC1D,QAAQ,0BAA0B,sBAAsB,EAAE,EAC1D,QAAQ,wBAAwB,sBAAsB,IAAI,CAAC,EAC3D,QAAQ,wBAAwB,oBAAoB,IAAI,CAAC;AAG5D,QAAM,EAAE,MAAM,YAAY,MAAM,IAAI,UAAM,wBAAa;AAAA,IACrD,GAAG;AAAA,IACH,UAAU,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,CAAC;AAAA,EAChD,CAAC;AAED,uBAAO,KAAK,GAAG,OAAO,eAAe,CAAC,6BAA6B;AAEnE,SAAO;AAAA,IACL,iBAAa,gCAAY,UAAU;AAAA,IACnC,WAAW,OAAO,eAAe;AAAA,EACnC;AACF;","names":[]}
@@ -22,11 +22,11 @@ __export(dictionary_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(dictionary_exports);
24
24
  var import_ensureMongoDocumentToObject = require('./../../utils/ensureMongoDocumentToObject.cjs');
25
- const mapDictionaryToAPI = (dictionary, projectId, version) => {
25
+ const mapDictionaryToAPI = (dictionary, version) => {
26
26
  const dictionaryObject = (0, import_ensureMongoDocumentToObject.ensureMongoDocumentToObject)(dictionary);
27
+ let versionList = [...dictionaryObject.content.keys() ?? []];
27
28
  let returnedVersion = version;
28
29
  if (!returnedVersion) {
29
- const versionList = [...dictionaryObject.content.keys() ?? []];
30
30
  const lastVersion = versionList[versionList.length - 1];
31
31
  returnedVersion = lastVersion;
32
32
  }
@@ -34,7 +34,8 @@ const mapDictionaryToAPI = (dictionary, projectId, version) => {
34
34
  return {
35
35
  ...dictionaryObject,
36
36
  content,
37
- filePath: dictionaryObject.filePath?.[String(projectId)]
37
+ version: returnedVersion,
38
+ versionList
38
39
  };
39
40
  };
40
41
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/utils/mapper/dictionary.ts"],"sourcesContent":["import type { Dictionary, DictionaryAPI } from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\n\n/**\n * Maps a dictionary to an API response.\n * If the version is not provided, the latest version is used.\n *\n * @param dictionary - The dictionary to map.\n * @param projectId - The ID of the project the dictionary belongs to.\n * @returns The dictionary mapped to an API response.\n */\nexport const mapDictionaryToAPI = (\n dictionary: Dictionary,\n projectId: string | Project['id'],\n version?: string\n): DictionaryAPI => {\n const dictionaryObject = ensureMongoDocumentToObject<Dictionary>(dictionary);\n\n let returnedVersion = version;\n\n if (!returnedVersion) {\n const versionList = [...(dictionaryObject.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n returnedVersion = lastVersion;\n }\n\n const content =\n (dictionaryObject.content.get(returnedVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n return {\n ...dictionaryObject,\n content,\n filePath: dictionaryObject.filePath?.[String(projectId)],\n } as unknown as DictionaryAPI;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yCAA4C;AAUrC,MAAM,qBAAqB,CAChC,YACA,WACA,YACkB;AAClB,QAAM,uBAAmB,gEAAwC,UAAU;AAE3E,MAAI,kBAAkB;AAEtB,MAAI,CAAC,iBAAiB;AACpB,UAAM,cAAc,CAAC,GAAI,iBAAiB,QAAQ,KAAK,KAAK,CAAC,CAAE;AAC/D,UAAM,cAAc,YAAY,YAAY,SAAS,CAAC;AACtD,sBAAkB;AAAA,EACpB;AAEA,QAAM,UACH,iBAAiB,QAAQ,IAAI,eAAe,GACzC,WAAwC;AAE9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,UAAU,iBAAiB,WAAW,OAAO,SAAS,CAAC;AAAA,EACzD;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/utils/mapper/dictionary.ts"],"sourcesContent":["import type { Dictionary, DictionaryAPI } from '@/types/dictionary.types';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\n\n/**\n * Maps a dictionary to an API response.\n * If the version is not provided, the latest version is used.\n *\n * @param dictionary - The dictionary to map.\n * @param projectId - The ID of the project the dictionary belongs to.\n * @returns The dictionary mapped to an API response.\n */\nexport const mapDictionaryToAPI = (\n dictionary: Dictionary,\n version?: string\n): DictionaryAPI => {\n const dictionaryObject = ensureMongoDocumentToObject<Dictionary>(dictionary);\n\n let versionList = [...(dictionaryObject.content.keys() ?? [])];\n let returnedVersion = version;\n\n if (!returnedVersion) {\n const lastVersion = versionList[versionList.length - 1];\n returnedVersion = lastVersion;\n }\n\n const content =\n (dictionaryObject.content.get(returnedVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n return {\n ...dictionaryObject,\n content,\n version: returnedVersion,\n versionList,\n } as unknown as DictionaryAPI;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,yCAA4C;AAUrC,MAAM,qBAAqB,CAChC,YACA,YACkB;AAClB,QAAM,uBAAmB,gEAAwC,UAAU;AAE3E,MAAI,cAAc,CAAC,GAAI,iBAAiB,QAAQ,KAAK,KAAK,CAAC,CAAE;AAC7D,MAAI,kBAAkB;AAEtB,MAAI,CAAC,iBAAiB;AACpB,UAAM,cAAc,YAAY,YAAY,SAAS,CAAC;AACtD,sBAAkB;AAAA,EACpB;AAEA,QAAM,UACH,iBAAiB,QAAQ,IAAI,eAAe,GACzC,WAAwC;AAE9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -43,7 +43,7 @@ const ipLimiter = (0, import_express_rate_limit.default)({
43
43
  legacyHeaders: false,
44
44
  // Use a custom key generator that handles proxy headers securely
45
45
  keyGenerator: (req) => {
46
- return req.ip ?? req.socket?.remoteAddress ?? "unknown";
46
+ return (0, import_express_rate_limit.ipKeyGenerator)(req.ip ?? req.socket?.remoteAddress ?? "unknown");
47
47
  },
48
48
  handler: (req, res, _next) => {
49
49
  const { limit, remaining, resetTime } = req.rateLimit;
@@ -65,7 +65,7 @@ const unauthenticatedChatBotLimiter = (0, import_express_rate_limit.default)({
65
65
  legacyHeaders: false,
66
66
  // Use a custom key generator that handles proxy headers securely
67
67
  keyGenerator: (req) => {
68
- return req.ip ?? req.socket?.remoteAddress ?? "unknown";
68
+ return (0, import_express_rate_limit.ipKeyGenerator)(req.ip ?? req.socket?.remoteAddress ?? "unknown");
69
69
  },
70
70
  handler: (req, res) => {
71
71
  const { limit, remaining, resetTime } = req.rateLimit;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/rateLimiter.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\nimport rateLimit from 'express-rate-limit';\nimport { ErrorHandler } from './errors';\n\n// -------------------------------------------------------------\n// Create the rate-limiter instances once at module load-time so\n// that the hit counters are shared across every incoming request.\n// -------------------------------------------------------------\n\nexport const ipLimiter: (\n req: Request,\n res: Response,\n next: NextFunction\n) => unknown = rateLimit({\n windowMs: 60 * 1000, // 1-minute window\n limit: 500, // 500 requests / IP / window\n standardHeaders: 'draft-8',\n legacyHeaders: false,\n // Use a custom key generator that handles proxy headers securely\n keyGenerator: (req) => {\n // Use the real IP address, falling back to socket remote address\n return req.ip ?? req.socket?.remoteAddress ?? 'unknown';\n },\n handler: (req, res, _next) => {\n const { limit, remaining, resetTime } = (req as any).rateLimit;\n\n ErrorHandler.handleGenericErrorResponse(res, 'RATE_LIMIT_EXCEEDED', {\n limit: `${limit} per minute`,\n retryAfter: Math.ceil((resetTime!.getTime() - Date.now()) / 1000),\n remaining,\n });\n },\n});\n\nexport const unauthenticatedChatBotLimiter: (\n req: Request,\n res: Response,\n next: NextFunction\n) => any = rateLimit({\n windowMs: 60 * 60 * 1000, // 1-hour window\n limit: 3, // 3 requests / IP / window\n standardHeaders: 'draft-8',\n skip: (_req, res) => Boolean(res.locals.user), // authenticated? then skip\n legacyHeaders: false,\n // Use a custom key generator that handles proxy headers securely\n keyGenerator: (req) => {\n // Use the real IP address, falling back to socket remote address\n return req.ip ?? req.socket?.remoteAddress ?? 'unknown';\n },\n handler: (req, res) => {\n const { limit, remaining, resetTime } = (req as any).rateLimit;\n\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'RATE_LIMIT_EXCEEDED_UNAUTHENTICATED',\n {\n limit: `${limit} per hour`,\n retryAfter: Math.ceil((resetTime!.getTime() - Date.now()) / 1000),\n remaining,\n }\n );\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,gCAAsB;AACtB,oBAA6B;AAOtB,MAAM,gBAIE,0BAAAA,SAAU;AAAA,EACvB,UAAU,KAAK;AAAA;AAAA,EACf,OAAO;AAAA;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA;AAAA,EAEf,cAAc,CAAC,QAAQ;AAErB,WAAO,IAAI,MAAM,IAAI,QAAQ,iBAAiB;AAAA,EAChD;AAAA,EACA,SAAS,CAAC,KAAK,KAAK,UAAU;AAC5B,UAAM,EAAE,OAAO,WAAW,UAAU,IAAK,IAAY;AAErD,+BAAa,2BAA2B,KAAK,uBAAuB;AAAA,MAClE,OAAO,GAAG,KAAK;AAAA,MACf,YAAY,KAAK,MAAM,UAAW,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AACF,CAAC;AAEM,MAAM,oCAIF,0BAAAA,SAAU;AAAA,EACnB,UAAU,KAAK,KAAK;AAAA;AAAA,EACpB,OAAO;AAAA;AAAA,EACP,iBAAiB;AAAA,EACjB,MAAM,CAAC,MAAM,QAAQ,QAAQ,IAAI,OAAO,IAAI;AAAA;AAAA,EAC5C,eAAe;AAAA;AAAA,EAEf,cAAc,CAAC,QAAQ;AAErB,WAAO,IAAI,MAAM,IAAI,QAAQ,iBAAiB;AAAA,EAChD;AAAA,EACA,SAAS,CAAC,KAAK,QAAQ;AACrB,UAAM,EAAE,OAAO,WAAW,UAAU,IAAK,IAAY;AAErD,+BAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,GAAG,KAAK;AAAA,QACf,YAAY,KAAK,MAAM,UAAW,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":["rateLimit"]}
1
+ {"version":3,"sources":["../../../src/utils/rateLimiter.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\nimport rateLimit, { ipKeyGenerator } from 'express-rate-limit';\nimport { ErrorHandler } from './errors';\n\n// -------------------------------------------------------------\n// Create the rate-limiter instances once at module load-time so\n// that the hit counters are shared across every incoming request.\n// -------------------------------------------------------------\n\nexport const ipLimiter: (\n req: Request,\n res: Response,\n next: NextFunction\n) => unknown = rateLimit({\n windowMs: 60 * 1000, // 1-minute window\n limit: 500, // 500 requests / IP / window\n standardHeaders: 'draft-8',\n legacyHeaders: false,\n // Use a custom key generator that handles proxy headers securely\n keyGenerator: (req) => {\n // Normalize IPv6 to subnet using helper to avoid bypasses\n return ipKeyGenerator(req.ip ?? req.socket?.remoteAddress ?? 'unknown');\n },\n handler: (req, res, _next) => {\n const { limit, remaining, resetTime } = (req as any).rateLimit;\n\n ErrorHandler.handleGenericErrorResponse(res, 'RATE_LIMIT_EXCEEDED', {\n limit: `${limit} per minute`,\n retryAfter: Math.ceil((resetTime!.getTime() - Date.now()) / 1000),\n remaining,\n });\n },\n});\n\nexport const unauthenticatedChatBotLimiter: (\n req: Request,\n res: Response,\n next: NextFunction\n) => any = rateLimit({\n windowMs: 60 * 60 * 1000, // 1-hour window\n limit: 3, // 3 requests / IP / window\n standardHeaders: 'draft-8',\n skip: (_req, res) => Boolean(res.locals.user), // authenticated? then skip\n legacyHeaders: false,\n // Use a custom key generator that handles proxy headers securely\n keyGenerator: (req) => {\n // Normalize IPv6 to subnet using helper to avoid bypasses\n return ipKeyGenerator(req.ip ?? req.socket?.remoteAddress ?? 'unknown');\n },\n handler: (req, res) => {\n const { limit, remaining, resetTime } = (req as any).rateLimit;\n\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'RATE_LIMIT_EXCEEDED_UNAUTHENTICATED',\n {\n limit: `${limit} per hour`,\n retryAfter: Math.ceil((resetTime!.getTime() - Date.now()) / 1000),\n remaining,\n }\n );\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,gCAA0C;AAC1C,oBAA6B;AAOtB,MAAM,gBAIE,0BAAAA,SAAU;AAAA,EACvB,UAAU,KAAK;AAAA;AAAA,EACf,OAAO;AAAA;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA;AAAA,EAEf,cAAc,CAAC,QAAQ;AAErB,eAAO,0CAAe,IAAI,MAAM,IAAI,QAAQ,iBAAiB,SAAS;AAAA,EACxE;AAAA,EACA,SAAS,CAAC,KAAK,KAAK,UAAU;AAC5B,UAAM,EAAE,OAAO,WAAW,UAAU,IAAK,IAAY;AAErD,+BAAa,2BAA2B,KAAK,uBAAuB;AAAA,MAClE,OAAO,GAAG,KAAK;AAAA,MACf,YAAY,KAAK,MAAM,UAAW,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AACF,CAAC;AAEM,MAAM,oCAIF,0BAAAA,SAAU;AAAA,EACnB,UAAU,KAAK,KAAK;AAAA;AAAA,EACpB,OAAO;AAAA;AAAA,EACP,iBAAiB;AAAA,EACjB,MAAM,CAAC,MAAM,QAAQ,QAAQ,IAAI,OAAO,IAAI;AAAA;AAAA,EAC5C,eAAe;AAAA;AAAA,EAEf,cAAc,CAAC,QAAQ;AAErB,eAAO,0CAAe,IAAI,MAAM,IAAI,QAAQ,iBAAiB,SAAS;AAAA,EACxE;AAAA,EACA,SAAS,CAAC,KAAK,QAAQ;AACrB,UAAM,EAAE,OAAO,WAAW,UAAU,IAAK,IAAY;AAErD,+BAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,GAAG,KAAK;AAAA,QACf,YAAY,KAAK,MAAM,UAAW,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":["rateLimit"]}
@@ -27,11 +27,12 @@ const defaultFieldsToCheck = ["projectIds"];
27
27
  const validateDictionary = async (dictionary, fieldsToCheck = defaultFieldsToCheck) => {
28
28
  const errors = {};
29
29
  const fieldsToValidate = new Set(fieldsToCheck);
30
+ const dictionaryJSON = JSON.parse(JSON.stringify(dictionary));
30
31
  const projects = await (0, import_project.findProjects)({
31
32
  _id: dictionary.projectIds
32
33
  });
33
34
  for (const field of fieldsToValidate) {
34
- const value = dictionary[field];
35
+ const value = dictionaryJSON[field];
35
36
  errors[field] = [];
36
37
  if (field === "projectIds") {
37
38
  const projectsErrors = (0, import_validateArray.validateArray)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/utils/validation/validateDictionary.ts"],"sourcesContent":["import { findProjects } from '@services/project.service';\nimport { validateArray } from './validateArray';\nimport type { Dictionary } from '@/types/dictionary.types';\n\nexport type DictionaryFields = (keyof Dictionary)[];\n\nconst defaultFieldsToCheck: DictionaryFields = ['projectIds'];\n\ntype FieldsToCheck = (typeof defaultFieldsToCheck)[number];\ntype ValidationErrors = Partial<\n Record<(typeof defaultFieldsToCheck)[number], string[]>\n>;\n\n/**\n * Validates an dictionary object.\n * @param dictionary The dictionary object to validate.\n * @returns An object containing the validation errors for each field.\n */\nexport const validateDictionary = async (\n dictionary: Partial<Dictionary>,\n fieldsToCheck = defaultFieldsToCheck\n): Promise<ValidationErrors> => {\n const errors: ValidationErrors = {};\n\n // Define the fields to validate\n const fieldsToValidate = new Set<FieldsToCheck>(fieldsToCheck);\n\n const projects = await findProjects({\n _id: dictionary.projectIds as unknown as string[],\n });\n\n // Validate each field\n for (const field of fieldsToValidate) {\n const value = dictionary[field];\n\n // Initialize error array for the field\n errors[field] = [];\n\n if (field === 'projectIds') {\n const projectsErrors = validateArray<string>(\n value as unknown as string[],\n 'Project',\n 'string',\n (value) => {\n const projectErrors: string[] = [];\n\n if (typeof value !== 'string') {\n projectErrors.push('Project id must be a string');\n }\n\n if (!value) {\n projectErrors.push('Project id is required');\n }\n\n if (!projects) {\n projectErrors.push('Project not found');\n }\n\n return projectsErrors;\n }\n );\n }\n\n // Remove the error field if there are no errors\n if (errors[field].length === 0) {\n delete errors[field];\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA6B;AAC7B,2BAA8B;AAK9B,MAAM,uBAAyC,CAAC,YAAY;AAYrD,MAAM,qBAAqB,OAChC,YACA,gBAAgB,yBACc;AAC9B,QAAM,SAA2B,CAAC;AAGlC,QAAM,mBAAmB,IAAI,IAAmB,aAAa;AAE7D,QAAM,WAAW,UAAM,6BAAa;AAAA,IAClC,KAAK,WAAW;AAAA,EAClB,CAAC;AAGD,aAAW,SAAS,kBAAkB;AACpC,UAAM,QAAQ,WAAW,KAAK;AAG9B,WAAO,KAAK,IAAI,CAAC;AAEjB,QAAI,UAAU,cAAc;AAC1B,YAAM,qBAAiB;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA,CAACA,WAAU;AACT,gBAAM,gBAA0B,CAAC;AAEjC,cAAI,OAAOA,WAAU,UAAU;AAC7B,0BAAc,KAAK,6BAA6B;AAAA,UAClD;AAEA,cAAI,CAACA,QAAO;AACV,0BAAc,KAAK,wBAAwB;AAAA,UAC7C;AAEA,cAAI,CAAC,UAAU;AACb,0BAAc,KAAK,mBAAmB;AAAA,UACxC;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,EAAE,WAAW,GAAG;AAC9B,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;","names":["value"]}
1
+ {"version":3,"sources":["../../../../src/utils/validation/validateDictionary.ts"],"sourcesContent":["import type { Dictionary } from '@/types/dictionary.types';\nimport { findProjects } from '@services/project.service';\nimport { validateArray } from './validateArray';\n\nexport type DictionaryFields = (keyof Dictionary)[];\n\nconst defaultFieldsToCheck: DictionaryFields = ['projectIds'];\n\ntype FieldsToCheck = (typeof defaultFieldsToCheck)[number];\ntype ValidationErrors = Partial<\n Record<(typeof defaultFieldsToCheck)[number], string[]>\n>;\n\n/**\n * Validates an dictionary object.\n * @param dictionary The dictionary object to validate.\n * @returns An object containing the validation errors for each field.\n */\nexport const validateDictionary = async (\n dictionary: Partial<Dictionary>,\n fieldsToCheck = defaultFieldsToCheck\n): Promise<ValidationErrors> => {\n const errors: ValidationErrors = {};\n\n // Define the fields to validate\n const fieldsToValidate = new Set<FieldsToCheck>(fieldsToCheck);\n\n const dictionaryJSON = JSON.parse(JSON.stringify(dictionary));\n\n const projects = await findProjects({\n _id: dictionary.projectIds as unknown as string[],\n });\n\n // Validate each field\n for (const field of fieldsToValidate) {\n const value = dictionaryJSON[field];\n\n // Initialize error array for the field\n errors[field] = [];\n\n if (field === 'projectIds') {\n const projectsErrors: string[] = validateArray<string>(\n value as unknown as string[],\n 'Project',\n 'string',\n (value) => {\n const projectErrors: string[] = [];\n\n if (typeof value !== 'string') {\n projectErrors.push('Project id must be a string');\n }\n\n if (!value) {\n projectErrors.push('Project id is required');\n }\n\n if (!projects) {\n projectErrors.push('Project not found');\n }\n\n return projectsErrors;\n }\n );\n }\n\n // Remove the error field if there are no errors\n if (errors[field].length === 0) {\n delete errors[field];\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,qBAA6B;AAC7B,2BAA8B;AAI9B,MAAM,uBAAyC,CAAC,YAAY;AAYrD,MAAM,qBAAqB,OAChC,YACA,gBAAgB,yBACc;AAC9B,QAAM,SAA2B,CAAC;AAGlC,QAAM,mBAAmB,IAAI,IAAmB,aAAa;AAE7D,QAAM,iBAAiB,KAAK,MAAM,KAAK,UAAU,UAAU,CAAC;AAE5D,QAAM,WAAW,UAAM,6BAAa;AAAA,IAClC,KAAK,WAAW;AAAA,EAClB,CAAC;AAGD,aAAW,SAAS,kBAAkB;AACpC,UAAM,QAAQ,eAAe,KAAK;AAGlC,WAAO,KAAK,IAAI,CAAC;AAEjB,QAAI,UAAU,cAAc;AAC1B,YAAM,qBAA2B;AAAA,QAC/B;AAAA,QACA;AAAA,QACA;AAAA,QACA,CAACA,WAAU;AACT,gBAAM,gBAA0B,CAAC;AAEjC,cAAI,OAAOA,WAAU,UAAU;AAC7B,0BAAc,KAAK,6BAA6B;AAAA,UAClD;AAEA,cAAI,CAACA,QAAO;AACV,0BAAc,KAAK,wBAAwB;AAAA,UAC7C;AAEA,cAAI,CAAC,UAAU;AACb,0BAAc,KAAK,mBAAmB;AAAA,UACxC;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,EAAE,WAAW,GAAG;AAC9B,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;","names":["value"]}
@@ -39,8 +39,9 @@ const MEMBERS_MIN_LENGTH = 1;
39
39
  const validateProject = async (project, fieldsToCheck = defaultFieldsToCheck) => {
40
40
  const errors = {};
41
41
  const fieldsToValidate = new Set(fieldsToCheck);
42
+ const projectJson = JSON.parse(JSON.stringify(project));
42
43
  for (const field of fieldsToValidate) {
43
- const value = project[field];
44
+ const value = projectJson[field];
44
45
  errors[field] = [];
45
46
  if (field === "name") {
46
47
  const nameErrors = (0, import_validateString.validateString)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/utils/validation/validateProject.ts"],"sourcesContent":["import { getOrganizationById } from '@services/organization.service';\nimport { validateArray } from './validateArray';\nimport { validateString } from './validateString';\nimport type { Project } from '@/types/project.types';\n\nexport type ProjectFields = (keyof Project)[];\n\nconst defaultFieldsToCheck: ProjectFields = [\n 'name',\n 'membersIds',\n 'adminsIds',\n 'organizationId',\n];\n\ntype FieldsToCheck = (typeof defaultFieldsToCheck)[number];\ntype ValidationErrors = Partial<\n Record<(typeof defaultFieldsToCheck)[number], string[]>\n>;\n\nexport const NAME_MIN_LENGTH = 4;\nexport const NAME_MAX_LENGTH = 100;\n\nexport const MEMBERS_MIN_LENGTH = 1;\n\n/**\n * Validates an project object.\n * @param project The project object to validate.\n * @returns An object containing the validation errors for each field.\n */\nexport const validateProject = async (\n project: Partial<Project>,\n fieldsToCheck = defaultFieldsToCheck\n): Promise<ValidationErrors> => {\n const errors: ValidationErrors = {};\n\n // Define the fields to validate\n const fieldsToValidate = new Set<FieldsToCheck>(fieldsToCheck);\n\n // Validate each field\n for (const field of fieldsToValidate) {\n const value = project[field];\n\n // Initialize error array for the field\n errors[field] = [];\n\n // Check for name validity\n if (field === 'name') {\n const nameErrors = validateString(\n value,\n 'Name',\n NAME_MIN_LENGTH,\n NAME_MAX_LENGTH\n );\n\n if (nameErrors.length > 0) {\n errors[field] = nameErrors;\n }\n }\n\n if (field === 'organizationId') {\n const organization = await getOrganizationById(field);\n const organizationErrors: string[] = [];\n\n if (typeof value !== 'string') {\n organizationErrors.push('Organization id must be a string');\n }\n\n if (!value) {\n organizationErrors.push('Organization id is required');\n }\n\n if (!organization) {\n organizationErrors.push('Organization not found');\n }\n\n if (organizationErrors.length > 0) {\n errors[field] = organizationErrors;\n }\n }\n\n if (field === 'membersIds' || field === 'adminsIds') {\n if (!project.organizationId) {\n errors[field] = [\n 'Organization id is required to validate project members',\n ];\n } else {\n const organization = await getOrganizationById(project.organizationId);\n const membersErrors = validateArray<string>(\n value as unknown as string[],\n 'Members',\n 'string',\n (item) =>\n (organization?.membersIds as unknown as string[]).includes(item),\n MEMBERS_MIN_LENGTH\n );\n\n if (membersErrors.length > 0) {\n errors[field] = membersErrors;\n }\n }\n }\n\n // Remove the error field if there are no errors\n if (errors[field].length === 0) {\n delete errors[field];\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAoC;AACpC,2BAA8B;AAC9B,4BAA+B;AAK/B,MAAM,uBAAsC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAExB,MAAM,qBAAqB;AAO3B,MAAM,kBAAkB,OAC7B,SACA,gBAAgB,yBACc;AAC9B,QAAM,SAA2B,CAAC;AAGlC,QAAM,mBAAmB,IAAI,IAAmB,aAAa;AAG7D,aAAW,SAAS,kBAAkB;AACpC,UAAM,QAAQ,QAAQ,KAAK;AAG3B,WAAO,KAAK,IAAI,CAAC;AAGjB,QAAI,UAAU,QAAQ;AACpB,YAAM,iBAAa;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU,kBAAkB;AAC9B,YAAM,eAAe,UAAM,yCAAoB,KAAK;AACpD,YAAM,qBAA+B,CAAC;AAEtC,UAAI,OAAO,UAAU,UAAU;AAC7B,2BAAmB,KAAK,kCAAkC;AAAA,MAC5D;AAEA,UAAI,CAAC,OAAO;AACV,2BAAmB,KAAK,6BAA6B;AAAA,MACvD;AAEA,UAAI,CAAC,cAAc;AACjB,2BAAmB,KAAK,wBAAwB;AAAA,MAClD;AAEA,UAAI,mBAAmB,SAAS,GAAG;AACjC,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU,gBAAgB,UAAU,aAAa;AACnD,UAAI,CAAC,QAAQ,gBAAgB;AAC3B,eAAO,KAAK,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM,eAAe,UAAM,yCAAoB,QAAQ,cAAc;AACrE,cAAM,oBAAgB;AAAA,UACpB;AAAA,UACA;AAAA,UACA;AAAA,UACA,CAAC,UACE,cAAc,YAAmC,SAAS,IAAI;AAAA,UACjE;AAAA,QACF;AAEA,YAAI,cAAc,SAAS,GAAG;AAC5B,iBAAO,KAAK,IAAI;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,EAAE,WAAW,GAAG;AAC9B,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../src/utils/validation/validateProject.ts"],"sourcesContent":["import type { Project, ProjectAPI } from '@/types/project.types';\nimport { getOrganizationById } from '@services/organization.service';\nimport { validateArray } from './validateArray';\nimport { validateString } from './validateString';\n\nexport type ProjectFields = (keyof Project)[];\n\nconst defaultFieldsToCheck: ProjectFields = [\n 'name',\n 'membersIds',\n 'adminsIds',\n 'organizationId',\n];\n\ntype FieldsToCheck = (typeof defaultFieldsToCheck)[number];\ntype ValidationErrors = Partial<\n Record<(typeof defaultFieldsToCheck)[number], string[]>\n>;\n\nexport const NAME_MIN_LENGTH = 4;\nexport const NAME_MAX_LENGTH = 100;\n\nexport const MEMBERS_MIN_LENGTH = 1;\n\n/**\n * Validates an project object.\n * @param project The project object to validate.\n * @returns An object containing the validation errors for each field.\n */\nexport const validateProject = async (\n project: Partial<Project | ProjectAPI>,\n fieldsToCheck = defaultFieldsToCheck\n): Promise<ValidationErrors> => {\n const errors: ValidationErrors = {};\n\n // Define the fields to validate\n const fieldsToValidate = new Set<FieldsToCheck>(fieldsToCheck);\n\n const projectJson = JSON.parse(JSON.stringify(project));\n\n // Validate each field\n for (const field of fieldsToValidate) {\n const value = projectJson[field];\n\n // Initialize error array for the field\n errors[field] = [];\n\n // Check for name validity\n if (field === 'name') {\n const nameErrors = validateString(\n value,\n 'Name',\n NAME_MIN_LENGTH,\n NAME_MAX_LENGTH\n );\n\n if (nameErrors.length > 0) {\n errors[field] = nameErrors;\n }\n }\n\n if (field === 'organizationId') {\n const organization = await getOrganizationById(field);\n const organizationErrors: string[] = [];\n\n if (typeof value !== 'string') {\n organizationErrors.push('Organization id must be a string');\n }\n\n if (!value) {\n organizationErrors.push('Organization id is required');\n }\n\n if (!organization) {\n organizationErrors.push('Organization not found');\n }\n\n if (organizationErrors.length > 0) {\n errors[field] = organizationErrors;\n }\n }\n\n if (field === 'membersIds' || field === 'adminsIds') {\n if (!project.organizationId) {\n errors[field] = [\n 'Organization id is required to validate project members',\n ];\n } else {\n const organization = await getOrganizationById(project.organizationId);\n const membersErrors = validateArray<string>(\n value as unknown as string[],\n 'Members',\n 'string',\n (item) =>\n (organization?.membersIds as unknown as string[]).includes(item),\n MEMBERS_MIN_LENGTH\n );\n\n if (membersErrors.length > 0) {\n errors[field] = membersErrors;\n }\n }\n }\n\n // Remove the error field if there are no errors\n if (errors[field].length === 0) {\n delete errors[field];\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,0BAAoC;AACpC,2BAA8B;AAC9B,4BAA+B;AAI/B,MAAM,uBAAsC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAExB,MAAM,qBAAqB;AAO3B,MAAM,kBAAkB,OAC7B,SACA,gBAAgB,yBACc;AAC9B,QAAM,SAA2B,CAAC;AAGlC,QAAM,mBAAmB,IAAI,IAAmB,aAAa;AAE7D,QAAM,cAAc,KAAK,MAAM,KAAK,UAAU,OAAO,CAAC;AAGtD,aAAW,SAAS,kBAAkB;AACpC,UAAM,QAAQ,YAAY,KAAK;AAG/B,WAAO,KAAK,IAAI,CAAC;AAGjB,QAAI,UAAU,QAAQ;AACpB,YAAM,iBAAa;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU,kBAAkB;AAC9B,YAAM,eAAe,UAAM,yCAAoB,KAAK;AACpD,YAAM,qBAA+B,CAAC;AAEtC,UAAI,OAAO,UAAU,UAAU;AAC7B,2BAAmB,KAAK,kCAAkC;AAAA,MAC5D;AAEA,UAAI,CAAC,OAAO;AACV,2BAAmB,KAAK,6BAA6B;AAAA,MACvD;AAEA,UAAI,CAAC,cAAc;AACjB,2BAAmB,KAAK,wBAAwB;AAAA,MAClD;AAEA,UAAI,mBAAmB,SAAS,GAAG;AACjC,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU,gBAAgB,UAAU,aAAa;AACnD,UAAI,CAAC,QAAQ,gBAAgB;AAC3B,eAAO,KAAK,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM,eAAe,UAAM,yCAAoB,QAAQ,cAAc;AACrE,cAAM,oBAAgB;AAAA,UACpB;AAAA,UACA;AAAA,UACA;AAAA,UACA,CAAC,UACE,cAAc,YAAmC,SAAS,IAAI;AAAA,UACjE;AAAA,QACF;AAEA,YAAI,cAAc,SAAS,GAAG;AAC5B,iBAAO,KAAK,IAAI;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,EAAE,WAAW,GAAG;AAC9B,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -29,11 +29,12 @@ var import_validateString = require('./validateString.cjs');
29
29
  const defaultFieldsToCheck = ["name", "phone", "email", "phone"];
30
30
  const NAMES_MIN_LENGTH = 4;
31
31
  const NAMES_MAX_LENGTH = 100;
32
- const validateUser = (project, fieldsToCheck = defaultFieldsToCheck) => {
32
+ const validateUser = (user, fieldsToCheck = defaultFieldsToCheck) => {
33
33
  const errors = {};
34
34
  const fieldsToValidate = new Set(fieldsToCheck);
35
+ const userJson = JSON.parse(JSON.stringify(user));
35
36
  for (const field of fieldsToValidate) {
36
- const value = project[field];
37
+ const value = userJson[field];
37
38
  errors[field] = [];
38
39
  if (field === "name") {
39
40
  const nameErrors = (0, import_validateString.validateString)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/utils/validation/validateUser.ts"],"sourcesContent":["/* eslint-disable sonarjs/cognitive-complexity */\nimport { validateEmail } from './validateEmail';\nimport { validatePhone } from './validatePhone';\nimport { validateString } from './validateString';\nimport type { User } from '@/types/user.types';\n\nexport type UserFields = (keyof User)[];\n\nconst defaultFieldsToCheck: UserFields = ['name', 'phone', 'email', 'phone'];\n\nexport type FieldsToCheck = (typeof defaultFieldsToCheck)[number];\ntype ValidationErrors = Partial<\n Record<(typeof defaultFieldsToCheck)[number], string[]>\n>;\nexport const NAMES_MIN_LENGTH = 4;\nexport const NAMES_MAX_LENGTH = 100;\n\n/**\n * Validates an user object.\n * @param user The user object to validate.\n * @returns An object containing the validation errors for each field.\n */\nexport const validateUser = (\n project: Partial<User>,\n fieldsToCheck = defaultFieldsToCheck\n): ValidationErrors => {\n const errors: ValidationErrors = {};\n\n // Define the fields to validate\n const fieldsToValidate = new Set<FieldsToCheck>(fieldsToCheck);\n\n // Validate each field\n for (const field of fieldsToValidate) {\n const value = project[field];\n\n // Initialize error array for the field\n errors[field] = [];\n\n // Check for name validity\n if (field === 'name') {\n const nameErrors = validateString(\n value,\n `User ${field}`,\n NAMES_MIN_LENGTH,\n NAMES_MAX_LENGTH\n );\n\n if (nameErrors.length > 0) {\n errors[field] = nameErrors;\n }\n }\n\n // Check for email validity\n if (field === 'email') {\n const emailErrors = validateEmail(value, 'User Email');\n\n if (emailErrors.length > 0) {\n errors[field] = emailErrors;\n }\n }\n\n if (field === 'phone') {\n const phoneErrors = validatePhone(value, 'User Phone', 8, 20);\n\n if (phoneErrors.length > 0) {\n errors[field] = phoneErrors;\n }\n }\n\n // Remove the error field if there are no errors\n if (errors[field].length === 0) {\n delete errors[field];\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,2BAA8B;AAC9B,2BAA8B;AAC9B,4BAA+B;AAK/B,MAAM,uBAAmC,CAAC,QAAQ,SAAS,SAAS,OAAO;AAMpE,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAOzB,MAAM,eAAe,CAC1B,SACA,gBAAgB,yBACK;AACrB,QAAM,SAA2B,CAAC;AAGlC,QAAM,mBAAmB,IAAI,IAAmB,aAAa;AAG7D,aAAW,SAAS,kBAAkB;AACpC,UAAM,QAAQ,QAAQ,KAAK;AAG3B,WAAO,KAAK,IAAI,CAAC;AAGjB,QAAI,UAAU,QAAQ;AACpB,YAAM,iBAAa;AAAA,QACjB;AAAA,QACA,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAGA,QAAI,UAAU,SAAS;AACrB,YAAM,kBAAc,oCAAc,OAAO,YAAY;AAErD,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU,SAAS;AACrB,YAAM,kBAAc,oCAAc,OAAO,cAAc,GAAG,EAAE;AAE5D,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,EAAE,WAAW,GAAG;AAC9B,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../src/utils/validation/validateUser.ts"],"sourcesContent":["import type { User, UserAPI } from '@/types/user.types';\nimport { validateEmail } from './validateEmail';\nimport { validatePhone } from './validatePhone';\nimport { validateString } from './validateString';\n\nexport type UserFields = (keyof User)[];\n\nconst defaultFieldsToCheck: UserFields = ['name', 'phone', 'email', 'phone'];\n\nexport type FieldsToCheck = (typeof defaultFieldsToCheck)[number];\ntype ValidationErrors = Partial<\n Record<(typeof defaultFieldsToCheck)[number], string[]>\n>;\nexport const NAMES_MIN_LENGTH = 4;\nexport const NAMES_MAX_LENGTH = 100;\n\n/**\n * Validates an user object.\n * @param user The user object to validate.\n * @returns An object containing the validation errors for each field.\n */\nexport const validateUser = (\n user: Partial<User | UserAPI>,\n fieldsToCheck = defaultFieldsToCheck\n): ValidationErrors => {\n const errors: ValidationErrors = {};\n\n // Define the fields to validate\n const fieldsToValidate = new Set<FieldsToCheck>(fieldsToCheck);\n\n const userJson = JSON.parse(JSON.stringify(user));\n\n // Validate each field\n for (const field of fieldsToValidate) {\n const value = userJson[field];\n\n // Initialize error array for the field\n errors[field] = [];\n\n // Check for name validity\n if (field === 'name') {\n const nameErrors = validateString(\n value,\n `User ${field}`,\n NAMES_MIN_LENGTH,\n NAMES_MAX_LENGTH\n );\n\n if (nameErrors.length > 0) {\n errors[field] = nameErrors;\n }\n }\n\n // Check for email validity\n if (field === 'email') {\n const emailErrors = validateEmail(value, 'User Email');\n\n if (emailErrors.length > 0) {\n errors[field] = emailErrors;\n }\n }\n\n if (field === 'phone') {\n const phoneErrors = validatePhone(value, 'User Phone', 8, 20);\n\n if (phoneErrors.length > 0) {\n errors[field] = phoneErrors;\n }\n }\n\n // Remove the error field if there are no errors\n if (errors[field].length === 0) {\n delete errors[field];\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,2BAA8B;AAC9B,2BAA8B;AAC9B,4BAA+B;AAI/B,MAAM,uBAAmC,CAAC,QAAQ,SAAS,SAAS,OAAO;AAMpE,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAOzB,MAAM,eAAe,CAC1B,MACA,gBAAgB,yBACK;AACrB,QAAM,SAA2B,CAAC;AAGlC,QAAM,mBAAmB,IAAI,IAAmB,aAAa;AAE7D,QAAM,WAAW,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAGhD,aAAW,SAAS,kBAAkB;AACpC,UAAM,QAAQ,SAAS,KAAK;AAG5B,WAAO,KAAK,IAAI,CAAC;AAGjB,QAAI,UAAU,QAAQ;AACpB,YAAM,iBAAa;AAAA,QACjB;AAAA,QACA,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAGA,QAAI,UAAU,SAAS;AACrB,YAAM,kBAAc,oCAAc,OAAO,YAAY;AAErD,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU,SAAS;AACrB,YAAM,kBAAc,oCAAc,OAAO,cAAc,GAAG,EAAE;AAE5D,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,EAAE,WAAW,GAAG;AAC9B,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -44,9 +44,7 @@ const getDictionaries = async (req, res, _next) => {
44
44
  return;
45
45
  }
46
46
  const totalItems = await dictionaryService.countDictionaries(filters);
47
- const dictionariesAPI = dictionaries.map(
48
- (el) => mapDictionaryToAPI(el, project.id)
49
- );
47
+ const dictionariesAPI = dictionaries.map((el) => mapDictionaryToAPI(el));
50
48
  const responseData = formatPaginatedResponse({
51
49
  data: dictionariesAPI,
52
50
  page,
@@ -92,6 +90,43 @@ const getDictionariesKeys = async (_req, res, _next) => {
92
90
  return;
93
91
  }
94
92
  };
93
+ const getDictionariesUpdateTimestamp = async (_req, res, _next) => {
94
+ const { project, roles } = res.locals;
95
+ if (!project) {
96
+ ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
97
+ return;
98
+ }
99
+ try {
100
+ const dictionaries = await dictionaryService.findDictionaries({
101
+ projectIds: project.id
102
+ });
103
+ if (!hasPermission(
104
+ roles,
105
+ "dictionary:read"
106
+ )({
107
+ ...res.locals,
108
+ targetDictionaries: dictionaries
109
+ })) {
110
+ ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
111
+ return;
112
+ }
113
+ const dictionariesUpdateTimestamp = dictionaries.reduce(
114
+ (acc, dictionary) => ({
115
+ ...acc,
116
+ [dictionary.key]: new Date(dictionary.updatedAt).getTime()
117
+ }),
118
+ {}
119
+ );
120
+ const responseData = formatResponse({
121
+ data: dictionariesUpdateTimestamp
122
+ });
123
+ res.json(responseData);
124
+ return;
125
+ } catch (error) {
126
+ ErrorHandler.handleAppErrorResponse(res, error);
127
+ return;
128
+ }
129
+ };
95
130
  const getDictionaryByKey = async (req, res, _next) => {
96
131
  const { project, user, roles } = res.locals;
97
132
  const { dictionaryKey } = req.params;
@@ -126,7 +161,7 @@ const getDictionaryByKey = async (req, res, _next) => {
126
161
  );
127
162
  return;
128
163
  }
129
- const apiResult = mapDictionaryToAPI(dictionary, project.id, version);
164
+ const apiResult = mapDictionaryToAPI(dictionary, version);
130
165
  const responseData = formatResponse({
131
166
  data: apiResult
132
167
  });
@@ -164,9 +199,6 @@ const addDictionary = async (req, res, _next) => {
164
199
  ["v1", { content: dictionaryData.content ?? {} }]
165
200
  ]),
166
201
  creatorId: user.id,
167
- filePath: {
168
- [String(project.id)]: dictionaryData.filePath ?? ""
169
- },
170
202
  projectIds: dictionaryData.projectIds ?? [String(project.id)]
171
203
  };
172
204
  if (!hasPermission(roles, "dictionary:write")(res.locals)) {
@@ -175,7 +207,7 @@ const addDictionary = async (req, res, _next) => {
175
207
  }
176
208
  try {
177
209
  const newDictionary = await dictionaryService.createDictionary(dictionary);
178
- const apiResult = mapDictionaryToAPI(newDictionary, project.id);
210
+ const apiResult = mapDictionaryToAPI(newDictionary);
179
211
  const responseData = formatResponse({
180
212
  message: t({
181
213
  en: "Dictionary created successfully",
@@ -192,7 +224,7 @@ const addDictionary = async (req, res, _next) => {
192
224
  res.json(responseData);
193
225
  eventListener.sendDictionaryUpdate([
194
226
  {
195
- dictionary: mapDictionaryToAPI(newDictionary, project.id),
227
+ dictionary: mapDictionaryToAPI(newDictionary),
196
228
  status: "ADDED"
197
229
  }
198
230
  ]);
@@ -248,16 +280,11 @@ const pushDictionaries = async (req, res, _next) => {
248
280
  content: /* @__PURE__ */ new Map([
249
281
  ["v1", { content: dictionaryDataEl.content ?? {} }]
250
282
  ]),
251
- filePath: {
252
- [String(project.id)]: dictionaryDataEl.filePath ?? ""
253
- },
254
283
  key: dictionaryDataEl.key
255
284
  };
256
285
  try {
257
286
  const newDictionary = await dictionaryService.createDictionary(dictionary);
258
- newDictionariesResult.push(
259
- mapDictionaryToAPI(newDictionary, project.id)
260
- );
287
+ newDictionariesResult.push(mapDictionaryToAPI(newDictionary));
261
288
  } catch (error) {
262
289
  ErrorHandler.handleAppErrorResponse(res, error);
263
290
  return;
@@ -290,9 +317,6 @@ const pushDictionaries = async (req, res, _next) => {
290
317
  content: newContent,
291
318
  projectIds: [String(project.id)],
292
319
  creatorId: user.id,
293
- filePath: {
294
- [String(project.id)]: dictionaryDataEl.filePath ?? ""
295
- },
296
320
  key: dictionaryDataEl.key
297
321
  };
298
322
  try {
@@ -301,9 +325,7 @@ const pushDictionaries = async (req, res, _next) => {
301
325
  dictionary,
302
326
  project.id
303
327
  );
304
- updatedDictionariesResult.push(
305
- mapDictionaryToAPI(updatedDictionary, project.id)
306
- );
328
+ updatedDictionariesResult.push(mapDictionaryToAPI(updatedDictionary));
307
329
  } catch (error) {
308
330
  ErrorHandler.handleAppErrorResponse(res, error);
309
331
  return;
@@ -382,7 +404,7 @@ const updateDictionary = async (req, res, _next) => {
382
404
  dictionaryId,
383
405
  dictionaryData
384
406
  );
385
- const apiResult = mapDictionaryToAPI(updatedDictionary, project.id);
407
+ const apiResult = mapDictionaryToAPI(updatedDictionary);
386
408
  const responseData = formatResponse({
387
409
  message: t({
388
410
  en: "Dictionary updated successfully",
@@ -441,7 +463,7 @@ const deleteDictionary = async (req, res, _next) => {
441
463
  return;
442
464
  }
443
465
  logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);
444
- const apiResult = mapDictionaryToAPI(deletedDictionary, project.id);
466
+ const apiResult = mapDictionaryToAPI(deletedDictionary);
445
467
  const responseData = formatResponse({
446
468
  message: t({
447
469
  en: "Dictionary deleted successfully",
@@ -473,6 +495,7 @@ export {
473
495
  deleteDictionary,
474
496
  getDictionaries,
475
497
  getDictionariesKeys,
498
+ getDictionariesUpdateTimestamp,
476
499
  getDictionaryByKey,
477
500
  pushDictionaries,
478
501
  updateDictionary
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n Dictionary as LocalDictionary,\n} from '@intlayer/core';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as dictionaryService from '@services/dictionary.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n req: Request<GetDictionariesParams>,\n res: ResponseWithSession<GetDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(req);\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) =>\n mapDictionaryToAPI(el, project.id)\n );\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _req: Request,\n res: ResponseWithSession<GetDictionariesKeysResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n req: Request<GetDictionaryParams, any, any, GetDictionaryQuery>,\n res: ResponseWithSession<GetDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const { dictionaryKey } = req.params;\n const version = req.query.version;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, project.id, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n req: Request<any, any, AddDictionaryBody>,\n res: ResponseWithSession<AddDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionary;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n ['v1', { content: dictionaryData.content ?? ({} as ContentNode) }],\n ]),\n creatorId: user.id,\n filePath: {\n [String(project.id)]: dictionaryData.filePath ?? '',\n },\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary, project.id),\n status: 'ADDED',\n },\n ]);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: string[];\n updatedDictionaries: string[];\n error: { dictionaryId: string; message: string }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response containing the created dictionary.\n */\nexport const pushDictionaries = async (\n req: Request<any, any, PushDictionariesBody>,\n res: ResponseWithSession<PushDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionaries;\n const dictionariesKeys = dictionaryData.map((dictionary) => dictionary.key);\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARIES_NOT_PROVIDED');\n return;\n } else if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const { existingDictionariesKey, newDictionariesKey } =\n await dictionaryService.getExistingDictionaryKey(\n dictionariesKeys,\n project.id\n );\n\n const existingDictionaries = dictionaryData.filter((dictionary) =>\n existingDictionariesKey.includes(dictionary.key)\n );\n const newDictionaries = dictionaryData.filter((dictionary) =>\n newDictionariesKey.includes(dictionary.key)\n );\n\n const newDictionariesResult: DictionaryAPI[] = [];\n const updatedDictionariesResult: DictionaryAPI[] = [];\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 ['v1', { content: dictionaryDataEl.content ?? ({} as ContentNode) }],\n ]),\n filePath: {\n [String(project.id)]: dictionaryDataEl.filePath ?? '',\n },\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push(\n mapDictionaryToAPI(newDictionary, project.id)\n );\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n\n if (existingDictionariesKey.length >= 0) {\n const existingDictionariesDB =\n await dictionaryService.getDictionariesByKeys(\n existingDictionariesKey,\n project.id\n );\n\n for (const dictionaryDataEl of existingDictionaries) {\n const existingDictionaryDB = existingDictionariesDB.find(\n (dictionaryDB) => dictionaryDB.key === dictionaryDataEl.key\n )!;\n\n const versionList = [...(existingDictionaryDB.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (existingDictionaryDB.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent =\n JSON.stringify(lastContent) ===\n JSON.stringify(dictionaryDataEl.content);\n\n let newContent: VersionedContent = existingDictionaryDB.content;\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(existingDictionaryDB);\n\n existingDictionaryDB.content.set(newContentVersion, {\n content: dictionaryDataEl.content ?? ({} as ContentNode),\n });\n\n newContent = existingDictionaryDB.content;\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(existingDictionaryDB),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n filePath: {\n [String(project.id)]: dictionaryDataEl.filePath ?? '',\n },\n key: dictionaryDataEl.key,\n };\n\n try {\n const updatedDictionary =\n await dictionaryService.updateDictionaryByKey(\n dictionaryDataEl.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push(\n mapDictionaryToAPI(updatedDictionary, project.id)\n );\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n updatedDictionaries: updatedDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n req: Request<UpdateDictionaryParam, any, UpdateDictionaryBody>,\n res: ResponseWithSession<UpdateDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { dictionaryId } = req.params;\n const { project, roles } = res.locals;\n const dictionaryData = req.body;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n if (typeof dictionaryId === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n req: Request<DeleteDictionaryParam>,\n res: ResponseWithSession<DeleteDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, roles } = res.locals;\n const { dictionaryId } = req.params as Partial<DeleteDictionaryParam>;\n\n if (!dictionaryId) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:admin')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_NOT_FOUND', {\n dictionaryId,\n });\n return;\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n Dictionary as LocalDictionary,\n} from '@intlayer/core';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as dictionaryService from '@services/dictionary.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n req: Request<GetDictionariesParams>,\n res: ResponseWithSession<GetDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(req);\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) => mapDictionaryToAPI(el));\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _req: Request,\n res: ResponseWithSession<GetDictionariesKeysResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesUpdateTimestampResult = ResponseData<\n Record<string, number>\n>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesUpdateTimestamp = async (\n _req: Request,\n res: ResponseWithSession<GetDictionariesUpdateTimestampResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionariesUpdateTimestamp = dictionaries.reduce(\n (acc, dictionary) => ({\n ...acc,\n [dictionary.key]: new Date(dictionary.updatedAt).getTime(),\n }),\n {}\n );\n\n const responseData = formatResponse<Record<string, number>>({\n data: dictionariesUpdateTimestamp,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n req: Request<GetDictionaryParams, any, any, GetDictionaryQuery>,\n res: ResponseWithSession<GetDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const { dictionaryKey } = req.params;\n const version = req.query.version;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n req: Request<any, any, AddDictionaryBody>,\n res: ResponseWithSession<AddDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionary;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n ['v1', { content: dictionaryData.content ?? ({} as ContentNode) }],\n ]),\n creatorId: user.id,\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: string[];\n updatedDictionaries: string[];\n error: { dictionaryId: string; message: string }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response containing the created dictionary.\n */\nexport const pushDictionaries = async (\n req: Request<any, any, PushDictionariesBody>,\n res: ResponseWithSession<PushDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionaries;\n const dictionariesKeys = dictionaryData.map((dictionary) => dictionary.key);\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARIES_NOT_PROVIDED');\n return;\n } else if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const { existingDictionariesKey, newDictionariesKey } =\n await dictionaryService.getExistingDictionaryKey(\n dictionariesKeys,\n project.id\n );\n\n const existingDictionaries = dictionaryData.filter((dictionary) =>\n existingDictionariesKey.includes(dictionary.key)\n );\n const newDictionaries = dictionaryData.filter((dictionary) =>\n newDictionariesKey.includes(dictionary.key)\n );\n\n const newDictionariesResult: DictionaryAPI[] = [];\n const updatedDictionariesResult: DictionaryAPI[] = [];\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 ['v1', { content: dictionaryDataEl.content ?? ({} as ContentNode) }],\n ]),\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push(mapDictionaryToAPI(newDictionary));\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n\n if (existingDictionariesKey.length >= 0) {\n const existingDictionariesDB =\n await dictionaryService.getDictionariesByKeys(\n existingDictionariesKey,\n project.id\n );\n\n for (const dictionaryDataEl of existingDictionaries) {\n const existingDictionaryDB = existingDictionariesDB.find(\n (dictionaryDB) => dictionaryDB.key === dictionaryDataEl.key\n )!;\n\n const versionList = [...(existingDictionaryDB.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (existingDictionaryDB.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent =\n JSON.stringify(lastContent) ===\n JSON.stringify(dictionaryDataEl.content);\n\n let newContent: VersionedContent = existingDictionaryDB.content;\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(existingDictionaryDB);\n\n existingDictionaryDB.content.set(newContentVersion, {\n content: dictionaryDataEl.content ?? ({} as ContentNode),\n });\n\n newContent = existingDictionaryDB.content;\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(existingDictionaryDB),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n key: dictionaryDataEl.key,\n };\n\n try {\n const updatedDictionary =\n await dictionaryService.updateDictionaryByKey(\n dictionaryDataEl.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push(mapDictionaryToAPI(updatedDictionary));\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n updatedDictionaries: updatedDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n req: Request<UpdateDictionaryParam, any, UpdateDictionaryBody>,\n res: ResponseWithSession<UpdateDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { dictionaryId } = req.params;\n const { project, roles } = res.locals;\n const dictionaryData = req.body;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n if (typeof dictionaryId === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n req: Request<DeleteDictionaryParam>,\n res: ResponseWithSession<DeleteDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, roles } = res.locals;\n const { dictionaryId } = req.params as Partial<DeleteDictionaryParam>;\n\n if (!dictionaryId) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:admin')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_NOT_FOUND', {\n dictionaryId,\n });\n return;\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}
@@ -22,11 +22,7 @@ const sendDictionaryUpdate = (args) => {
22
22
  });
23
23
  };
24
24
  const listenChangeSSE = async (req, res) => {
25
- const { accessToken } = req.params;
26
- if (!accessToken) {
27
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_AUTHENTICATED");
28
- return;
29
- }
25
+ const { project } = res.locals;
30
26
  if (clients.length >= MAX_SSE_CONNECTIONS) {
31
27
  ErrorHandler.handleGenericErrorResponse(res, "TOO_MANY_CONNECTIONS");
32
28
  return;
@@ -40,7 +36,7 @@ const listenChangeSSE = async (req, res) => {
40
36
  const clientId = Date.now();
41
37
  const newClient = {
42
38
  id: clientId,
43
- projectId: String(res.locals.session.session.activeProjectId),
39
+ projectId: String(project.id),
44
40
  res
45
41
  };
46
42
  clients.push(newClient);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import type { DictionaryAPI } from '@/types/dictionary.types';\nimport { logger } from '@logger';\nimport { ErrorHandler } from '@utils/errors';\nimport type { Request, Response } from 'express';\n\nexport type Object = 'DICTIONARY';\nexport type Status = 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n\nexport type MessageEventData = {\n object: Object;\n status: Status;\n data: any;\n};\n\nlet clients: Array<{ id: number; projectId: string; res: Response }> = [];\nconst MAX_SSE_CONNECTIONS = 10;\n\nexport type SendDictionaryUpdateArg = {\n dictionary: DictionaryAPI;\n status: 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n};\n\nexport const sendDictionaryUpdate = (args: SendDictionaryUpdateArg[]) => {\n const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);\n\n const filteredClients = clients.filter((client) =>\n projectIds.map((id) => String(id)).includes(String(client.projectId))\n );\n\n const data: MessageEventData[] = args.map((arg) => ({\n object: 'DICTIONARY',\n status: arg.status,\n data: arg.dictionary,\n }));\n\n process.nextTick(() => {\n for (const client of filteredClients) {\n client.res.write(`data: ${JSON.stringify(data)}\\n\\n`);\n client.res.flush?.(); // Ensure the data is sent immediately\n }\n });\n};\n\nexport type CheckDictionaryChangeSSEParams = { accessToken: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const listenChangeSSE = async (\n req: Request<CheckDictionaryChangeSSEParams, any, any>,\n res: Response\n) => {\n const { accessToken } = req.params;\n\n if (!accessToken) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_AUTHENTICATED');\n return;\n }\n\n if (clients.length >= MAX_SSE_CONNECTIONS) {\n ErrorHandler.handleGenericErrorResponse(res, 'TOO_MANY_CONNECTIONS');\n return;\n }\n\n // Set headers for SSE\n res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n res.write(':\\n\\n'); // Comment to keep connection alive\n res.flushHeaders?.();\n\n const clientId = Date.now();\n\n // Add client to the list\n const newClient = {\n id: clientId,\n projectId: String((res.locals.session as any).session.activeProjectId),\n res,\n };\n clients.push(newClient);\n\n logger.info(\n `New client connected to SSE. Total clients: ${clients.length ?? 0}`\n );\n\n // Remove client on connection close\n req.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":"AACA,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAY7B,IAAI,UAAmE,CAAC;AACxE,MAAM,sBAAsB;AAOrB,MAAM,uBAAuB,CAAC,SAAoC;AACvE,QAAM,aAAa,KAAK,QAAQ,CAAC,QAAQ,IAAI,WAAW,UAAU;AAElE,QAAM,kBAAkB,QAAQ;AAAA,IAAO,CAAC,WACtC,WAAW,IAAI,CAAC,OAAO,OAAO,EAAE,CAAC,EAAE,SAAS,OAAO,OAAO,SAAS,CAAC;AAAA,EACtE;AAEA,QAAM,OAA2B,KAAK,IAAI,CAAC,SAAS;AAAA,IAClD,QAAQ;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,EACZ,EAAE;AAEF,UAAQ,SAAS,MAAM;AACrB,eAAW,UAAU,iBAAiB;AACpC,aAAO,IAAI,MAAM,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM;AACpD,aAAO,IAAI,QAAQ;AAAA,IACrB;AAAA,EACF,CAAC;AACH;AAOO,MAAM,kBAAkB,OAC7B,KACA,QACG;AACH,QAAM,EAAE,YAAY,IAAI,IAAI;AAE5B,MAAI,CAAC,aAAa;AAChB,iBAAa,2BAA2B,KAAK,wBAAwB;AACrE;AAAA,EACF;AAEA,MAAI,QAAQ,UAAU,qBAAqB;AACzC,iBAAa,2BAA2B,KAAK,sBAAsB;AACnE;AAAA,EACF;AAGA,MAAI,UAAU,gBAAgB,iCAAiC;AAC/D,MAAI,UAAU,iBAAiB,wBAAwB;AACvD,MAAI,UAAU,cAAc,YAAY;AACxC,MAAI,UAAU,qBAAqB,IAAI;AAGvC,MAAI,MAAM,OAAO;AACjB,MAAI,eAAe;AAEnB,QAAM,WAAW,KAAK,IAAI;AAG1B,QAAM,YAAY;AAAA,IAChB,IAAI;AAAA,IACJ,WAAW,OAAQ,IAAI,OAAO,QAAgB,QAAQ,eAAe;AAAA,IACrE;AAAA,EACF;AACA,UAAQ,KAAK,SAAS;AAEtB,SAAO;AAAA,IACL,+CAA+C,QAAQ,UAAU,CAAC;AAAA,EACpE;AAGA,MAAI,GAAG,SAAS,MAAM;AACpB,cAAU,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,QAAQ;AAAA,EAC7D,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import type { DictionaryAPI } from '@/types/dictionary.types';\nimport { logger } from '@logger';\nimport { ErrorHandler } from '@utils/errors';\nimport type { Request, Response } from 'express';\n\nexport type Object = 'DICTIONARY';\nexport type Status = 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n\nexport type MessageEventData = {\n object: Object;\n status: Status;\n data: any;\n};\n\nlet clients: Array<{ id: number; projectId: string; res: Response }> = [];\nconst MAX_SSE_CONNECTIONS = 10;\n\nexport type SendDictionaryUpdateArg = {\n dictionary: DictionaryAPI;\n status: 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n};\n\nexport const sendDictionaryUpdate = (args: SendDictionaryUpdateArg[]) => {\n const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);\n\n const filteredClients = clients.filter((client) =>\n projectIds.map((id) => String(id)).includes(String(client.projectId))\n );\n\n const data: MessageEventData[] = args.map((arg) => ({\n object: 'DICTIONARY',\n status: arg.status,\n data: arg.dictionary,\n }));\n\n process.nextTick(() => {\n for (const client of filteredClients) {\n client.res.write(`data: ${JSON.stringify(data)}\\n\\n`);\n client.res.flush?.(); // Ensure the data is sent immediately\n }\n });\n};\n\nexport type CheckDictionaryChangeSSEParams = { accessToken: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const listenChangeSSE = async (\n req: Request<CheckDictionaryChangeSSEParams, any, any>,\n res: Response\n) => {\n const { project } = res.locals;\n\n if (clients.length >= MAX_SSE_CONNECTIONS) {\n ErrorHandler.handleGenericErrorResponse(res, 'TOO_MANY_CONNECTIONS');\n return;\n }\n\n // Set headers for SSE\n res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n res.write(':\\n\\n'); // Comment to keep connection alive\n res.flushHeaders?.();\n\n const clientId = Date.now();\n\n // Add client to the list\n const newClient = {\n id: clientId,\n projectId: String(project.id),\n res,\n };\n clients.push(newClient);\n\n logger.info(\n `New client connected to SSE. Total clients: ${clients.length ?? 0}`\n );\n\n // Remove client on connection close\n req.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":"AACA,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAY7B,IAAI,UAAmE,CAAC;AACxE,MAAM,sBAAsB;AAOrB,MAAM,uBAAuB,CAAC,SAAoC;AACvE,QAAM,aAAa,KAAK,QAAQ,CAAC,QAAQ,IAAI,WAAW,UAAU;AAElE,QAAM,kBAAkB,QAAQ;AAAA,IAAO,CAAC,WACtC,WAAW,IAAI,CAAC,OAAO,OAAO,EAAE,CAAC,EAAE,SAAS,OAAO,OAAO,SAAS,CAAC;AAAA,EACtE;AAEA,QAAM,OAA2B,KAAK,IAAI,CAAC,SAAS;AAAA,IAClD,QAAQ;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,EACZ,EAAE;AAEF,UAAQ,SAAS,MAAM;AACrB,eAAW,UAAU,iBAAiB;AACpC,aAAO,IAAI,MAAM,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,CAAM;AACpD,aAAO,IAAI,QAAQ;AAAA,IACrB;AAAA,EACF,CAAC;AACH;AAOO,MAAM,kBAAkB,OAC7B,KACA,QACG;AACH,QAAM,EAAE,QAAQ,IAAI,IAAI;AAExB,MAAI,QAAQ,UAAU,qBAAqB;AACzC,iBAAa,2BAA2B,KAAK,sBAAsB;AACnE;AAAA,EACF;AAGA,MAAI,UAAU,gBAAgB,iCAAiC;AAC/D,MAAI,UAAU,iBAAiB,wBAAwB;AACvD,MAAI,UAAU,cAAc,YAAY;AACxC,MAAI,UAAU,qBAAqB,IAAI;AAGvC,MAAI,MAAM,OAAO;AACjB,MAAI,eAAe;AAEnB,QAAM,WAAW,KAAK,IAAI;AAG1B,QAAM,YAAY;AAAA,IAChB,IAAI;AAAA,IACJ,WAAW,OAAO,QAAQ,EAAE;AAAA,IAC5B;AAAA,EACF;AACA,UAAQ,KAAK,SAAS;AAEtB,SAAO;AAAA,IACL,+CAA+C,QAAQ,UAAU,CAAC;AAAA,EACpE;AAGA,MAAI,GAAG,SAAS,MAAM;AACpB,cAAU,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,QAAQ;AAAA,EAC7D,CAAC;AACH;","names":[]}