@intlayer/backend 5.1.7 → 5.1.8

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 (53) hide show
  1. package/dist/cjs/controllers/dictionary.controller.cjs +5 -7
  2. package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
  3. package/dist/cjs/controllers/{event-listener.cjs → eventListener.controller.cjs} +27 -8
  4. package/dist/cjs/controllers/eventListener.controller.cjs.map +1 -0
  5. package/dist/cjs/export.cjs.map +1 -1
  6. package/dist/cjs/index.cjs +2 -2
  7. package/dist/cjs/index.cjs.map +1 -1
  8. package/dist/cjs/routes/{event-listener.routes.cjs → eventListener.routes.cjs} +6 -6
  9. package/dist/cjs/routes/eventListener.routes.cjs.map +1 -0
  10. package/dist/cjs/services/dictionary.service.cjs +11 -30
  11. package/dist/cjs/services/dictionary.service.cjs.map +1 -1
  12. package/dist/cjs/types/dictionary.types.cjs.map +1 -1
  13. package/dist/cjs/utils/AI/askDocQuestion/embeddings.json +4298 -612
  14. package/dist/cjs/utils/errors/errorCodes.cjs +18 -1
  15. package/dist/cjs/utils/errors/errorCodes.cjs.map +1 -1
  16. package/dist/esm/controllers/dictionary.controller.mjs +5 -7
  17. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  18. package/dist/esm/controllers/{event-listener.mjs → eventListener.controller.mjs} +24 -5
  19. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -0
  20. package/dist/esm/export.mjs.map +1 -1
  21. package/dist/esm/index.mjs +1 -1
  22. package/dist/esm/index.mjs.map +1 -1
  23. package/dist/esm/routes/{event-listener.routes.mjs → eventListener.routes.mjs} +2 -2
  24. package/dist/esm/routes/eventListener.routes.mjs.map +1 -0
  25. package/dist/esm/services/dictionary.service.mjs +11 -30
  26. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  27. package/dist/esm/utils/AI/askDocQuestion/embeddings.json +4298 -612
  28. package/dist/esm/utils/errors/errorCodes.mjs +18 -1
  29. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  30. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  31. package/dist/types/controllers/{event-listener.d.ts → eventListener.controller.d.ts} +8 -1
  32. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -0
  33. package/dist/types/export.d.ts +1 -0
  34. package/dist/types/export.d.ts.map +1 -1
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/types/models/dictionary.model.d.ts +0 -1
  37. package/dist/types/models/dictionary.model.d.ts.map +1 -1
  38. package/dist/types/routes/{event-listener.routes.d.ts → eventListener.routes.d.ts} +1 -1
  39. package/dist/types/routes/eventListener.routes.d.ts.map +1 -0
  40. package/dist/types/schemas/dictionary.schema.d.ts +0 -1
  41. package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
  42. package/dist/types/services/dictionary.service.d.ts.map +1 -1
  43. package/dist/types/types/dictionary.types.d.ts +0 -1
  44. package/dist/types/types/dictionary.types.d.ts.map +1 -1
  45. package/dist/types/utils/errors/errorCodes.d.ts +18 -1
  46. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  47. package/package.json +8 -8
  48. package/dist/cjs/controllers/event-listener.cjs.map +0 -1
  49. package/dist/cjs/routes/event-listener.routes.cjs.map +0 -1
  50. package/dist/esm/controllers/event-listener.mjs.map +0 -1
  51. package/dist/esm/routes/event-listener.routes.mjs.map +0 -1
  52. package/dist/types/controllers/event-listener.d.ts.map +0 -1
  53. package/dist/types/routes/event-listener.routes.d.ts.map +0 -1
@@ -1,32 +1,48 @@
1
1
  import * as oAuth2Service from "./../services/oAuth2.service.mjs";
2
2
  import { ErrorHandler } from "./../utils/errors/index.mjs";
3
+ import { logger } from "./../logger/index.mjs";
3
4
  let clients = [];
5
+ const MAX_SSE_CONNECTIONS = 10;
4
6
  const sendDictionaryUpdate = (args) => {
5
7
  const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);
6
8
  const filteredClients = clients.filter(
7
9
  (client) => projectIds.map((id) => String(id)).includes(String(client.projectId))
8
10
  );
