@intlayer/backend 7.5.12 → 7.5.14

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 (100) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/blog/en/per-component_vs_centralized_i18n.json +6158 -0
  2. package/dist/assets/utils/AI/askDocQuestion/embeddings/frequent_questions/en/error-vite-env-only.json +2054 -0
  3. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  4. package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -1
  5. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  6. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/github.controller.mjs.map +1 -1
  8. package/dist/esm/controllers/gitlab.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  10. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  12. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  13. package/dist/esm/emails/InviteUserEmail.mjs.map +1 -1
  14. package/dist/esm/emails/MagicLinkEmail.mjs.map +1 -1
  15. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
  16. package/dist/esm/emails/PasswordChangeConfirmation.mjs.map +1 -1
  17. package/dist/esm/emails/ResetUserPassword.mjs.map +1 -1
  18. package/dist/esm/emails/SubscriptionPaymentCancellation.mjs.map +1 -1
  19. package/dist/esm/emails/SubscriptionPaymentError.mjs.map +1 -1
  20. package/dist/esm/emails/SubscriptionPaymentSuccess.mjs.map +1 -1
  21. package/dist/esm/emails/ValidateUserEmail.mjs.map +1 -1
  22. package/dist/esm/emails/Welcome.mjs.map +1 -1
  23. package/dist/esm/index.mjs.map +1 -1
  24. package/dist/esm/services/bitbucket.service.mjs.map +1 -1
  25. package/dist/esm/services/github.service.mjs.map +1 -1
  26. package/dist/esm/services/gitlab.service.mjs.map +1 -1
  27. package/dist/esm/services/oAuth2.service.mjs.map +1 -1
  28. package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
  29. package/dist/esm/services/subscription.service.mjs.map +1 -1
  30. package/dist/esm/services/user.service.mjs.map +1 -1
  31. package/dist/esm/services/webhook.service.mjs.map +1 -1
  32. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  33. package/dist/esm/utils/AI/askDocQuestion/embeddings/blog/en/per-component_vs_centralized_i18n.json +6158 -0
  34. package/dist/esm/utils/AI/askDocQuestion/embeddings/frequent_questions/en/error-vite-env-only.json +2054 -0
  35. package/dist/esm/utils/AI/askDocQuestion/indexMarkdownFiles.mjs.map +1 -1
  36. package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -1
  37. package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
  38. package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -1
  39. package/dist/esm/utils/AI/auditTag/index.mjs.map +1 -1
  40. package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -1
  41. package/dist/esm/utils/AI/customQuery/index.mjs.map +1 -1
  42. package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -1
  43. package/dist/esm/utils/accessControl.mjs.map +1 -1
  44. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  45. package/dist/esm/utils/cors.mjs.map +1 -1
  46. package/dist/esm/utils/ensureArrayQueryFilter.mjs.map +1 -1
  47. package/dist/esm/utils/ensureMongoDocumentToObject.mjs.map +1 -1
  48. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  49. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  50. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  51. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  52. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  53. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  54. package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
  55. package/dist/esm/utils/oAuth2.mjs.map +1 -1
  56. package/dist/esm/utils/permissions.mjs.map +1 -1
  57. package/dist/esm/utils/plan.mjs.map +1 -1
  58. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  59. package/dist/esm/utils/validation/validateArray.mjs.map +1 -1
  60. package/dist/esm/utils/validation/validateDictionary.mjs.map +1 -1
  61. package/dist/esm/utils/validation/validateOrganization.mjs.map +1 -1
  62. package/dist/esm/utils/validation/validateProject.mjs.map +1 -1
  63. package/dist/esm/utils/validation/validateString.mjs.map +1 -1
  64. package/dist/esm/utils/validation/validateTag.mjs.map +1 -1
  65. package/dist/esm/utils/validation/validateUser.mjs.map +1 -1
  66. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  67. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  68. package/dist/types/controllers/gitlab.controller.d.ts.map +1 -1
  69. package/dist/types/emails/InviteUserEmail.d.ts +4 -4
  70. package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
  71. package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
  72. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
  73. package/dist/types/emails/PasswordChangeConfirmation.d.ts +4 -4
  74. package/dist/types/emails/ResetUserPassword.d.ts +4 -4
  75. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
  76. package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
  77. package/dist/types/emails/SubscriptionPaymentError.d.ts.map +1 -1
  78. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
  79. package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
  80. package/dist/types/emails/Welcome.d.ts +4 -4
  81. package/dist/types/models/dictionary.model.d.ts +4 -4
  82. package/dist/types/models/discussion.model.d.ts +3 -3
  83. package/dist/types/models/oAuth2.model.d.ts +3 -3
  84. package/dist/types/schemas/dictionary.schema.d.ts +6 -6
  85. package/dist/types/schemas/discussion.schema.d.ts +6 -6
  86. package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
  87. package/dist/types/schemas/organization.schema.d.ts +6 -6
  88. package/dist/types/schemas/plans.schema.d.ts +6 -6
  89. package/dist/types/schemas/plans.schema.d.ts.map +1 -1
  90. package/dist/types/schemas/project.schema.d.ts +6 -6
  91. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  92. package/dist/types/schemas/session.schema.d.ts +6 -6
  93. package/dist/types/schemas/tag.schema.d.ts +6 -6
  94. package/dist/types/schemas/user.schema.d.ts +6 -6
  95. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  96. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +2 -2
  97. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +2 -2
  98. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +2 -2
  99. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +2 -2
  100. package/package.json +20 -20