9
- for (const client of filteredClients) {
10
- client.res.write(`data: ${JSON.stringify(args)}
11
+ const data = args.map((arg) => ({
12
+ object: "DICTIONARY",
13
+ status: arg.status,
14
+ data: arg.dictionary
15
+ }));
16
+ process.nextTick(() => {
17
+ for (const client of filteredClients) {
18
+ client.res.write(`data: ${JSON.stringify(data)}
11
19
 
12
20
  `);
13
- }
21
+ client.res.flush?.();
22
+ }
23
+ });
14
24
  };
15
25
  const listenChangeSSE = async (req, res) => {
16
26
  const { accessToken } = req.params;
17
27
  if (!accessToken) {
18
28
  ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_AUTHENTICATED");
29
+ return;
19
30
  }
20
31
  const tokenInformation = await oAuth2Service.getAccessToken(accessToken);
21
32
  if (!tokenInformation) {
22
33
  ErrorHandler.handleGenericErrorResponse(res, "AUTH_ERROR");
34
+ return;
35
+ }
36
+ if (clients.length >= MAX_SSE_CONNECTIONS) {
37
+ ErrorHandler.handleGenericErrorResponse(res, "TOO_MANY_CONNECTIONS");
38
+ return;
23
39
  }
24
40
  res.setHeader("Content-Type", "text/event-stream;charset=utf-8");
25
41
  res.setHeader("Cache-Control", "no-cache, no-transform");
26
42
  res.setHeader("Connection", "keep-alive");
27
43
  res.setHeader("X-Accel-Buffering", "no");
28
44
  res.write(":\n\n");
29
- res.flushHeaders();
45
+ res.flushHeaders?.();
30
46
  const clientId = Date.now();
31
47
  const newClient = {
32
48
  id: clientId,
@@ -34,6 +50,9 @@ const listenChangeSSE = async (req, res) => {
34
50
  res
35
51
  };
36
52
  clients.push(newClient);
53
+ logger.info(
54
+ `New client connected to SSE. Total clients: ${clients.length ?? 0}`
55
+ );
37
56
  req.on("close", () => {
38
57
  clients = clients.filter((client) => client.id !== clientId);
39
58
  });
@@ -42,4 +61,4 @@ export {
42
61
  listenChangeSSE,
43
62
  sendDictionaryUpdate
44
63
  };
45
- //# sourceMappingURL=event-listener.mjs.map
64
+ //# sourceMappingURL=eventListener.controller.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import type { ResponseWithInformation } from '@middlewares/sessionAuth.middleware';\nimport type { Token } from '@schemas/oAuth2.schema';\nimport * as oAuth2Service from '@services/oAuth2.service';\nimport { ErrorHandler } from '@utils/errors';\nimport type { Response, Request } from 'express';\nimport type { DictionaryAPI } from '@/types/dictionary.types';\nimport { logger } from '@logger';\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: ResponseWithInformation\n) => {\n const { accessToken } = req.params;\n\n if (!accessToken) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_AUTHENTICATED');\n return;\n }\n\n const tokenInformation = await oAuth2Service.getAccessToken(accessToken);\n\n if (!tokenInformation) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUTH_ERROR');\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((tokenInformation as unknown as Token).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":"AAEA,YAAY,mBAAmB;AAC/B,SAAS,oBAAoB;AAG7B,SAAS,cAAc;AAWvB,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,QAAM,mBAAmB,MAAM,cAAc,eAAe,WAAW;AAEvE,MAAI,CAAC,kBAAkB;AACrB,iBAAa,2BAA2B,KAAK,YAAY;AACzD;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,iBAAsC,QAAQ,GAAG;AAAA,IACpE;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 +1 @@
1
- {"version":3,"sources":["../../src/export.ts"],"sourcesContent":["// Routes\nexport { getUserRoutes } from '@routes/user.routes';\nexport { getOrganizationRoutes } from '@routes/organization.routes';\nexport { getProjectRoutes } from '@routes/project.routes';\nexport { getDictionaryRoutes } from '@routes/dictionary.routes';\nexport { getSessionAuthRoutes } from '@routes/sessionAuth.routes';\n\n// Controllers types\nexport type * from '@controllers/sessionAuth.controller';\nexport type * from '@controllers/oAuth2.controller';\nexport type * from '@controllers/organization.controller';\nexport type * from '@controllers/project.controller';\nexport type * from '@controllers/projectAccessKey.controller';\nexport type * from '@controllers/user.controller';\nexport type * from '@controllers/dictionary.controller';\nexport type * from '@controllers/stripe.controller';\nexport type * from '@controllers/ai.controller';\nexport type * from '@controllers/tag.controller';\n\n// Objects types\nexport type * from '@/types/organization.types';\nexport type * from '@/types/project.types';\nexport type * from '@/types/user.types';\nexport type * from '@/types/dictionary.types';\nexport type * from '@/types/plan.types';\nexport type * from '@/types/tag.types';\nexport type * from '@/types/oAuth2.types';\n\n// Utils\nexport * from '@utils/cookies';\nexport * from '@utils/httpStatusCodes';\nexport * from '@utils/responseData';\n"],"mappings":"AACA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AAwBrC,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../src/export.ts"],"sourcesContent":["// Routes\nexport { getUserRoutes } from '@routes/user.routes';\nexport { getOrganizationRoutes } from '@routes/organization.routes';\nexport { getProjectRoutes } from '@routes/project.routes';\nexport { getDictionaryRoutes } from '@routes/dictionary.routes';\nexport { getSessionAuthRoutes } from '@routes/sessionAuth.routes';\n\n// Controllers types\nexport type * from '@controllers/sessionAuth.controller';\nexport type * from '@controllers/oAuth2.controller';\nexport type * from '@controllers/organization.controller';\nexport type * from '@controllers/eventListener.controller';\nexport type * from '@controllers/project.controller';\nexport type * from '@controllers/projectAccessKey.controller';\nexport type * from '@controllers/user.controller';\nexport type * from '@controllers/dictionary.controller';\nexport type * from '@controllers/stripe.controller';\nexport type * from '@controllers/ai.controller';\nexport type * from '@controllers/tag.controller';\n\n// Objects types\nexport type * from '@/types/organization.types';\nexport type * from '@/types/project.types';\nexport type * from '@/types/user.types';\nexport type * from '@/types/dictionary.types';\nexport type * from '@/types/plan.types';\nexport type * from '@/types/tag.types';\nexport type * from '@/types/oAuth2.types';\n\n// Utils\nexport * from '@utils/cookies';\nexport * from '@utils/httpStatusCodes';\nexport * from '@utils/responseData';\n"],"mappings":"AACA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AAyBrC,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -24,7 +24,7 @@ import { sessionAuthRouter } from "./routes/sessionAuth.routes.mjs";
24
24
  import { userRouter } from "./routes/user.routes.mjs";
25
25
  import { stripeRouter } from "./routes/stripe.routes.mjs";
26
26
  import { aiRouter } from "./routes/ai.routes.mjs";
27
- import { eventListenerRouter } from "./routes/event-listener.routes.mjs";
27
+ import { eventListenerRouter } from "./routes/eventListener.routes.mjs";
28
28
  import { stripeWebhook } from "./webhooks/stripe.webhook.mjs";
29
29
  import { getOAuth2Token } from "./controllers/oAuth2.controller.mjs";
30
30
  import {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["/* eslint-disable import/order */\n\n// Libraries\nimport compression from 'compression';\nimport cookieParser from 'cookie-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport dotenv from 'dotenv';\nimport express, { type Express } from 'express';\nimport { intlayer, t } from 'express-intlayer';\nimport helmet from 'helmet';\n\n// Middlewares\nimport {\n type RequestWithOAuth2Information,\n attachOAuthInstance,\n authenticateOAuth2,\n} from '@middlewares/oAuth2.middleware';\nimport { logAPIRequestURL } from '@middlewares/request.middleware';\nimport {\n type ResponseWithInformation,\n checkUser,\n checkOrganization,\n checkProject,\n checkAdmin,\n} from '@middlewares/sessionAuth.middleware';\n\n// Routes\nimport { dictionaryRouter } from '@routes/dictionary.routes';\nimport { organizationRouter } from '@routes/organization.routes';\nimport { projectRouter } from '@routes/project.routes';\nimport { tagRouter } from '@routes/tags.routes';\nimport { sessionAuthRouter } from '@routes/sessionAuth.routes';\nimport { userRouter } from '@routes/user.routes';\nimport { stripeRouter } from '@routes/stripe.routes';\nimport { aiRouter } from '@routes/ai.routes';\nimport { eventListenerRouter } from '@routes/event-listener.routes';\n\n// Webhooks\nimport { stripeWebhook } from '@webhooks/stripe.webhook';\n\n// Controllers\nimport { getOAuth2Token } from '@controllers/oAuth2.controller';\nimport {\n getSessionInformation,\n setCSRFToken,\n} from '@controllers/sessionAuth.controller';\n\n// Utils\nimport { doubleCsrfProtection } from '@utils/CSRF';\nimport { connectDB } from '@utils/mongoDB/connectDB';\n\n// Logger\nimport { logger } from './logger/index';\n\nconst app: Express = express();\n\napp.disable('x-powered-by'); // Disabled to prevent attackers from knowing that the app is running Express\napp.use(helmet());\n\n// Environment variables\nconst env = app.get('env');\n\nlogger.info(`run as ${env}`);\n\ndotenv.config({\n path: [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env'],\n});\n\n// Parse incoming requests with cookies\napp.use(cookieParser());\n\n// Load internationalization request handler\napp.use(intlayer());\n\nconst isDev = env === 'development';\n\n// Connect to MongoDB\nconnectDB();\n\n// Stripe\napp.post(\n '/webhook/stripe',\n express.raw({ type: 'application/json' }),\n stripeWebhook\n);\n\n// Compress all HTTP responses\napp.use(compression());\n\n// Parse incoming requests with JSON payloads\napp.use(express.json({ limit: '50mb' }));\n\n// Parse incoming requests with urlencoded payloads\napp.use(express.urlencoded({ extended: true }));\n\nconst whitelist = [process.env.CLIENT_URL!];\n\n// CORS\nconst corsOptions: CorsOptions = {\n origin: (origin, callback) => {\n // Allow requests with no origin (like mobile apps or curl requests)\n if (!origin) return callback(null, true);\n\n if (whitelist.includes(origin)) {\n logger.info('whitelisted origin', origin);\n return callback(null, true);\n }\n\n logger.info('non whitelisted origin', origin);\n // Reflect the request's origin (echo back the origin header)\n callback(null, origin);\n },\n allowedHeaders: [\n 'authorization',\n 'Content-Type',\n 'credentials',\n 'cache-control',\n 'Access-Control-Allow-Origin',\n 'private-state-token-redemption',\n 'private-state-token-issuance',\n 'browsing-topics',\n ],\n exposedHeaders: [''],\n preflightContinue: false,\n methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',\n credentials: true,\n};\napp.use(cors(corsOptions));\n\n// Liveness check\napp.get('/', (_req, res) => {\n res.send(\n t({\n en: 'Ok - locale: en',\n fr: 'Ok - locale: fr',\n es: 'Ok - locale: es',\n })\n );\n});\n\n// middleware - jwt & session auth\napp.use(/(.*)/, checkUser);\napp.use(/(.*)/, checkOrganization);\napp.use(/(.*)/, checkProject);\napp.use(/(.*)/, checkAdmin);\n\n// debug\nif (isDev) {\n app.use(logAPIRequestURL);\n}\n\n// Sessions\napp.get('/session', getSessionInformation);\napp.use('/api/auth', sessionAuthRouter);\n\n// CSRF\napp.get('/csrf-token', setCSRFToken);\n\n// oAuth2\napp.use(/(.*)/, attachOAuthInstance);\napp.post('/oauth2/token', getOAuth2Token); // Route to get the token\napp.use(/(.*)/, (req, res, next) => {\n // If the request is not already authenticated check the oAuth2 token\n if (!res.locals.authType) {\n return authenticateOAuth2(\n req as RequestWithOAuth2Information,\n res as ResponseWithInformation,\n next\n );\n }\n next();\n});\n\n// CSRF protection\napp.use(/(.*)/, (req, res, next) => {\n // If the request is authenticated using the session auth check the CSRF token\n if (res.locals.authType === 'session') {\n return doubleCsrfProtection(req, res, next);\n }\n next();\n});\n\n// Routes\napp.use('/api/user', userRouter);\napp.use('/api/organization', organizationRouter);\napp.use('/api/project', projectRouter);\napp.use('/api/tag', tagRouter);\napp.use('/api/dictionary', dictionaryRouter);\napp.use('/api/stripe', stripeRouter);\napp.use('/api/ai', aiRouter);\napp.use('/api/event-listener', eventListenerRouter);\n\n// Server\napp.listen(process.env.PORT, () => {\n logger.info(`Listening on port ${process.env.PORT}`);\n});\n\n// Export tu use as serverless function\nexport default app;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["// Libraries\nimport compression from 'compression';\nimport cookieParser from 'cookie-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport dotenv from 'dotenv';\nimport express, { type Express } from 'express';\nimport { intlayer, t } from 'express-intlayer';\nimport helmet from 'helmet';\n\n// Middlewares\nimport {\n type RequestWithOAuth2Information,\n attachOAuthInstance,\n authenticateOAuth2,\n} from '@middlewares/oAuth2.middleware';\nimport { logAPIRequestURL } from '@middlewares/request.middleware';\nimport {\n type ResponseWithInformation,\n checkUser,\n checkOrganization,\n checkProject,\n checkAdmin,\n} from '@middlewares/sessionAuth.middleware';\n\n// Routes\nimport { dictionaryRouter } from '@routes/dictionary.routes';\nimport { organizationRouter } from '@routes/organization.routes';\nimport { projectRouter } from '@routes/project.routes';\nimport { tagRouter } from '@routes/tags.routes';\nimport { sessionAuthRouter } from '@routes/sessionAuth.routes';\nimport { userRouter } from '@routes/user.routes';\nimport { stripeRouter } from '@routes/stripe.routes';\nimport { aiRouter } from '@routes/ai.routes';\nimport { eventListenerRouter } from '@routes/eventListener.routes';\n\n// Webhooks\nimport { stripeWebhook } from '@webhooks/stripe.webhook';\n\n// Controllers\nimport { getOAuth2Token } from '@controllers/oAuth2.controller';\nimport {\n getSessionInformation,\n setCSRFToken,\n} from '@controllers/sessionAuth.controller';\n\n// Utils\nimport { doubleCsrfProtection } from '@utils/CSRF';\nimport { connectDB } from '@utils/mongoDB/connectDB';\n\n// Logger\nimport { logger } from './logger/index';\n\nconst app: Express = express();\n\napp.disable('x-powered-by'); // Disabled to prevent attackers from knowing that the app is running Express\napp.use(helmet());\n\n// Environment variables\nconst env = app.get('env');\n\nlogger.info(`run as ${env}`);\n\ndotenv.config({\n path: [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env'],\n});\n\n// Parse incoming requests with cookies\napp.use(cookieParser());\n\n// Load internationalization request handler\napp.use(intlayer());\n\nconst isDev = env === 'development';\n\n// Connect to MongoDB\nconnectDB();\n\n// Stripe\napp.post(\n '/webhook/stripe',\n express.raw({ type: 'application/json' }),\n stripeWebhook\n);\n\n// Compress all HTTP responses\napp.use(compression());\n\n// Parse incoming requests with JSON payloads\napp.use(express.json({ limit: '50mb' }));\n\n// Parse incoming requests with urlencoded payloads\napp.use(express.urlencoded({ extended: true }));\n\nconst whitelist = [process.env.CLIENT_URL!];\n\n// CORS\nconst corsOptions: CorsOptions = {\n origin: (origin, callback) => {\n // Allow requests with no origin (like mobile apps or curl requests)\n if (!origin) return callback(null, true);\n\n if (whitelist.includes(origin)) {\n logger.info('whitelisted origin', origin);\n return callback(null, true);\n }\n\n logger.info('non whitelisted origin', origin);\n // Reflect the request's origin (echo back the origin header)\n callback(null, origin);\n },\n allowedHeaders: [\n 'authorization',\n 'Content-Type',\n 'credentials',\n 'cache-control',\n 'Access-Control-Allow-Origin',\n 'private-state-token-redemption',\n 'private-state-token-issuance',\n 'browsing-topics',\n ],\n exposedHeaders: [''],\n preflightContinue: false,\n methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',\n credentials: true,\n};\napp.use(cors(corsOptions));\n\n// Liveness check\napp.get('/', (_req, res) => {\n res.send(\n t({\n en: 'Ok - locale: en',\n fr: 'Ok - locale: fr',\n es: 'Ok - locale: es',\n })\n );\n});\n\n// middleware - jwt & session auth\napp.use(/(.*)/, checkUser);\napp.use(/(.*)/, checkOrganization);\napp.use(/(.*)/, checkProject);\napp.use(/(.*)/, checkAdmin);\n\n// debug\nif (isDev) {\n app.use(logAPIRequestURL);\n}\n\n// Sessions\napp.get('/session', getSessionInformation);\napp.use('/api/auth', sessionAuthRouter);\n\n// CSRF\napp.get('/csrf-token', setCSRFToken);\n\n// oAuth2\napp.use(/(.*)/, attachOAuthInstance);\napp.post('/oauth2/token', getOAuth2Token); // Route to get the token\napp.use(/(.*)/, (req, res, next) => {\n // If the request is not already authenticated check the oAuth2 token\n if (!res.locals.authType) {\n return authenticateOAuth2(\n req as RequestWithOAuth2Information,\n res as ResponseWithInformation,\n next\n );\n }\n next();\n});\n\n// CSRF protection\napp.use(/(.*)/, (req, res, next) => {\n // If the request is authenticated using the session auth check the CSRF token\n if (res.locals.authType === 'session') {\n return doubleCsrfProtection(req, res, next);\n }\n next();\n});\n\n// Routes\napp.use('/api/user', userRouter);\napp.use('/api/organization', organizationRouter);\napp.use('/api/project', projectRouter);\napp.use('/api/tag', tagRouter);\napp.use('/api/dictionary', dictionaryRouter);\napp.use('/api/stripe', stripeRouter);\napp.use('/api/ai', aiRouter);\napp.use('/api/event-listener', eventListenerRouter);\n\n// Server\napp.listen(process.env.PORT, () => {\n logger.info(`Listening on port ${process.env.PORT}`);\n});\n\n// Export tu use as serverless function\nexport default app;\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { listenChangeSSE } from "./../controllers/event-listener.mjs";
1
+ import { listenChangeSSE } from "./../controllers/eventListener.controller.mjs";
2
2
  import { Router } from "express";
3
3
  const eventListenerRouter = Router();
4
4
  const baseURL = () => `${process.env.BACKEND_URL}/api/event-listener`;
@@ -17,4 +17,4 @@ export {
17
17
  eventListenerRouter,
18
18
  eventListenerRoutes
19
19
  };
20
- //# sourceMappingURL=event-listener.routes.mjs.map
20
+ //# sourceMappingURL=eventListener.routes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/routes/eventListener.routes.ts"],"sourcesContent":["import { listenChangeSSE } from '@controllers/eventListener.controller';\nimport { Router } from 'express';\nimport type { Routes } from '@/types/Routes';\n\nexport const eventListenerRouter: Router = Router();\n\nconst baseURL = () => `${process.env.BACKEND_URL}/api/event-listener`;\n\nexport const eventListenerRoutes = () =>\n ({\n checkDictionaryChangeSSE: {\n urlModel: '/:accessToken',\n url: ({ accessToken }: { accessToken: string }) =>\n `${baseURL}/${accessToken}`,\n method: 'GET',\n },\n }) satisfies Routes;\n\neventListenerRouter.get(\n eventListenerRoutes().checkDictionaryChangeSSE.urlModel,\n listenChangeSSE\n);\n"],"mappings":"AAAA,SAAS,uBAAuB;AAChC,SAAS,cAAc;AAGhB,MAAM,sBAA8B,OAAO;AAElD,MAAM,UAAU,MAAM,GAAG,QAAQ,IAAI,WAAW;AAEzC,MAAM,sBAAsB,OAChC;AAAA,EACC,0BAA0B;AAAA,IACxB,UAAU;AAAA,IACV,KAAK,CAAC,EAAE,YAAY,MAClB,GAAG,OAAO,IAAI,WAAW;AAAA,IAC3B,QAAQ;AAAA,EACV;AACF;AAEF,oBAAoB;AAAA,EAClB,oBAAoB,EAAE,yBAAyB;AAAA,EAC/C;AACF;","names":[]}
@@ -13,26 +13,7 @@ const findDictionaries = async (filters, skip = 0, limit = 100) => {
13
13
  // Stage 2: Skip for pagination
14
14
  { $skip: skip },
15
15
  // Stage 3: Limit the number of documents
16
- { $limit: limit },
17
- // Stage 4: Add the 'availableVersions' field
18
- {
19
- $addFields: {
20
- availableVersions: {
21
- $map: {
22
- input: { $objectToArray: "$content" },
23
- as: "version",
24
- in: "$$version.k"
25
- }
26
- }
27
- }
28
- }
29
- // (Optional) Stage 5: Project the fields you want to include/exclude
30
- // For example, to exclude the entire 'content' field and keep only 'availableVersions'
31
- // {
32
- // $project: {
33
- // content: 0 // Exclude the 'content' field
34
- // }
35
- // }
16
+ { $limit: limit }
36
17
  ]);
37
18
  const formattedResults = dictionaries.map(
38
19
  (result) => new DictionaryModel(result)
@@ -215,18 +196,18 @@ const deleteDictionaryById = async (dictionaryId) => {
215
196
  }
216
197
  return dictionary;
217
198
  };
199
+ const getVersionNumber = (version) => {
200
+ const match = version.match(/^v(\d+)$/);
201
+ if (!match) {
202
+ throw new Error(`Invalid version format: ${version}`);
203
+ }
204
+ return parseInt(match[1], 10);
205
+ };
218
206
  const incrementVersion = (dictionary) => {
219
207
  const VERSION_PREFIX = "v";
220
- const availableVersions = dictionary.availableVersions ?? [];
221
- const currentVersion = availableVersions.length > 0 ? availableVersions[availableVersions.length - 1] : "v1";
222
- const getVersionNumber = (version) => {
223
- const match = version.match(/^v(\d+)$/);
224
- if (!match) {
225
- throw new Error(`Invalid version format: ${version}`);
226
- }
227
- return parseInt(match[1], 10);
228
- };
229
- let newNumber = getVersionNumber(currentVersion) + 1;
208
+ const availableVersions = [...dictionary.content.keys() ?? []];
209
+ const lastVersion = availableVersions[availableVersions.length - 1];
210
+ let newNumber = getVersionNumber(lastVersion) + 1;
230
211
  let newVersion = `${VERSION_PREFIX}${newNumber}`;
231
212
  while (availableVersions.includes(newVersion)) {
232
213
  newNumber += 1;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { DictionaryModel } from '@models/dictionary.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport type { ObjectId } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Skip for pagination\n { $skip: skip },\n\n // Stage 3: Limit the number of documents\n { $limit: limit },\n\n // Stage 4: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n\n // (Optional) Stage 5: Project the fields you want to include/exclude\n // For example, to exclude the entire 'content' field and keep only 'availableVersions'\n // {\n // $project: {\n // content: 0 // Exclude the 'content' field\n // }\n // }\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n console.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'availableVersions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: dictionaryId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesKeys = async (\n projectId: string | ObjectId\n): Promise<string[]> => {\n const dictionaries = await DictionaryModel.find({\n projectIds: projectId,\n }).select('key');\n\n return dictionaries.map((dictionary) => dictionary.key);\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['_id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\ntype GetExistingDictionaryResult = {\n existingDictionariesKey: string[];\n newDictionariesKey: string[];\n};\n\n/**\n * Gets the existing dictionaries from the provided list of keys.\n * @param dictionariesKeys - List of dictionary keys to check.\n * @param projectId - The ID of the project to check the dictionaries against.\n * @returns The existing dictionaries and the new dictionaries.\n */\nexport const getExistingDictionaryKey = async (\n dictionariesKeys: string[],\n projectId: string | ObjectId\n): Promise<GetExistingDictionaryResult> => {\n // Fetch dictionaries from the database where the key is in the provided list\n const existingDictionaries = await DictionaryModel.find({\n key: { $in: dictionariesKeys },\n projectIds: projectId,\n });\n\n // Map existing dictionaries to a LocalDictionary object\n const existingDictionariesKey: string[] = [];\n const newDictionariesKey: string[] = [];\n\n for (const key of dictionariesKeys) {\n const isDictionaryExist = existingDictionaries.some(\n (dictionary) => dictionary.key === key\n );\n\n if (isDictionaryExist) {\n existingDictionariesKey.push(key);\n } else {\n newDictionariesKey.push(key);\n }\n }\n\n return { existingDictionariesKey, newDictionariesKey };\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryKey,\n projectId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { key: dictionaryKey, projectIds: projectId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const updatedDictionary = await getDictionaryByKey(dictionaryKey, projectId);\n\n return updatedDictionary;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n const availableVersions = dictionary.availableVersions ?? [];\n\n // Get the current version from the version list, default to 'v1' if not present\n const currentVersion =\n availableVersions.length > 0\n ? availableVersions[availableVersions.length - 1]\n : 'v1';\n\n // Function to extract the numeric part of the version\n const getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n };\n\n // Start with the next version number\n let newNumber = getVersionNumber(currentVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (availableVersions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { DictionaryModel } from '@models/dictionary.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport type { ObjectId } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Skip for pagination\n { $skip: skip },\n\n // Stage 3: Limit the number of documents\n { $limit: limit },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n console.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'availableVersions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: dictionaryId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesKeys = async (\n projectId: string | ObjectId\n): Promise<string[]> => {\n const dictionaries = await DictionaryModel.find({\n projectIds: projectId,\n }).select('key');\n\n return dictionaries.map((dictionary) => dictionary.key);\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['_id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\ntype GetExistingDictionaryResult = {\n existingDictionariesKey: string[];\n newDictionariesKey: string[];\n};\n\n/**\n * Gets the existing dictionaries from the provided list of keys.\n * @param dictionariesKeys - List of dictionary keys to check.\n * @param projectId - The ID of the project to check the dictionaries against.\n * @returns The existing dictionaries and the new dictionaries.\n */\nexport const getExistingDictionaryKey = async (\n dictionariesKeys: string[],\n projectId: string | ObjectId\n): Promise<GetExistingDictionaryResult> => {\n // Fetch dictionaries from the database where the key is in the provided list\n const existingDictionaries = await DictionaryModel.find({\n key: { $in: dictionariesKeys },\n projectIds: projectId,\n });\n\n // Map existing dictionaries to a LocalDictionary object\n const existingDictionariesKey: string[] = [];\n const newDictionariesKey: string[] = [];\n\n for (const key of dictionariesKeys) {\n const isDictionaryExist = existingDictionaries.some(\n (dictionary) => dictionary.key === key\n );\n\n if (isDictionaryExist) {\n existingDictionariesKey.push(key);\n } else {\n newDictionariesKey.push(key);\n }\n }\n\n return { existingDictionariesKey, newDictionariesKey };\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryKey,\n projectId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { key: dictionaryKey, projectIds: projectId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const updatedDictionary = await getDictionaryByKey(dictionaryKey, projectId);\n\n return updatedDictionary;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\n// Function to extract the numeric part of the version\nconst getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n\n const availableVersions = [...(dictionary.content.keys() ?? [])];\n const lastVersion = availableVersions[availableVersions.length - 1];\n\n // Start with the next version number\n let newNumber = getVersionNumber(lastVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (availableVersions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n"],"mappings":"AAAA,SAAS,uBAAuB;AAChC,SAAS,mCAAmC;AAC5C,SAAS,oBAAoB;AAE7B,SAAS,wBAAwB;AACjC;AAAA,EAEE;AAAA,OACK;AAgBA,MAAM,mBAAmB,OAC9B,SACA,OAAO,GACP,QAAQ,QAC0B;AAClC,MAAI;AACF,UAAM,eAAe,MAAM,gBAAgB,UAA8B;AAAA;AAAA,MAEvE,EAAE,QAAQ,QAAQ;AAAA;AAAA,MAGlB,EAAE,OAAO,KAAK;AAAA;AAAA,MAGd,EAAE,QAAQ,MAAM;AAAA,IAClB,CAAC;AAED,UAAM,mBAAmB,aAAa;AAAA,MACpC,CAAC,WAAW,IAAI,gBAAgB,MAAM;AAAA,IACxC;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,gCAAgC,KAAK;AACnD,UAAM;AAAA,EACR;AACF;AAYO,MAAM,oBAAoB,OAC/B,iBACgC;AAChC,QAAM,eAAe,MAAM,gBAAgB,UAA8B;AAAA;AAAA,IAEvE,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE;AAAA;AAAA,IAGhC;AAAA,MACE,YAAY;AAAA,QACV,mBAAmB;AAAA,UACjB,MAAM;AAAA,YACJ,OAAO,EAAE,gBAAgB,WAAW;AAAA,YACpC,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,CAAC,aAAa,QAAQ;AACxB,UAAM,IAAI,aAAa,wBAAwB,EAAE,aAAa,CAAC;AAAA,EACjE;AAEA,SAAO,IAAI,gBAAgB,aAAa,CAAC,CAAC;AAC5C;AAOO,MAAM,qBAAqB,OAChC,eACA,cACgC;AAChC,QAAM,eAAe,MAAM,sBAAsB,CAAC,aAAa,GAAG,SAAS;AAE3E,SAAO,aAAa,CAAC;AACvB;AAEO,MAAM,wBAAwB,OACnC,gBACA,cACkC;AAClC,QAAM,eAAe,MAAM,gBAAgB,UAA8B;AAAA;AAAA,IAEvE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,eAAe,GAAG,YAAY,UAAU,EAAE;AAAA;AAAA,IAGlE;AAAA,MACE,YAAY;AAAA,QACV,mBAAmB;AAAA,UACjB,MAAM;AAAA,YACJ,OAAO,EAAE,gBAAgB,WAAW;AAAA,YACpC,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI,aAAa,wBAAwB;AAAA,MAC7C;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,mBAAmB,aAAa;AAAA,IACpC,CAAC,WAAW,IAAI,gBAAgB,MAAM;AAAA,EACxC;AAEA,SAAO;AACT;AAEO,MAAM,sBAAsB,OACjC,cACsB;AACtB,QAAM,eAAe,MAAM,gBAAgB,KAAK;AAAA,IAC9C,YAAY;AAAA,EACd,CAAC,EAAE,OAAO,KAAK;AAEf,SAAO,aAAa,IAAI,CAAC,eAAe,WAAW,GAAG;AACxD;AAEO,MAAM,wBAAwB,OACnC,MACA,cACkC;AAClC,QAAM,eAAe,MAAM,gBAAgB,UAA8B;AAAA;AAAA,IAEvE;AAAA,MACE,QAAQ;AAAA,QACN,MAAM,EAAE,KAAK,KAAK;AAAA,QAClB,YAAY;AAAA,MACd;AAAA,IACF;AAAA;AAAA,IAGA;AAAA,MACE,YAAY;AAAA,QACV,mBAAmB;AAAA,UACjB,MAAM;AAAA,YACJ,OAAO,EAAE,gBAAgB,WAAW;AAAA,YACpC,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,mBAAmB,aAAa;AAAA,IACpC,CAAC,WAAW,IAAI,gBAAgB,MAAM;AAAA,EACxC;AAEA,SAAO;AACT;AAOO,MAAM,oBAAoB,OAC/B,YACoB;AACpB,QAAM,SAAS,MAAM,gBAAgB,eAAe,OAAO;AAE3D,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI,aAAa,2BAA2B,EAAE,QAAQ,CAAC;AAAA,EAC/D;AAEA,SAAO;AACT;AAOO,MAAM,mBAAmB,OAC9B,eACgC;AAChC,QAAM,SAAS,MAAM,mBAAmB,UAAU;AAElD,MAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,UAAM,IAAI,aAAa,6BAA6B;AAAA,MAClD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,MAAM,gBAAgB,OAAO,UAAU;AAChD;AAaO,MAAM,2BAA2B,OACtC,kBACA,cACyC;AAEzC,QAAM,uBAAuB,MAAM,gBAAgB,KAAK;AAAA,IACtD,KAAK,EAAE,KAAK,iBAAiB;AAAA,IAC7B,YAAY;AAAA,EACd,CAAC;AAGD,QAAM,0BAAoC,CAAC;AAC3C,QAAM,qBAA+B,CAAC;AAEtC,aAAW,OAAO,kBAAkB;AAClC,UAAM,oBAAoB,qBAAqB;AAAA,MAC7C,CAAC,eAAe,WAAW,QAAQ;AAAA,IACrC;AAEA,QAAI,mBAAmB;AACrB,8BAAwB,KAAK,GAAG;AAAA,IAClC,OAAO;AACL,yBAAmB,KAAK,GAAG;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO,EAAE,yBAAyB,mBAAmB;AACvD;AAQO,MAAM,uBAAuB,OAClC,cACA,eACgC;AAChC,QAAM,mBAAmB,4BAA4B,UAAU;AAC/D,QAAM,qBAAqB,iBAAiB,kBAAkB,CAAC,KAAK,CAAC;AAErE,QAAM,cAAc,OAAO,KAAK,kBAAkB;AAClD,QAAM,SAAS,MAAM,mBAAmB,oBAAoB,WAAW;AAEvE,MAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,UAAM,IAAI,aAAa,6BAA6B;AAAA,MAClD;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,MAAM,gBAAgB;AAAA,IACnC,EAAE,KAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAEA,MAAI,OAAO,iBAAiB,GAAG;AAC7B,UAAM,IAAI,aAAa,4BAA4B,EAAE,aAAa,CAAC;AAAA,EACrE;AAEA,QAAM,oBAAoB,MAAM,kBAAkB,YAAY;AAE9D,SAAO;AACT;AAQO,MAAM,wBAAwB,OACnC,eACA,YACA,cACgC;AAChC,QAAM,mBAAmB,4BAA4B,UAAU;AAC/D,QAAM,qBAAqB,iBAAiB,kBAAkB,CAAC,KAAK,CAAC;AAErE,QAAM,cAAc,OAAO,KAAK,kBAAkB;AAClD,QAAM,SAAS,MAAM,mBAAmB,oBAAoB,WAAW;AAEvE,MAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,UAAM,IAAI,aAAa,6BAA6B;AAAA,MAClD;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,MAAM,gBAAgB;AAAA,IACnC,EAAE,KAAK,eAAe,YAAY,UAAU;AAAA,IAC5C;AAAA,EACF;AAEA,MAAI,OAAO,iBAAiB,GAAG;AAC7B,UAAM,IAAI,aAAa,4BAA4B,EAAE,cAAc,CAAC;AAAA,EACtE;AAEA,QAAM,oBAAoB,MAAM,mBAAmB,eAAe,SAAS;AAE3E,SAAO;AACT;AAOO,MAAM,uBAAuB,OAClC,iBACgC;AAChC,QAAM,aAAa,MAAM,gBAAgB,kBAAkB,YAAY;AAEvE,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,aAAa,wBAAwB,EAAE,aAAa,CAAC;AAAA,EACjE;AAEA,SAAO;AACT;AAGA,MAAM,mBAAmB,CAAC,YAA4B;AACpD,QAAM,QAAQ,QAAQ,MAAM,UAAU;AACtC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,2BAA2B,OAAO,EAAE;AAAA,EACtD;AACA,SAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAC9B;AAEO,MAAM,mBAAmB,CAAC,eAAmC;AAClE,QAAM,iBAAiB;AAEvB,QAAM,oBAAoB,CAAC,GAAI,WAAW,QAAQ,KAAK,KAAK,CAAC,CAAE;AAC/D,QAAM,cAAc,kBAAkB,kBAAkB,SAAS,CAAC;AAGlE,MAAI,YAAY,iBAAiB,WAAW,IAAI;AAChD,MAAI,aAAa,GAAG,cAAc,GAAG,SAAS;AAG9C,SAAO,kBAAkB,SAAS,UAAU,GAAG;AAC7C,iBAAa;AACb,iBAAa,GAAG,cAAc,GAAG,SAAS;AAAA,EAC5C;AAEA,SAAO;AACT;","names":[]}