@@ -1 +1 @@
1
- {"version":3,"file":"ai.controller.mjs","names":["aiConfig: AIConfig","customQueryUtil.aiDefaultOptions","customQueryUtil.customQuery","translateJSONUtil.aiDefaultOptions","tags: Tag[]","translateJSONUtil.translateJSON","auditContentDeclarationUtil.aiDefaultOptions","auditContentDeclarationUtil.auditDictionary","auditContentDeclarationFieldUtil.aiDefaultOptions","auditContentDeclarationFieldUtil.auditDictionaryField","auditContentDeclarationMetadataUtil.aiDefaultOptions","tagService.findTags","auditContentDeclarationMetadataUtil.auditDictionaryMetadata","auditTagUtil.aiDefaultOptions","dictionaries: Dictionary[]","auditTagUtil.auditTag","askDocQuestionUtil.askDocQuestion","err: any","autocompleteUtil.aiDefaultOptions","autocompleteUtil.autocomplete","numberOfMessagesById: Record<string, number>"],"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import {\n type AIConfig,\n type AIOptions,\n type ChatCompletionRequestMessage,\n getAIConfig,\n} from '@intlayer/ai';\nimport type { KeyPath, Locale } from '@intlayer/types';\nimport { logger } from '@logger';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport * as auditContentDeclarationUtil from '@utils/AI/auditDictionary';\nimport * as auditContentDeclarationFieldUtil from '@utils/AI/auditDictionaryField';\nimport * as auditContentDeclarationMetadataUtil from '@utils/AI/auditDictionaryMetadata';\nimport * as auditTagUtil from '@utils/AI/auditTag';\nimport * as autocompleteUtil from '@utils/AI/autocomplete';\nimport * as customQueryUtil from '@utils/AI/customQuery';\nimport * as translateJSONUtil from '@utils/AI/translateJSON';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DiscussionFiltersParams,\n getDiscussionFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDiscussionFiltersAndPagination';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { DiscussionAPI } from '@/types/discussion.types';\nimport type { Tag, TagAPI } from '@/types/tag.types';\n\ntype ReplaceAIConfigByOptions<T> = Omit<T, 'aiConfig'> & {\n aiOptions?: AIOptions;\n};\n\nexport type CustomQueryBody =\n ReplaceAIConfigByOptions<customQueryUtil.CustomQueryOptions> & {\n tagsKeys?: string[];\n applicationContext?: string;\n };\nexport type CustomQueryResult =\n ResponseData<customQueryUtil.CustomQueryResultData>;\n\nexport const customQuery = async (\n request: FastifyRequest<{ Body: CustomQueryBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { aiOptions, tagsKeys, ...rest } = request.body;\n const { user, project } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: customQueryUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n const auditResponse = await customQueryUtil.customQuery({\n ...rest,\n aiConfig,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'QUERY_FAILED');\n }\n\n const responseData = formatResponse<customQueryUtil.CustomQueryResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TranslateJSONBody = Omit<\n ReplaceAIConfigByOptions<translateJSONUtil.TranslateJSONOptions<JSON>>,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult = ResponseData<\n translateJSONUtil.TranslateJSONResultData<JSON>\n>;\n\nexport const translateJSON = async (\n request: FastifyRequest<{ Body: TranslateJSONBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { aiOptions, tagsKeys, ...rest } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: translateJSONUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId && tagsKeys) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await translateJSONUtil.translateJSON<any>({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData = formatResponse<\n translateJSONUtil.TranslateJSONResultData<any>\n >({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n defaultLocale: Locale;\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n};\nexport type AuditContentDeclarationResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclaration = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditContentDeclarationUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);\n }\n\n const auditResponse = await auditContentDeclarationUtil.auditDictionary({\n fileContent,\n filePath,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n defaultLocale,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationFieldBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationFieldBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditContentDeclarationFieldUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);\n }\n\n const auditResponse =\n await auditContentDeclarationFieldUtil.auditDictionaryField({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n tags,\n keyPath,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>(\n {\n data: auditResponse,\n }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\n\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationMetadataUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationMetadata = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationMetadataBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user } = request.locals || {};\n const { fileContent, aiOptions } = request.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationMetadataUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n const tags: Tag[] = await tagService.findTags(\n {\n organizationId: organization?.id,\n },\n 0,\n 1000\n );\n\n const auditResponse =\n await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationMetadataUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditTagBody = {\n aiOptions?: AIOptions;\n tag: TagAPI;\n};\nexport type AuditTagResult = ResponseData<auditTagUtil.TranslateJSONResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditTag = async (\n request: FastifyRequest<{ Body: AuditTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { aiOptions, tag } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditTagUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let dictionaries: Dictionary[] = [];\n if (project?.organizationId) {\n dictionaries = await getDictionariesByTags([tag.key], project.id);\n }\n\n const auditResponse = await auditTagUtil.auditTag({\n aiConfig,\n dictionaries,\n tag,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData = formatResponse<auditTagUtil.TranslateJSONResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AskDocQuestionBody = {\n messages: ChatCompletionRequestMessage[];\n discussionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n request: FastifyRequest<{ Body: AskDocQuestionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { messages = [], discussionId } = request.body;\n const { user, project, organization } = request.locals || {};\n\n // Hijack response\n reply.hijack();\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n\n // Wrap EVERYTHING in a main try/catch block\n try {\n // Auth Check\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: {},\n projectOptions: projectAIOptions,\n accessType: ['public'],\n },\n !!user\n );\n } catch (_error) {\n // Manually handle this specific error case\n const errorPayload = {\n code: 'AI_ACCESS_DENIED',\n title: 'Access Denied',\n message: 'Unable to configure AI access.',\n };\n reply.raw.write(\n `event: error\\ndata: ${JSON.stringify(errorPayload)}\\n\\n`\n );\n reply.raw.end();\n return;\n }\n\n // Copy Headers\n const headers = reply.getHeaders();\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n reply.raw.setHeader(key, value);\n }\n }\n\n // Set Stream Headers & Flush\n reply.raw.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no');\n\n if (reply.raw.flushHeaders) {\n reply.raw.flushHeaders();\n }\n\n reply.raw.write(': connected\\n\\n');\n\n // Execute AI Logic (Awaited properly)\n // This is where 'generateEmbedding' or 'streamText' will throw\n const fullResponse = await askDocQuestionUtil.askDocQuestion(\n messages,\n aiConfig,\n {\n onMessage: (chunk) => {\n if (!reply.raw.writableEnded) {\n reply.raw.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n }\n },\n }\n );\n\n // Persist Discussion (Only on success)\n const lastUserMessageContent = messages.findLast(\n (m) => m.role === 'user'\n )?.content;\n const lastUserMessageNbWords = lastUserMessageContent\n ? lastUserMessageContent.split(' ').length\n : 0;\n\n if (lastUserMessageNbWords > 2) {\n await DiscussionModel.findOneAndUpdate(\n { discussionId },\n {\n $set: {\n discussionId,\n userId: user?.id,\n projectId: project?.id,\n organizationId: organization?.id,\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp,\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n },\n },\n { upsert: true, new: true }\n );\n }\n\n // Send Completion Event\n if (!reply.raw.writableEnded) {\n reply.raw.write(\n `data: ${JSON.stringify({ done: true, response: fullResponse })}\\n\\n`\n );\n reply.raw.end();\n }\n } catch (err: any) {\n // -------------------------------------------------------------------------\n // CENTRALIZED ERROR CATCHER\n // -------------------------------------------------------------------------\n const errorMessage = err instanceof Error ? err.message : String(err);\n const errorStack = err instanceof Error ? err.stack : undefined;\n\n // Log the full error to your backend console\n logger.error('AI Stream Error Caught:', {\n message: errorMessage,\n stack: errorStack,\n });\n\n // Determine if it's an Auth error (common with OpenAI 401)\n const isAuthError =\n errorMessage.includes('401') ||\n errorMessage.includes('Incorrect API key');\n\n // Format error for Frontend\n const errorPayload = {\n code: isAuthError ? 'AI_AUTH_ERROR' : 'AI_STREAM_ERROR',\n title: isAuthError ? 'AI Configuration Error' : 'Generation Failed',\n message: errorMessage,\n };\n\n // Send error event to client\n if (!reply.raw.writableEnded) {\n reply.raw.write(\n `event: error\\ndata: ${JSON.stringify(errorPayload)}\\n\\n`\n );\n reply.raw.end();\n }\n }\n};\n\nexport type AutocompleteBody = {\n text: string;\n aiOptions?: AIOptions;\n contextBefore?: string;\n currentLine?: string;\n contextAfter?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n request: FastifyRequest<{ Body: AutocompleteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n try {\n const { text, aiOptions, contextBefore, currentLine, contextAfter } =\n request.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: autocompleteUtil.aiDefaultOptions,\n accessType: ['public'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n contextBefore,\n currentLine,\n contextAfter,\n })) ?? {\n autocompletion: '',\n tokenUsed: 0,\n };\n\n const responseData =\n formatResponse<autocompleteUtil.AutocompleteFileResultData>({\n data: response,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDiscussionsParams =\n | ({\n page?: string | number;\n pageSize?: string | number;\n includeMessages?: 'true' | 'false';\n } & DiscussionFiltersParams)\n | undefined;\n\nexport type GetDiscussionsResult = PaginatedResponse<DiscussionAPI>;\n\n/**\n * Retrieves a list of discussions with filters and pagination.\n * Only the owner or admins can access. By default, users only see their own.\n */\nexport const getDiscussions = async (\n request: FastifyRequest<{ Querystring: GetDiscussionsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDiscussionFiltersAndPagination(request);\n const includeMessagesParam = (request.query as any)?.includeMessages as\n | 'true'\n | 'false'\n | undefined;\n const includeMessages = includeMessagesParam !== 'false';\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const projection = includeMessages ? {} : { messages: 0 };\n const discussions = await DiscussionModel.find(filters, projection)\n .sort(sortOptions)\n .skip(skip)\n .limit(pageSize)\n .lean();\n\n // Compute number of messages for each discussion\n const numberOfMessagesById: Record<string, number> = {};\n if (!includeMessages && discussions.length > 0) {\n const ids = discussions.map((d: any) => d._id);\n const counts = await DiscussionModel.aggregate([\n { $match: { _id: { $in: ids } } },\n {\n $project: {\n numberOfMessages: { $size: { $ifNull: ['$messages', []] } },\n },\n },\n ]);\n for (const c of counts as any[]) {\n numberOfMessagesById[String(c._id)] = c.numberOfMessages ?? 0;\n }\n }\n\n // Permission: allow admin, or the owner for all returned entries\n const allOwnedByUser = discussions.every(\n (d) => String(d.userId) === String(user.id)\n );\n const isAllowed = roles?.includes('admin') || allOwnedByUser;\n\n if (!isAllowed) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await DiscussionModel.countDocuments(filters);\n\n const responseData = formatPaginatedResponse({\n data: discussions.map((d: any) => ({\n ...d,\n id: String(d._id ?? d.id),\n numberOfMessages: includeMessages\n ? Array.isArray(d.messages)\n ? d.messages.length\n : 0\n : (numberOfMessagesById[String(d._id ?? d.id)] ?? 0),\n })),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData as any);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai.controller.mjs","names":["customQueryUtil.aiDefaultOptions","customQueryUtil.customQuery","translateJSONUtil.aiDefaultOptions","translateJSONUtil.translateJSON","auditContentDeclarationUtil.aiDefaultOptions","auditContentDeclarationUtil.auditDictionary","auditContentDeclarationFieldUtil.aiDefaultOptions","auditContentDeclarationFieldUtil.auditDictionaryField","auditContentDeclarationMetadataUtil.aiDefaultOptions","tagService.findTags","auditContentDeclarationMetadataUtil.auditDictionaryMetadata","auditTagUtil.aiDefaultOptions","auditTagUtil.auditTag","askDocQuestionUtil.askDocQuestion","autocompleteUtil.aiDefaultOptions","autocompleteUtil.autocomplete"],"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import {\n type AIConfig,\n type AIOptions,\n type ChatCompletionRequestMessage,\n getAIConfig,\n} from '@intlayer/ai';\nimport type { KeyPath, Locale } from '@intlayer/types';\nimport { logger } from '@logger';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport * as auditContentDeclarationUtil from '@utils/AI/auditDictionary';\nimport * as auditContentDeclarationFieldUtil from '@utils/AI/auditDictionaryField';\nimport * as auditContentDeclarationMetadataUtil from '@utils/AI/auditDictionaryMetadata';\nimport * as auditTagUtil from '@utils/AI/auditTag';\nimport * as autocompleteUtil from '@utils/AI/autocomplete';\nimport * as customQueryUtil from '@utils/AI/customQuery';\nimport * as translateJSONUtil from '@utils/AI/translateJSON';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DiscussionFiltersParams,\n getDiscussionFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDiscussionFiltersAndPagination';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { DiscussionAPI } from '@/types/discussion.types';\nimport type { Tag, TagAPI } from '@/types/tag.types';\n\ntype ReplaceAIConfigByOptions<T> = Omit<T, 'aiConfig'> & {\n aiOptions?: AIOptions;\n};\n\nexport type CustomQueryBody =\n ReplaceAIConfigByOptions<customQueryUtil.CustomQueryOptions> & {\n tagsKeys?: string[];\n applicationContext?: string;\n };\nexport type CustomQueryResult =\n ResponseData<customQueryUtil.CustomQueryResultData>;\n\nexport const customQuery = async (\n request: FastifyRequest<{ Body: CustomQueryBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { aiOptions, tagsKeys, ...rest } = request.body;\n const { user, project } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: customQueryUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n const auditResponse = await customQueryUtil.customQuery({\n ...rest,\n aiConfig,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'QUERY_FAILED');\n }\n\n const responseData = formatResponse<customQueryUtil.CustomQueryResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TranslateJSONBody = Omit<\n ReplaceAIConfigByOptions<translateJSONUtil.TranslateJSONOptions<JSON>>,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult = ResponseData<\n translateJSONUtil.TranslateJSONResultData<JSON>\n>;\n\nexport const translateJSON = async (\n request: FastifyRequest<{ Body: TranslateJSONBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { aiOptions, tagsKeys, ...rest } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: translateJSONUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId && tagsKeys) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await translateJSONUtil.translateJSON<any>({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData = formatResponse<\n translateJSONUtil.TranslateJSONResultData<any>\n >({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n defaultLocale: Locale;\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n};\nexport type AuditContentDeclarationResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclaration = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditContentDeclarationUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);\n }\n\n const auditResponse = await auditContentDeclarationUtil.auditDictionary({\n fileContent,\n filePath,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n defaultLocale,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationFieldBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationFieldBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditContentDeclarationFieldUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);\n }\n\n const auditResponse =\n await auditContentDeclarationFieldUtil.auditDictionaryField({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n tags,\n keyPath,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>(\n {\n data: auditResponse,\n }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\n\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationMetadataUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationMetadata = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationMetadataBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user } = request.locals || {};\n const { fileContent, aiOptions } = request.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationMetadataUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n const tags: Tag[] = await tagService.findTags(\n {\n organizationId: organization?.id,\n },\n 0,\n 1000\n );\n\n const auditResponse =\n await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationMetadataUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditTagBody = {\n aiOptions?: AIOptions;\n tag: TagAPI;\n};\nexport type AuditTagResult = ResponseData<auditTagUtil.TranslateJSONResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditTag = async (\n request: FastifyRequest<{ Body: AuditTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { aiOptions, tag } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditTagUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let dictionaries: Dictionary[] = [];\n if (project?.organizationId) {\n dictionaries = await getDictionariesByTags([tag.key], project.id);\n }\n\n const auditResponse = await auditTagUtil.auditTag({\n aiConfig,\n dictionaries,\n tag,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData = formatResponse<auditTagUtil.TranslateJSONResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AskDocQuestionBody = {\n messages: ChatCompletionRequestMessage[];\n discussionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n request: FastifyRequest<{ Body: AskDocQuestionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { messages = [], discussionId } = request.body;\n const { user, project, organization } = request.locals || {};\n\n // Hijack response\n reply.hijack();\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n\n // Wrap EVERYTHING in a main try/catch block\n try {\n // Auth Check\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: {},\n projectOptions: projectAIOptions,\n accessType: ['public'],\n },\n !!user\n );\n } catch (_error) {\n // Manually handle this specific error case\n const errorPayload = {\n code: 'AI_ACCESS_DENIED',\n title: 'Access Denied',\n message: 'Unable to configure AI access.',\n };\n reply.raw.write(\n `event: error\\ndata: ${JSON.stringify(errorPayload)}\\n\\n`\n );\n reply.raw.end();\n return;\n }\n\n // Copy Headers\n const headers = reply.getHeaders();\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n reply.raw.setHeader(key, value);\n }\n }\n\n // Set Stream Headers & Flush\n reply.raw.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no');\n\n if (reply.raw.flushHeaders) {\n reply.raw.flushHeaders();\n }\n\n reply.raw.write(': connected\\n\\n');\n\n // Execute AI Logic (Awaited properly)\n // This is where 'generateEmbedding' or 'streamText' will throw\n const fullResponse = await askDocQuestionUtil.askDocQuestion(\n messages,\n aiConfig,\n {\n onMessage: (chunk) => {\n if (!reply.raw.writableEnded) {\n reply.raw.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n }\n },\n }\n );\n\n // Persist Discussion (Only on success)\n const lastUserMessageContent = messages.findLast(\n (m) => m.role === 'user'\n )?.content;\n const lastUserMessageNbWords = lastUserMessageContent\n ? lastUserMessageContent.split(' ').length\n : 0;\n\n if (lastUserMessageNbWords > 2) {\n await DiscussionModel.findOneAndUpdate(\n { discussionId },\n {\n $set: {\n discussionId,\n userId: user?.id,\n projectId: project?.id,\n organizationId: organization?.id,\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp,\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n },\n },\n { upsert: true, new: true }\n );\n }\n\n // Send Completion Event\n if (!reply.raw.writableEnded) {\n reply.raw.write(\n `data: ${JSON.stringify({ done: true, response: fullResponse })}\\n\\n`\n );\n reply.raw.end();\n }\n } catch (err: any) {\n // -------------------------------------------------------------------------\n // CENTRALIZED ERROR CATCHER\n // -------------------------------------------------------------------------\n const errorMessage = err instanceof Error ? err.message : String(err);\n const errorStack = err instanceof Error ? err.stack : undefined;\n\n // Log the full error to your backend console\n logger.error('AI Stream Error Caught:', {\n message: errorMessage,\n stack: errorStack,\n });\n\n // Determine if it's an Auth error (common with OpenAI 401)\n const isAuthError =\n errorMessage.includes('401') ||\n errorMessage.includes('Incorrect API key');\n\n // Format error for Frontend\n const errorPayload = {\n code: isAuthError ? 'AI_AUTH_ERROR' : 'AI_STREAM_ERROR',\n title: isAuthError ? 'AI Configuration Error' : 'Generation Failed',\n message: errorMessage,\n };\n\n // Send error event to client\n if (!reply.raw.writableEnded) {\n reply.raw.write(\n `event: error\\ndata: ${JSON.stringify(errorPayload)}\\n\\n`\n );\n reply.raw.end();\n }\n }\n};\n\nexport type AutocompleteBody = {\n text: string;\n aiOptions?: AIOptions;\n contextBefore?: string;\n currentLine?: string;\n contextAfter?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n request: FastifyRequest<{ Body: AutocompleteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n try {\n const { text, aiOptions, contextBefore, currentLine, contextAfter } =\n request.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: autocompleteUtil.aiDefaultOptions,\n accessType: ['public'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n contextBefore,\n currentLine,\n contextAfter,\n })) ?? {\n autocompletion: '',\n tokenUsed: 0,\n };\n\n const responseData =\n formatResponse<autocompleteUtil.AutocompleteFileResultData>({\n data: response,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDiscussionsParams =\n | ({\n page?: string | number;\n pageSize?: string | number;\n includeMessages?: 'true' | 'false';\n } & DiscussionFiltersParams)\n | undefined;\n\nexport type GetDiscussionsResult = PaginatedResponse<DiscussionAPI>;\n\n/**\n * Retrieves a list of discussions with filters and pagination.\n * Only the owner or admins can access. By default, users only see their own.\n */\nexport const getDiscussions = async (\n request: FastifyRequest<{ Querystring: GetDiscussionsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDiscussionFiltersAndPagination(request);\n const includeMessagesParam = (request.query as any)?.includeMessages as\n | 'true'\n | 'false'\n | undefined;\n const includeMessages = includeMessagesParam !== 'false';\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const projection = includeMessages ? {} : { messages: 0 };\n const discussions = await DiscussionModel.find(filters, projection)\n .sort(sortOptions)\n .skip(skip)\n .limit(pageSize)\n .lean();\n\n // Compute number of messages for each discussion\n const numberOfMessagesById: Record<string, number> = {};\n if (!includeMessages && discussions.length > 0) {\n const ids = discussions.map((d: any) => d._id);\n const counts = await DiscussionModel.aggregate([\n { $match: { _id: { $in: ids } } },\n {\n $project: {\n numberOfMessages: { $size: { $ifNull: ['$messages', []] } },\n },\n },\n ]);\n for (const c of counts as any[]) {\n numberOfMessagesById[String(c._id)] = c.numberOfMessages ?? 0;\n }\n }\n\n // Permission: allow admin, or the owner for all returned entries\n const allOwnedByUser = discussions.every(\n (d) => String(d.userId) === String(user.id)\n );\n const isAllowed = roles?.includes('admin') || allOwnedByUser;\n\n if (!isAllowed) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await DiscussionModel.countDocuments(filters);\n\n const responseData = formatPaginatedResponse({\n data: discussions.map((d: any) => ({\n ...d,\n id: String(d._id ?? d.id),\n numberOfMessages: includeMessages\n ? Array.isArray(d.messages)\n ? d.messages.length\n : 0\n : (numberOfMessagesById[String(d._id ?? d.id)] ?? 0),\n })),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData as any);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bitbucket.controller.mjs","names":["bitbucketService.getAuthorizationUrl","bitbucketService.exchangeCodeForToken","accessToken: string | undefined","bitbucketService.getUserRepositories","bitbucketService.checkIntlayerConfig","bitbucketService.getRepositoryFileContents"],"sources":["../../../src/controllers/bitbucket.controller.ts"],"sourcesContent":["import * as bitbucketService from '@services/bitbucket.service';\nimport { getBitbucketTokenFromUser } from '@services/bitbucket.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type BitbucketGetAuthUrlQuerystring = {\n redirectUri: string;\n};\n\nexport type BitbucketGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: BitbucketGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = bitbucketService.getAuthorizationUrl(redirectUri);\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketAuthCallbackBody = {\n code: string;\n};\n\nexport type BitbucketAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: BitbucketAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code } = request.body;\n\n if (!code) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_CODE_MISSING'\n );\n }\n\n try {\n const token = await bitbucketService.exchangeCodeForToken(code);\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketListReposQuerystring = {\n token?: string;\n};\n\nexport type BitbucketListReposResult = ResponseData<\n bitbucketService.BitbucketRepository[]\n>;\n\nexport const listRepos = async (\n request: FastifyRequest<{ Querystring: BitbucketListReposQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token } = request.query;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await getBitbucketTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_TOKEN_MISSING'\n );\n }\n\n const repos = await bitbucketService.getUserRepositories(accessToken);\n const responseData = formatResponse<bitbucketService.BitbucketRepository[]>(\n {\n data: repos,\n }\n );\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketCheckConfigBody = {\n token?: string;\n workspace: string;\n repoSlug: string;\n branch?: string;\n};\n\nexport type BitbucketCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[];\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a Bitbucket repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: BitbucketCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, workspace, repoSlug, branch = 'main' } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await getBitbucketTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !workspace || !repoSlug) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n const configPaths = await bitbucketService.checkIntlayerConfig(\n accessToken,\n workspace,\n repoSlug,\n branch\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketGetConfigFileBody = {\n token?: string;\n workspace: string;\n repoSlug: string;\n branch?: string;\n path?: string;\n};\n\nexport type BitbucketGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: BitbucketGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n workspace,\n repoSlug,\n branch = 'main',\n path = 'intlayer.config.ts',\n } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await getBitbucketTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !workspace || !repoSlug) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await bitbucketService.getRepositoryFileContents(\n accessToken,\n workspace,\n repoSlug,\n path,\n branch\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAcA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,gBAAgB,QAAQ;AAEhC,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,iCACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAFMA,oBAAqC,YAAY,EAE9C,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,QAAQ;AAEzB,KAAI,CAAC,KACH,QAAO,aAAa,2BAClB,OACA,yBACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,OAFI,MAAMC,qBAAsC,KAAK,EAE9C,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,YAAY,OACvB,SACA,UACkB;CAClB,MAAM,EAAE,UAAU,QAAQ;CAC1B,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAIC,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eACG,MAAM,0BAA0B,OAAO,OAAO,CAAC,IAAK;AAGzD,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,0BACD;EAIH,MAAM,eAAe,eACnB,EACE,MAHU,MAAMC,oBAAqC,YAAY,EAIlE,CACF;AACD,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,WAAW,UAAU,SAAS,WAAW,QAAQ;CAChE,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAID,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eACG,MAAM,0BAA0B,OAAO,OAAO,CAAC,IAAK;AAGzD,MAAI,CAAC,eAAe,CAAC,aAAa,CAAC,SACjC,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,cAAc,MAAME,oBACxB,aACA,WACA,UACA,OACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,WACA,UACA,SAAS,QACT,OAAO,yBACL,QAAQ;CACZ,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAIF,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eACG,MAAM,0BAA0B,OAAO,OAAO,CAAC,IAAK;AAGzD,MAAI,CAAC,eAAe,CAAC,aAAa,CAAC,SACjC,QAAO,aAAa,2BAClB,OACA,2CACD;EAGH,MAAM,UAAU,MAAMG,0BACpB,aACA,WACA,UACA,MACA,OACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,kCACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}
1
+ {"version":3,"file":"bitbucket.controller.mjs","names":["bitbucketService.getAuthorizationUrl","bitbucketService.exchangeCodeForToken","bitbucketService.getUserRepositories","bitbucketService.checkIntlayerConfig","bitbucketService.getRepositoryFileContents"],"sources":["../../../src/controllers/bitbucket.controller.ts"],"sourcesContent":["import * as bitbucketService from '@services/bitbucket.service';\nimport { getBitbucketTokenFromUser } from '@services/bitbucket.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type BitbucketGetAuthUrlQuerystring = {\n redirectUri: string;\n};\n\nexport type BitbucketGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: BitbucketGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = bitbucketService.getAuthorizationUrl(redirectUri);\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketAuthCallbackBody = {\n code: string;\n};\n\nexport type BitbucketAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: BitbucketAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code } = request.body;\n\n if (!code) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_CODE_MISSING'\n );\n }\n\n try {\n const token = await bitbucketService.exchangeCodeForToken(code);\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketListReposQuerystring = {\n token?: string;\n};\n\nexport type BitbucketListReposResult = ResponseData<\n bitbucketService.BitbucketRepository[]\n>;\n\nexport const listRepos = async (\n request: FastifyRequest<{ Querystring: BitbucketListReposQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token } = request.query;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await getBitbucketTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_TOKEN_MISSING'\n );\n }\n\n const repos = await bitbucketService.getUserRepositories(accessToken);\n const responseData = formatResponse<bitbucketService.BitbucketRepository[]>(\n {\n data: repos,\n }\n );\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketCheckConfigBody = {\n token?: string;\n workspace: string;\n repoSlug: string;\n branch?: string;\n};\n\nexport type BitbucketCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[];\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a Bitbucket repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: BitbucketCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, workspace, repoSlug, branch = 'main' } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await getBitbucketTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !workspace || !repoSlug) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n const configPaths = await bitbucketService.checkIntlayerConfig(\n accessToken,\n workspace,\n repoSlug,\n branch\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type BitbucketGetConfigFileBody = {\n token?: string;\n workspace: string;\n repoSlug: string;\n branch?: string;\n path?: string;\n};\n\nexport type BitbucketGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: BitbucketGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n workspace,\n repoSlug,\n branch = 'main',\n path = 'intlayer.config.ts',\n } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken =\n (await getBitbucketTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !workspace || !repoSlug) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await bitbucketService.getRepositoryFileContents(\n accessToken,\n workspace,\n repoSlug,\n path,\n branch\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'BITBUCKET_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAcA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,gBAAgB,QAAQ;AAEhC,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,iCACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAFMA,oBAAqC,YAAY,EAE9C,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,QAAQ;AAEzB,KAAI,CAAC,KACH,QAAO,aAAa,2BAClB,OACA,yBACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,OAFI,MAAMC,qBAAsC,KAAK,EAE9C,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,YAAY,OACvB,SACA,UACkB;CAClB,MAAM,EAAE,UAAU,QAAQ;CAC1B,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eACG,MAAM,0BAA0B,OAAO,OAAO,CAAC,IAAK;AAGzD,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,0BACD;EAIH,MAAM,eAAe,eACnB,EACE,MAHU,MAAMC,oBAAqC,YAAY,EAIlE,CACF;AACD,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,WAAW,UAAU,SAAS,WAAW,QAAQ;CAChE,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eACG,MAAM,0BAA0B,OAAO,OAAO,CAAC,IAAK;AAGzD,MAAI,CAAC,eAAe,CAAC,aAAa,CAAC,SACjC,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,cAAc,MAAMC,oBACxB,aACA,WACA,UACA,OACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,WACA,UACA,SAAS,QACT,OAAO,yBACL,QAAQ;CACZ,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eACG,MAAM,0BAA0B,OAAO,OAAO,CAAC,IAAK;AAGzD,MAAI,CAAC,eAAe,CAAC,aAAa,CAAC,SACjC,QAAO,aAAa,2BAClB,OACA,2CACD;EAGH,MAAM,UAAU,MAAMC,0BACpB,aACA,WACA,UACA,MACA,OACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,kCACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"dictionary.controller.mjs","names":["clone: T","dictionaryService.findDictionaries","dictionaryService.countDictionaries","dictionariesUpdateTimestamp: Record<\n string,\n { key: string; updatedAt: number }\n >","dictionaryService.getDictionaryByKey","dictionary: DictionaryData","dictionaryService.createDictionary","projectService.getProjectById","webhooksService.triggerAll","newDictionariesResult: PushDictionariesResultData['newDictionaries']","updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries']","errorResult: PushDictionariesResultData['error']","dictionaryService.getDictionaryById","newContent: VersionedContent","dictionaryService.incrementVersion","dictionaryService.updateDictionaryByKey","result: PushDictionariesResultData","dictionaryService.updateDictionaryById","dictionaryService.deleteDictionaryById"],"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import { isDeepStrictEqual } from 'node:util';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n DictionaryId,\n Dictionary as LocalDictionary,\n LocalDictionaryId,\n} from '@intlayer/types';\nimport { logger } from '@logger';\nimport * as dictionaryService from '@services/dictionary.service';\nimport * as projectService from '@services/project.service';\nimport * as webhooksService from '@services/webhook.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\nconst removeMetadata = <T extends Record<string, any>>(obj: T): T => {\n if (Array.isArray(obj)) {\n return obj.map(removeMetadata) as unknown as T;\n }\n\n if (obj && typeof obj === 'object') {\n const clone: T = {} as T;\n for (const key in obj) {\n if (key !== 'metadata') {\n clone[key] = removeMetadata(obj[key]);\n }\n }\n return clone as T;\n }\n\n return obj as T;\n};\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n request: FastifyRequest<{ Querystring: GetDictionariesParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(request);\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) => mapDictionaryToAPI(el));\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { project, roles } = _request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ..._request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionariesUpdateTimestampResult = ResponseData<\n Record<DictionaryId, { key: string; updatedAt: number }>\n>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesUpdateTimestamp = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { project, roles } = _request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ..._request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const dictionariesUpdateTimestamp: Record<\n string,\n { key: string; updatedAt: number }\n > = {};\n for (const dictionary of dictionaries) {\n dictionariesUpdateTimestamp[dictionary.id] = {\n key: dictionary.key,\n updatedAt: new Date(dictionary.updatedAt).getTime(),\n };\n }\n\n const responseData = formatResponse<\n Record<string, { key: string; updatedAt: number }>\n >({\n data: dictionariesUpdateTimestamp,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n request: FastifyRequest<{\n Params: GetDictionaryParams;\n Querystring: GetDictionaryQuery;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n const { dictionaryKey } = request.params;\n const version = request.query.version;\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n request: FastifyRequest<{ Body: AddDictionaryBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n const dictionaryData = request.body.dictionary;\n\n if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n [\n 'v1',\n {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: removeMetadata(dictionaryData.content ?? {}) as ContentNode,\n },\n ],\n ]),\n creatorId: user.id,\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary creation\n logger.error(\n 'Failed to trigger CI builds after dictionary creation',\n error\n );\n }\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n updatedDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n error: {\n id: string | undefined;\n key: string;\n localId: LocalDictionaryId | undefined;\n message: string;\n }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n */\nexport const pushDictionaries = async (\n request: FastifyRequest<{ Body: PushDictionariesBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n\n // Normalize the input: handle both { dictionaries: [...] } and { dictionaries: { dictionaries: [...] } }\n // The latter can happen due to client-side double-wrapping issues\n let dictionaryData = request.body.dictionaries;\n if (\n dictionaryData &&\n !Array.isArray(dictionaryData) &&\n typeof dictionaryData === 'object' &&\n 'dictionaries' in dictionaryData &&\n Array.isArray(\n (dictionaryData as unknown as PushDictionariesBody).dictionaries\n )\n ) {\n dictionaryData = (dictionaryData as unknown as PushDictionariesBody)\n .dictionaries;\n }\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARIES_NOT_PROVIDED'\n );\n } else if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id !== undefined\n );\n const newDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id === undefined\n );\n\n const newDictionariesResult: PushDictionariesResultData['newDictionaries'] =\n [];\n const updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries'] =\n [];\n const errorResult: PushDictionariesResultData['error'] = [];\n\n for (const dictionaryDataEl of newDictionaries) {\n const dictionary: DictionaryData = {\n title: dictionaryDataEl.title,\n description: dictionaryDataEl.description,\n projectIds: [String(project.id)],\n creatorId: user.id,\n content: new Map([\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n\n [\n 'v1',\n {\n content:\n removeMetadata(dictionaryDataEl.content) ?? ({} as ContentNode),\n },\n ],\n ]),\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push({\n key: newDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: newDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n for (const dictionaryDataEl of existingDictionaries) {\n const remoteDictionary = await dictionaryService.getDictionaryById(\n dictionaryDataEl.id!\n );\n\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n const cleanedContent = removeMetadata(dictionaryDataEl.content);\n\n const versionList = [...(remoteDictionary.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (remoteDictionary.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent = isDeepStrictEqual(lastContent, cleanedContent);\n\n const newContent: VersionedContent = new Map(remoteDictionary.content);\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(remoteDictionary);\n\n newContent.set(newContentVersion, {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: cleanedContent,\n });\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(remoteDictionary),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n key: remoteDictionary.key,\n };\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryByKey(\n remoteDictionary.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push({\n key: updatedDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: updatedDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult,\n updatedDictionaries: updatedDictionariesResult,\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n // Trigger CI builds if configured (only if there were actual changes)\n if (\n project &&\n (newDictionariesResult.length > 0 || updatedDictionariesResult.length > 0)\n ) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary push\n logger.error(\n 'Failed to trigger CI builds after dictionary push',\n error\n );\n }\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n request: FastifyRequest<{\n Params: UpdateDictionaryParam;\n Body: UpdateDictionaryBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { dictionaryId } = request.params;\n const { project, roles } = request.locals || {};\n const dictionaryData = request.body;\n\n if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n if (typeof dictionaryId === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_ID_NOT_FOUND'\n );\n }\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary update\n logger.error(\n 'Failed to trigger CI builds after dictionary update',\n error\n );\n }\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n request: FastifyRequest<{ Params: DeleteDictionaryParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n const { dictionaryId } = request.params;\n\n if (!dictionaryId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_ID_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!hasPermission(roles || [], 'dictionary:admin')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_NOT_FOUND',\n {\n dictionaryId,\n }\n );\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dictionary.controller.mjs","names":["dictionaryService.findDictionaries","dictionaryService.countDictionaries","dictionaryService.getDictionaryByKey","dictionaryService.createDictionary","projectService.getProjectById","webhooksService.triggerAll","dictionaryService.getDictionaryById","dictionaryService.incrementVersion","dictionaryService.updateDictionaryByKey","dictionaryService.updateDictionaryById","dictionaryService.deleteDictionaryById"],"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import { isDeepStrictEqual } from 'node:util';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n DictionaryId,\n Dictionary as LocalDictionary,\n LocalDictionaryId,\n} from '@intlayer/types';\nimport { logger } from '@logger';\nimport * as dictionaryService from '@services/dictionary.service';\nimport * as projectService from '@services/project.service';\nimport * as webhooksService from '@services/webhook.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\nconst removeMetadata = <T extends Record<string, any>>(obj: T): T => {\n if (Array.isArray(obj)) {\n return obj.map(removeMetadata) as unknown as T;\n }\n\n if (obj && typeof obj === 'object') {\n const clone: T = {} as T;\n for (const key in obj) {\n if (key !== 'metadata') {\n clone[key] = removeMetadata(obj[key]);\n }\n }\n return clone as T;\n }\n\n return obj as T;\n};\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n request: FastifyRequest<{ Querystring: GetDictionariesParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(request);\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) => mapDictionaryToAPI(el));\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { project, roles } = _request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ..._request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionariesUpdateTimestampResult = ResponseData<\n Record<DictionaryId, { key: string; updatedAt: number }>\n>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesUpdateTimestamp = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { project, roles } = _request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ..._request.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const dictionariesUpdateTimestamp: Record<\n string,\n { key: string; updatedAt: number }\n > = {};\n for (const dictionary of dictionaries) {\n dictionariesUpdateTimestamp[dictionary.id] = {\n key: dictionary.key,\n updatedAt: new Date(dictionary.updatedAt).getTime(),\n };\n }\n\n const responseData = formatResponse<\n Record<string, { key: string; updatedAt: number }>\n >({\n data: dictionariesUpdateTimestamp,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n request: FastifyRequest<{\n Params: GetDictionaryParams;\n Querystring: GetDictionaryQuery;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n const { dictionaryKey } = request.params;\n const version = request.query.version;\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles || [],\n 'dictionary:read'\n )({\n ...request.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n request: FastifyRequest<{ Body: AddDictionaryBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n const dictionaryData = request.body.dictionary;\n\n if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n [\n 'v1',\n {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: removeMetadata(dictionaryData.content ?? {}) as ContentNode,\n },\n ],\n ]),\n creatorId: user.id,\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary),\n status: 'ADDED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary creation\n logger.error(\n 'Failed to trigger CI builds after dictionary creation',\n error\n );\n }\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n updatedDictionaries: {\n key: string;\n localId: LocalDictionaryId;\n id: string | undefined;\n }[];\n error: {\n id: string | undefined;\n key: string;\n localId: LocalDictionaryId | undefined;\n message: string;\n }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n */\nexport const pushDictionaries = async (\n request: FastifyRequest<{ Body: PushDictionariesBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user, roles } = request.locals || {};\n\n // Normalize the input: handle both { dictionaries: [...] } and { dictionaries: { dictionaries: [...] } }\n // The latter can happen due to client-side double-wrapping issues\n let dictionaryData = request.body.dictionaries;\n if (\n dictionaryData &&\n !Array.isArray(dictionaryData) &&\n typeof dictionaryData === 'object' &&\n 'dictionaries' in dictionaryData &&\n Array.isArray(\n (dictionaryData as unknown as PushDictionariesBody).dictionaries\n )\n ) {\n dictionaryData = (dictionaryData as unknown as PushDictionariesBody)\n .dictionaries;\n }\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARIES_NOT_PROVIDED'\n );\n } else if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id !== undefined\n );\n const newDictionaries = dictionaryData.filter(\n (dictionary) => dictionary.id === undefined\n );\n\n const newDictionariesResult: PushDictionariesResultData['newDictionaries'] =\n [];\n const updatedDictionariesResult: PushDictionariesResultData['updatedDictionaries'] =\n [];\n const errorResult: PushDictionariesResultData['error'] = [];\n\n for (const dictionaryDataEl of newDictionaries) {\n const dictionary: DictionaryData = {\n title: dictionaryDataEl.title,\n description: dictionaryDataEl.description,\n projectIds: [String(project.id)],\n creatorId: user.id,\n content: new Map([\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n\n [\n 'v1',\n {\n content:\n removeMetadata(dictionaryDataEl.content) ?? ({} as ContentNode),\n },\n ],\n ]),\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push({\n key: newDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: newDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n for (const dictionaryDataEl of existingDictionaries) {\n const remoteDictionary = await dictionaryService.getDictionaryById(\n dictionaryDataEl.id!\n );\n\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n const cleanedContent = removeMetadata(dictionaryDataEl.content);\n\n const versionList = [...(remoteDictionary.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (remoteDictionary.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent = isDeepStrictEqual(lastContent, cleanedContent);\n\n const newContent: VersionedContent = new Map(remoteDictionary.content);\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(remoteDictionary);\n\n newContent.set(newContentVersion, {\n // Remove metadata as markdown metadata are dynamic data inserted at build time\n content: cleanedContent,\n });\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(remoteDictionary),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n key: remoteDictionary.key,\n };\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryByKey(\n remoteDictionary.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push({\n key: updatedDictionary.key,\n localId: dictionaryDataEl.localId!,\n id: updatedDictionary.id,\n });\n } catch (error) {\n errorResult.push({\n id: dictionaryDataEl.id!,\n key: dictionaryDataEl.key,\n localId: dictionaryDataEl.localId!,\n message: (error as AppError).message,\n });\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult,\n updatedDictionaries: updatedDictionariesResult,\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n // Trigger CI builds if configured (only if there were actual changes)\n if (\n project &&\n (newDictionariesResult.length > 0 || updatedDictionariesResult.length > 0)\n ) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary push\n logger.error(\n 'Failed to trigger CI builds after dictionary push',\n error\n );\n }\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n request: FastifyRequest<{\n Params: UpdateDictionaryParam;\n Body: UpdateDictionaryBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { dictionaryId } = request.params;\n const { project, roles } = request.locals || {};\n const dictionaryData = request.body;\n\n if (!dictionaryData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_DATA_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n if (typeof dictionaryId === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_ID_NOT_FOUND'\n );\n }\n\n if (!hasPermission(roles || [], 'dictionary:write')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n // Trigger CI builds if configured\n if (project) {\n try {\n const fullProject = await projectService.getProjectById(project.id);\n await webhooksService.triggerAll(fullProject);\n } catch (error) {\n // Log error but don't fail the dictionary update\n logger.error(\n 'Failed to trigger CI builds after dictionary update',\n error\n );\n }\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n request: FastifyRequest<{ Params: DeleteDictionaryParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n const { dictionaryId } = request.params;\n\n if (!dictionaryId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_ID_NOT_FOUND'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!hasPermission(roles || [], 'dictionary:admin')(request.locals)) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'DICTIONARY_NOT_FOUND',\n {\n dictionaryId,\n }\n );\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"eventListener.controller.mjs","names":["clients: Array<{\n id: number;\n projectId: string;\n res: { raw: FastifyReply['raw'] };\n}>","data: MessageEventData[]"],"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { ErrorHandler } from '@utils/errors';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport type { DictionaryAPI } from '@/types/dictionary.types';\n\nexport type Object = 'DICTIONARY';\nexport type Status = 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n\nexport type MessageEventData = {\n object: Object;\n status: Status;\n data: any;\n};\n\nlet clients: Array<{\n id: number;\n projectId: string;\n res: { raw: FastifyReply['raw'] };\n}> = [];\nconst MAX_SSE_CONNECTIONS = 10;\n\nexport type SendDictionaryUpdateArg = {\n dictionary: DictionaryAPI;\n status: 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n};\n\nexport const sendDictionaryUpdate = (args: SendDictionaryUpdateArg[]) => {\n const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);\n\n const filteredClients = clients.filter((client) =>\n projectIds.map((id) => String(id)).includes(String(client.projectId))\n );\n\n const data: MessageEventData[] = args.map((arg) => ({\n object: 'DICTIONARY',\n status: arg.status,\n data: arg.dictionary,\n }));\n\n process.nextTick(() => {\n for (const client of filteredClients) {\n client.res.raw.write(`data: ${JSON.stringify(data)}\\n\\n`);\n }\n });\n};\n\nexport type CheckDictionaryChangeSSEParams = { accessToken: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const listenChangeSSE = async (\n request: FastifyRequest<{ Params: CheckDictionaryChangeSSEParams }>,\n reply: FastifyReply\n) => {\n const { project } = request.locals || {};\n\n if (clients.length >= MAX_SSE_CONNECTIONS) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TOO_MANY_CONNECTIONS'\n );\n }\n\n // Set headers for SSE\n reply.raw.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n reply.raw.write(':\\n\\n'); // Comment to keep connection alive\n reply.raw.flushHeaders?.();\n\n const clientId = Date.now();\n\n // Add client to the list\n const newClient = {\n id: clientId,\n projectId: String(project?.id),\n res: { raw: reply.raw },\n };\n clients.push(newClient);\n\n logger.info(\n `New client connected to SSE. Total clients: ${clients.length ?? 0}`\n );\n\n // Remove client on connection close\n request.raw.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":";;;;AAcA,IAAIA,UAIC,EAAE;AACP,MAAM,sBAAsB;AAO5B,MAAa,wBAAwB,SAAoC;CACvE,MAAM,aAAa,KAAK,SAAS,QAAQ,IAAI,WAAW,WAAW;CAEnE,MAAM,kBAAkB,QAAQ,QAAQ,WACtC,WAAW,KAAK,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,OAAO,OAAO,UAAU,CAAC,CACtE;CAED,MAAMC,OAA2B,KAAK,KAAK,SAAS;EAClD,QAAQ;EACR,QAAQ,IAAI;EACZ,MAAM,IAAI;EACX,EAAE;AAEH,SAAQ,eAAe;AACrB,OAAK,MAAM,UAAU,gBACnB,QAAO,IAAI,IAAI,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC,MAAM;GAE3D;;;;;AAQJ,MAAa,kBAAkB,OAC7B,SACA,UACG;CACH,MAAM,EAAE,YAAY,QAAQ,UAAU,EAAE;AAExC,KAAI,QAAQ,UAAU,oBACpB,QAAO,aAAa,2BAClB,OACA,uBACD;AAIH,OAAM,IAAI,UAAU,gBAAgB,kCAAkC;AACtE,OAAM,IAAI,UAAU,iBAAiB,yBAAyB;AAC9D,OAAM,IAAI,UAAU,cAAc,aAAa;AAC/C,OAAM,IAAI,UAAU,qBAAqB,KAAK;AAG9C,OAAM,IAAI,MAAM,QAAQ;AACxB,OAAM,IAAI,gBAAgB;CAE1B,MAAM,WAAW,KAAK,KAAK;CAG3B,MAAM,YAAY;EAChB,IAAI;EACJ,WAAW,OAAO,SAAS,GAAG;EAC9B,KAAK,EAAE,KAAK,MAAM,KAAK;EACxB;AACD,SAAQ,KAAK,UAAU;AAEvB,QAAO,KACL,+CAA+C,QAAQ,UAAU,IAClE;AAGD,SAAQ,IAAI,GAAG,eAAe;AAC5B,YAAU,QAAQ,QAAQ,WAAW,OAAO,OAAO,SAAS;GAC5D"}
1
+ {"version":3,"file":"eventListener.controller.mjs","names":[],"sources":["../../../src/controllers/eventListener.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { ErrorHandler } from '@utils/errors';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport type { DictionaryAPI } from '@/types/dictionary.types';\n\nexport type Object = 'DICTIONARY';\nexport type Status = 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n\nexport type MessageEventData = {\n object: Object;\n status: Status;\n data: any;\n};\n\nlet clients: Array<{\n id: number;\n projectId: string;\n res: { raw: FastifyReply['raw'] };\n}> = [];\nconst MAX_SSE_CONNECTIONS = 10;\n\nexport type SendDictionaryUpdateArg = {\n dictionary: DictionaryAPI;\n status: 'ADDED' | 'UPDATED' | 'DELETED' | 'CREATED';\n};\n\nexport const sendDictionaryUpdate = (args: SendDictionaryUpdateArg[]) => {\n const projectIds = args.flatMap((arg) => arg.dictionary.projectIds);\n\n const filteredClients = clients.filter((client) =>\n projectIds.map((id) => String(id)).includes(String(client.projectId))\n );\n\n const data: MessageEventData[] = args.map((arg) => ({\n object: 'DICTIONARY',\n status: arg.status,\n data: arg.dictionary,\n }));\n\n process.nextTick(() => {\n for (const client of filteredClients) {\n client.res.raw.write(`data: ${JSON.stringify(data)}\\n\\n`);\n }\n });\n};\n\nexport type CheckDictionaryChangeSSEParams = { accessToken: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const listenChangeSSE = async (\n request: FastifyRequest<{ Params: CheckDictionaryChangeSSEParams }>,\n reply: FastifyReply\n) => {\n const { project } = request.locals || {};\n\n if (clients.length >= MAX_SSE_CONNECTIONS) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TOO_MANY_CONNECTIONS'\n );\n }\n\n // Set headers for SSE\n reply.raw.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n reply.raw.write(':\\n\\n'); // Comment to keep connection alive\n reply.raw.flushHeaders?.();\n\n const clientId = Date.now();\n\n // Add client to the list\n const newClient = {\n id: clientId,\n projectId: String(project?.id),\n res: { raw: reply.raw },\n };\n clients.push(newClient);\n\n logger.info(\n `New client connected to SSE. Total clients: ${clients.length ?? 0}`\n );\n\n // Remove client on connection close\n request.raw.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":";;;;AAcA,IAAI,UAIC,EAAE;AACP,MAAM,sBAAsB;AAO5B,MAAa,wBAAwB,SAAoC;CACvE,MAAM,aAAa,KAAK,SAAS,QAAQ,IAAI,WAAW,WAAW;CAEnE,MAAM,kBAAkB,QAAQ,QAAQ,WACtC,WAAW,KAAK,OAAO,OAAO,GAAG,CAAC,CAAC,SAAS,OAAO,OAAO,UAAU,CAAC,CACtE;CAED,MAAM,OAA2B,KAAK,KAAK,SAAS;EAClD,QAAQ;EACR,QAAQ,IAAI;EACZ,MAAM,IAAI;EACX,EAAE;AAEH,SAAQ,eAAe;AACrB,OAAK,MAAM,UAAU,gBACnB,QAAO,IAAI,IAAI,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC,MAAM;GAE3D;;;;;AAQJ,MAAa,kBAAkB,OAC7B,SACA,UACG;CACH,MAAM,EAAE,YAAY,QAAQ,UAAU,EAAE;AAExC,KAAI,QAAQ,UAAU,oBACpB,QAAO,aAAa,2BAClB,OACA,uBACD;AAIH,OAAM,IAAI,UAAU,gBAAgB,kCAAkC;AACtE,OAAM,IAAI,UAAU,iBAAiB,yBAAyB;AAC9D,OAAM,IAAI,UAAU,cAAc,aAAa;AAC/C,OAAM,IAAI,UAAU,qBAAqB,KAAK;AAG9C,OAAM,IAAI,MAAM,QAAQ;AACxB,OAAM,IAAI,gBAAgB;CAE1B,MAAM,WAAW,KAAK,KAAK;CAG3B,MAAM,YAAY;EAChB,IAAI;EACJ,WAAW,OAAO,SAAS,GAAG;EAC9B,KAAK,EAAE,KAAK,MAAM,KAAK;EACxB;AACD,SAAQ,KAAK,UAAU;AAEvB,QAAO,KACL,+CAA+C,QAAQ,UAAU,IAClE;AAGD,SAAQ,IAAI,GAAG,eAAe;AAC5B,YAAU,QAAQ,QAAQ,WAAW,OAAO,OAAO,SAAS;GAC5D"}
@@ -1 +1 @@
1
- {"version":3,"file":"github.controller.mjs","names":["githubService.getAuthorizationUrl","githubService.exchangeCodeForToken","accessToken: string | undefined","githubService.getUserRepos","githubService.checkIntlayerConfig","githubService.getRepositoryFileContents"],"sources":["../../../src/controllers/github.controller.ts"],"sourcesContent":["import * as githubService from '@services/github.service';\nimport { getGitHubTokenFromUser } from '@services/github.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitHubGetAuthUrlQuerystring = {\n redirectUri: string;\n login?: string;\n};\n\nexport type GitHubGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitHubGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = githubService.getAuthorizationUrl(redirectUri, login);\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubAuthCallbackBody = {\n code: string;\n};\n\nexport type GitHubAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitHubAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code } = request.body;\n\n if (!code) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CODE_MISSING'\n );\n }\n\n try {\n const token = await githubService.exchangeCodeForToken(code);\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubListReposQuerystring = {\n token?: string;\n};\n\nexport type GitHubListReposResult = ResponseData<\n githubService.GitHubRepository[]\n>;\n\nexport const listRepos = async (\n request: FastifyRequest<{ Querystring: GitHubListReposQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token } = request.query;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitHubTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_TOKEN_MISSING'\n );\n }\n\n const repos = await githubService.getUserRepos(accessToken);\n const responseData = formatResponse<githubService.GitHubRepository[]>({\n data: repos,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubCheckConfigBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n};\n\nexport type GitHubCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[]; // Changed from single path to array\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitHubCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, owner, repository, branch = 'main' } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitHubTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n // Returns array of strings\n const configPaths = await githubService.checkIntlayerConfig(\n accessToken,\n owner,\n repository,\n branch\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubGetConfigFileBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n path?: string;\n};\n\nexport type GitHubGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitHubGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n owner,\n repository,\n branch = 'main',\n path = 'intlayer.config.ts',\n } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitHubTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await githubService.getRepositoryFileContents(\n accessToken,\n owner,\n repository,\n path,\n branch\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAeA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,aAAa,UAAU,QAAQ;AAEvC,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,8BACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAFMA,oBAAkC,aAAa,MAAM,EAElD,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,QAAQ;AAEzB,KAAI,CAAC,KACH,QAAO,aAAa,2BAClB,OACA,sBACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,OAFI,MAAMC,qBAAmC,KAAK,EAE3C,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,YAAY,OACvB,SACA,UACkB;CAClB,MAAM,EAAE,UAAU,QAAQ;CAC1B,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAIC,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,uBACD;EAIH,MAAM,eAAe,eAAiD,EACpE,MAFY,MAAMC,aAA2B,YAAY,EAG1D,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,OAAO,YAAY,SAAS,WAAW,QAAQ;CAC9D,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAID,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,SAAS,CAAC,WAC7B,QAAO,aAAa,2BAClB,OACA,qCACD;EAIH,MAAM,cAAc,MAAME,oBACxB,aACA,OACA,YACA,OACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,OACA,YACA,SAAS,QACT,OAAO,yBACL,QAAQ;CACZ,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAIF,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,SAAS,CAAC,WAC7B,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,UAAU,MAAMG,0BACpB,aACA,OACA,YACA,MACA,OACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,+BACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}
1
+ {"version":3,"file":"github.controller.mjs","names":["githubService.getAuthorizationUrl","githubService.exchangeCodeForToken","githubService.getUserRepos","githubService.checkIntlayerConfig","githubService.getRepositoryFileContents"],"sources":["../../../src/controllers/github.controller.ts"],"sourcesContent":["import * as githubService from '@services/github.service';\nimport { getGitHubTokenFromUser } from '@services/github.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitHubGetAuthUrlQuerystring = {\n redirectUri: string;\n login?: string;\n};\n\nexport type GitHubGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitHubGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = githubService.getAuthorizationUrl(redirectUri, login);\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubAuthCallbackBody = {\n code: string;\n};\n\nexport type GitHubAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitHubAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code } = request.body;\n\n if (!code) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CODE_MISSING'\n );\n }\n\n try {\n const token = await githubService.exchangeCodeForToken(code);\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubListReposQuerystring = {\n token?: string;\n};\n\nexport type GitHubListReposResult = ResponseData<\n githubService.GitHubRepository[]\n>;\n\nexport const listRepos = async (\n request: FastifyRequest<{ Querystring: GitHubListReposQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token } = request.query;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitHubTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_TOKEN_MISSING'\n );\n }\n\n const repos = await githubService.getUserRepos(accessToken);\n const responseData = formatResponse<githubService.GitHubRepository[]>({\n data: repos,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubCheckConfigBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n};\n\nexport type GitHubCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[]; // Changed from single path to array\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitHubCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, owner, repository, branch = 'main' } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitHubTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n // Returns array of strings\n const configPaths = await githubService.checkIntlayerConfig(\n accessToken,\n owner,\n repository,\n branch\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitHubGetConfigFileBody = {\n token?: string;\n owner: string;\n repository: string;\n branch?: string;\n path?: string;\n};\n\nexport type GitHubGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitHubGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n owner,\n repository,\n branch = 'main',\n path = 'intlayer.config.ts',\n } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitHubTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !owner || !repository) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await githubService.getRepositoryFileContents(\n accessToken,\n owner,\n repository,\n path,\n branch\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITHUB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAeA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,aAAa,UAAU,QAAQ;AAEvC,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,8BACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAFMA,oBAAkC,aAAa,MAAM,EAElD,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,QAAQ;AAEzB,KAAI,CAAC,KACH,QAAO,aAAa,2BAClB,OACA,sBACD;AAGH,KAAI;EAEF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,OAFI,MAAMC,qBAAmC,KAAK,EAE3C,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAYxE,MAAa,YAAY,OACvB,SACA,UACkB;CAClB,MAAM,EAAE,UAAU,QAAQ;CAC1B,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,uBACD;EAIH,MAAM,eAAe,eAAiD,EACpE,MAFY,MAAMC,aAA2B,YAAY,EAG1D,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,OAAO,YAAY,SAAS,WAAW,QAAQ;CAC9D,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,SAAS,CAAC,WAC7B,QAAO,aAAa,2BAClB,OACA,qCACD;EAIH,MAAM,cAAc,MAAMC,oBACxB,aACA,OACA,YACA,OACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,OACA,YACA,SAAS,QACT,OAAO,yBACL,QAAQ;CACZ,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,SAAS,CAAC,WAC7B,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,UAAU,MAAMC,0BACpB,aACA,OACA,YACA,MACA,OACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,+BACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"gitlab.controller.mjs","names":["gitlabService.getAuthorizationUrl","gitlabService.exchangeCodeForToken","accessToken: string | undefined","gitlabService.getUserProjects","gitlabService.checkIntlayerConfig","gitlabService.getRepositoryFileContents"],"sources":["../../../src/controllers/gitlab.controller.ts"],"sourcesContent":["import * as gitlabService from '@services/gitlab.service';\nimport { getGitLabTokenFromUser } from '@services/gitlab.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitLabGetAuthUrlQuerystring = {\n redirectUri: string;\n instanceUrl?: string;\n login?: string;\n};\n\nexport type GitLabGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitLabGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, instanceUrl, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = gitlabService.getAuthorizationUrl(\n redirectUri,\n instanceUrl,\n login\n );\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabAuthCallbackBody = {\n code: string;\n redirectUri: string;\n instanceUrl?: string;\n};\n\nexport type GitLabAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitLabAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code, redirectUri, instanceUrl } = request.body;\n\n if (!code || !redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CODE_MISSING'\n );\n }\n\n try {\n const token = await gitlabService.exchangeCodeForToken(\n code,\n redirectUri,\n instanceUrl\n );\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabListProjectsQuerystring = {\n token?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabListProjectsResult = ResponseData<\n gitlabService.GitLabProject[]\n>;\n\nexport const listProjects = async (\n request: FastifyRequest<{ Querystring: GitLabListProjectsQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, instanceUrl } = request.query;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_TOKEN_MISSING'\n );\n }\n\n const projects = await gitlabService.getUserProjects(\n accessToken,\n instanceUrl\n );\n const responseData = formatResponse<gitlabService.GitLabProject[]>({\n data: projects,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabCheckConfigBody = {\n token?: string;\n projectId: number;\n branch?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[];\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a GitLab repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitLabCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, projectId, branch = 'main', instanceUrl } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n const configPaths = await gitlabService.checkIntlayerConfig(\n accessToken,\n projectId,\n branch,\n instanceUrl\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabGetConfigFileBody = {\n token?: string;\n projectId: number;\n branch?: string;\n path?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitLabGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n projectId,\n branch = 'main',\n path = 'intlayer.config.ts',\n instanceUrl,\n } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await gitlabService.getRepositoryFileContents(\n accessToken,\n projectId,\n path,\n branch,\n instanceUrl\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAgBA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,aAAa,aAAa,UAAU,QAAQ;AAEpD,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,8BACD;AAGH,KAAI;EAMF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SANMA,oBACd,aACA,aACA,MACD,EAEkB,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAcxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,MAAM,aAAa,gBAAgB,QAAQ;AAEnD,KAAI,CAAC,QAAQ,CAAC,YACZ,QAAO,aAAa,2BAClB,OACA,sBACD;AAGH,KAAI;EAMF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,OANI,MAAMC,qBAClB,MACA,aACA,YACD,EAEgB,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAaxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,gBAAgB,QAAQ;CACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAIC,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,uBACD;EAOH,MAAM,eAAe,eAA8C,EACjE,MALe,MAAMC,gBACrB,aACA,YACD,EAGA,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,WAAW,SAAS,QAAQ,gBAAgB,QAAQ;CACnE,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAID,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,UACnB,QAAO,aAAa,2BAClB,OACA,qCACD;EAGH,MAAM,cAAc,MAAME,oBACxB,aACA,WACA,QACA,YACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,WACA,SAAS,QACT,OAAO,sBACP,gBACE,QAAQ;CACZ,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAIF,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,UACnB,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,UAAU,MAAMG,0BACpB,aACA,WACA,MACA,QACA,YACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,+BACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}
1
+ {"version":3,"file":"gitlab.controller.mjs","names":["gitlabService.getAuthorizationUrl","gitlabService.exchangeCodeForToken","gitlabService.getUserProjects","gitlabService.checkIntlayerConfig","gitlabService.getRepositoryFileContents"],"sources":["../../../src/controllers/gitlab.controller.ts"],"sourcesContent":["import * as gitlabService from '@services/gitlab.service';\nimport { getGitLabTokenFromUser } from '@services/gitlab.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type GitLabGetAuthUrlQuerystring = {\n redirectUri: string;\n instanceUrl?: string;\n login?: string;\n};\n\nexport type GitLabGetAuthUrlResult = ResponseData<{\n authUrl: string;\n}>;\n\nexport const getAuthUrl = async (\n request: FastifyRequest<{ Querystring: GitLabGetAuthUrlQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { redirectUri, instanceUrl, login } = request.query;\n\n if (!redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_REDIRECT_URI_MISSING'\n );\n }\n\n try {\n const authUrl = gitlabService.getAuthorizationUrl(\n redirectUri,\n instanceUrl,\n login\n );\n const responseData = formatResponse<{ authUrl: string }>({\n data: { authUrl },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabAuthCallbackBody = {\n code: string;\n redirectUri: string;\n instanceUrl?: string;\n};\n\nexport type GitLabAuthCallbackResult = ResponseData<{\n token: string;\n}>;\n\nexport const authCallback = async (\n request: FastifyRequest<{ Body: GitLabAuthCallbackBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { code, redirectUri, instanceUrl } = request.body;\n\n if (!code || !redirectUri) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CODE_MISSING'\n );\n }\n\n try {\n const token = await gitlabService.exchangeCodeForToken(\n code,\n redirectUri,\n instanceUrl\n );\n const responseData = formatResponse<{ token: string }>({\n data: { token },\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabListProjectsQuerystring = {\n token?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabListProjectsResult = ResponseData<\n gitlabService.GitLabProject[]\n>;\n\nexport const listProjects = async (\n request: FastifyRequest<{ Querystring: GitLabListProjectsQuerystring }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, instanceUrl } = request.query;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_TOKEN_MISSING'\n );\n }\n\n const projects = await gitlabService.getUserProjects(\n accessToken,\n instanceUrl\n );\n const responseData = formatResponse<gitlabService.GitLabProject[]>({\n data: projects,\n });\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabCheckConfigBody = {\n token?: string;\n projectId: number;\n branch?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabCheckConfigResult = ResponseData<{\n hasConfig: boolean;\n configPaths: string[];\n}>;\n\n/**\n * Check if intlayer.config.ts (or candidates) exists in a GitLab repository\n */\nexport const checkConfig = async (\n request: FastifyRequest<{ Body: GitLabCheckConfigBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { token, projectId, branch = 'main', instanceUrl } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CHECK_CONFIG_MISSING_PARAMS'\n );\n }\n\n const configPaths = await gitlabService.checkIntlayerConfig(\n accessToken,\n projectId,\n branch,\n instanceUrl\n );\n\n const responseData = formatResponse<{\n hasConfig: boolean;\n configPaths: string[];\n }>({\n data: {\n hasConfig: configPaths.length > 0,\n configPaths: configPaths,\n },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GitLabGetConfigFileBody = {\n token?: string;\n projectId: number;\n branch?: string;\n path?: string;\n instanceUrl?: string;\n};\n\nexport type GitLabGetConfigFileResult = ResponseData<{\n content: string;\n}>;\n\nexport const getConfigFile = async (\n request: FastifyRequest<{ Body: GitLabGetConfigFileBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n token,\n projectId,\n branch = 'main',\n path = 'intlayer.config.ts',\n instanceUrl,\n } = request.body;\n const userId = request.locals?.user?.id;\n\n try {\n let accessToken: string | undefined = token;\n\n if (!accessToken && userId) {\n accessToken = (await getGitLabTokenFromUser(String(userId))) ?? undefined;\n }\n\n if (!accessToken || !projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_GET_CONFIG_FILE_MISSING_PARAMS'\n );\n }\n\n const content = await gitlabService.getRepositoryFileContents(\n accessToken,\n projectId,\n path,\n branch,\n instanceUrl\n );\n\n if (!content) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'GITLAB_CONFIG_FILE_NOT_FOUND'\n );\n }\n\n const responseData = formatResponse<{ content: string }>({\n data: { content },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":";;;;;AAgBA,MAAa,aAAa,OACxB,SACA,UACkB;CAClB,MAAM,EAAE,aAAa,aAAa,UAAU,QAAQ;AAEpD,KAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,8BACD;AAGH,KAAI;EAMF,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SANMA,oBACd,aACA,aACA,MACD,EAEkB,EAClB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAcxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,MAAM,aAAa,gBAAgB,QAAQ;AAEnD,KAAI,CAAC,QAAQ,CAAC,YACZ,QAAO,aAAa,2BAClB,OACA,sBACD;AAGH,KAAI;EAMF,MAAM,eAAe,eAAkC,EACrD,MAAM,EAAE,OANI,MAAMC,qBAClB,MACA,aACA,YACD,EAEgB,EAChB,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAaxE,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,gBAAgB,QAAQ;CACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,YACH,QAAO,aAAa,2BAClB,OACA,uBACD;EAOH,MAAM,eAAe,eAA8C,EACjE,MALe,MAAMC,gBACrB,aACA,YACD,EAGA,CAAC;AACF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;;;;AAmBxE,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,OAAO,WAAW,SAAS,QAAQ,gBAAgB,QAAQ;CACnE,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,UACnB,QAAO,aAAa,2BAClB,OACA,qCACD;EAGH,MAAM,cAAc,MAAMC,oBACxB,aACA,WACA,QACA,YACD;EAED,MAAM,eAAe,eAGlB,EACD,MAAM;GACJ,WAAW,YAAY,SAAS;GACnB;GACd,EACF,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB;;;AAgBxE,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EACJ,OACA,WACA,SAAS,QACT,OAAO,sBACP,gBACE,QAAQ;CACZ,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,KAAI;EACF,IAAI,cAAkC;AAEtC,MAAI,CAAC,eAAe,OAClB,eAAe,MAAM,uBAAuB,OAAO,OAAO,CAAC,IAAK;AAGlE,MAAI,CAAC,eAAe,CAAC,UACnB,QAAO,aAAa,2BAClB,OACA,wCACD;EAGH,MAAM,UAAU,MAAMC,0BACpB,aACA,WACA,MACA,QACA,YACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,2BAClB,OACA,+BACD;EAGH,MAAM,eAAe,eAAoC,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AAEF,SAAO,MAAM,KAAK,aAAa;UACxB,OAAO;AACd,SAAO,aAAa,uBAAuB,OAAO,MAAkB"}