@intlayer/backend 8.10.0-canary.0 → 8.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10934 -8925
- package/dist/esm/controllers/ai.controller.mjs +2 -2
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/organization.controller.mjs +16 -4
- package/dist/esm/controllers/organization.controller.mjs.map +1 -1
- package/dist/esm/controllers/project.controller.mjs +10 -3
- package/dist/esm/controllers/project.controller.mjs.map +1 -1
- package/dist/esm/schemas/user.schema.mjs +8 -0
- package/dist/esm/schemas/user.schema.mjs.map +1 -1
- package/dist/esm/services/audit/recursiveAudit.service.mjs +1 -1
- package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
- package/dist/esm/services/cliSessionToken.service.mjs +2 -2
- package/dist/esm/services/cliSessionToken.service.mjs.map +1 -1
- package/dist/esm/services/dictionary.service.mjs +1 -1
- package/dist/esm/services/dictionary.service.mjs.map +1 -1
- package/dist/esm/services/oAuth2.service.mjs +3 -3
- package/dist/esm/services/oAuth2.service.mjs.map +1 -1
- package/dist/esm/services/projectAccessKey.service.mjs +2 -2
- package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
- package/dist/esm/services/showcase/showcaseProject.service.mjs +1 -1
- package/dist/esm/services/showcase/showcaseProject.service.mjs.map +1 -1
- package/dist/esm/services/user.service.mjs +1 -1
- package/dist/esm/services/user.service.mjs.map +1 -1
- package/dist/esm/types/user.types.mjs.map +1 -1
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10934 -8925
- package/dist/esm/utils/auth/getAuth.mjs +34 -8
- package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
- package/dist/types/controllers/organization.controller.d.ts.map +1 -1
- package/dist/types/controllers/project.controller.d.ts.map +1 -1
- package/dist/types/schemas/dictionary.schema.d.ts +8 -8
- package/dist/types/schemas/discussion.schema.d.ts +3 -3
- package/dist/types/schemas/organization.schema.d.ts +7 -7
- package/dist/types/schemas/plans.schema.d.ts +8 -8
- package/dist/types/schemas/project.schema.d.ts +7 -7
- package/dist/types/schemas/showcaseProject.schema.d.ts +17 -17
- package/dist/types/schemas/tag.schema.d.ts +8 -8
- package/dist/types/schemas/user.schema.d.ts +30 -8
- package/dist/types/services/showcase/showcaseProject.service.d.ts.map +1 -1
- package/dist/types/types/user.types.d.ts +2 -0
- package/dist/types/types/user.types.d.ts.map +1 -1
- package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
- package/dist/types/utils/errors/ErrorHandler.d.ts +8 -4
- package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +4 -4
- package/package.json +14 -14
|
@@ -267,7 +267,7 @@ const askDocQuestion = async (request, reply) => {
|
|
|
267
267
|
if (user?.id) updatePayload.userId = user.id;
|
|
268
268
|
if (project?.id) updatePayload.projectId = project.id;
|
|
269
269
|
if (organization?.id) updatePayload.organizationId = organization.id;
|
|
270
|
-
await DiscussionModel.findOneAndUpdate({ discussionId }, { $set: updatePayload }, {
|
|
270
|
+
await DiscussionModel.findOneAndUpdate({ discussionId: String(discussionId) }, { $set: updatePayload }, {
|
|
271
271
|
upsert: true,
|
|
272
272
|
returnDocument: "after"
|
|
273
273
|
});
|
|
@@ -363,7 +363,7 @@ const chat = async (request, reply) => {
|
|
|
363
363
|
if (user?.id) updatePayload.userId = user.id;
|
|
364
364
|
if (project?.id) updatePayload.projectId = project.id;
|
|
365
365
|
if (organization?.id) updatePayload.organizationId = organization.id;
|
|
366
|
-
await DiscussionModel.findOneAndUpdate({ discussionId }, { $set: updatePayload }, {
|
|
366
|
+
await DiscussionModel.findOneAndUpdate({ discussionId: String(discussionId) }, { $set: updatePayload }, {
|
|
367
367
|
upsert: true,
|
|
368
368
|
returnDocument: "after"
|
|
369
369
|
});
|
|
@@ -1 +1 @@
|
|
|
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","chatUtil.chat","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 { Locale } from '@intlayer/types/allLocales';\nimport type { KeyPath } from '@intlayer/types/keyPath';\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 chatUtil from '@utils/AI/chat';\nimport { createSessionTools } from '@utils/AI/chat/sessionTools';\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\nexport type {\n AIConfig,\n AIOptions,\n AIProvider,\n ChatCompletionRequestMessage,\n} from '@intlayer/ai';\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.session || {};\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.session || {};\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.session || {};\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.session || {};\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.session || {};\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.session || {};\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.session || {};\n\n // Hijack response\n reply.hijack();\n\n // Copy all Fastify-managed headers (including CORS) to the raw response\n // immediately after hijacking, before any early returns.\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 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 console.error(error);\n\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\n reply.raw.end();\n return;\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 reversedMessages = [...messages].reverse();\n const lastUserMessageContent = reversedMessages.find(\n (message) => message.role === 'user'\n )?.content;\n const lastUserMessageNbWords =\n typeof lastUserMessageContent === 'string'\n ? lastUserMessageContent.split(' ').length\n : 0;\n\n if (lastUserMessageNbWords >= 2 || messages.length >= 2) {\n const updatePayload: any = {\n discussionId,\n type: 'doc',\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp ?? new Date(),\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n };\n\n if (user?.id) updatePayload.userId = user.id;\n if (project?.id) updatePayload.projectId = project.id;\n if (organization?.id) updatePayload.organizationId = organization.id;\n\n await DiscussionModel.findOneAndUpdate(\n { discussionId },\n { $set: updatePayload },\n { upsert: true, returnDocument: 'after' }\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) {\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 ChatBody = {\n messages: ChatCompletionRequestMessage[];\n discussionId: string;\n};\nexport type ChatResult = ResponseData<chatUtil.ChatResultData>;\n\nexport const chat = async (\n request: FastifyRequest<{ Body: ChatBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { messages = [], discussionId } = request.body;\n const { user, project, organization, roles } = request.session || {};\n\n reply.hijack();\n\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 const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n try {\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: {},\n projectOptions: projectAIOptions,\n accessType: ['registered_user'],\n },\n !!user\n );\n } catch (error) {\n console.error(error);\n\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 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 const sessionTools = createSessionTools({\n projectId: project?.id ? String(project.id) : undefined,\n organizationId: organization?.id ? String(organization.id) : undefined,\n userId: user?.id ? String(user.id) : undefined,\n roles: roles || [],\n session: request.session,\n onAction: (action) => {\n if (!reply.raw.writableEnded) {\n reply.raw.write(`data: ${JSON.stringify({ action })}\\n\\n`);\n }\n },\n });\n\n const fullResponse = await chatUtil.chat(messages, aiConfig, {\n tools: sessionTools,\n onMessage: (chunk) => {\n if (!reply.raw.writableEnded) {\n reply.raw.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n }\n },\n });\n\n const reversedMessages = [...messages].reverse();\n const lastUserMessageContent = reversedMessages.find(\n (message) => message.role === 'user'\n )?.content;\n const lastUserMessageNbWords =\n typeof lastUserMessageContent === 'string'\n ? lastUserMessageContent.split(' ').length\n : 0;\n\n if (lastUserMessageNbWords >= 2 || messages.length >= 2) {\n const updatePayload: any = {\n discussionId,\n type: 'dashboard',\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp ?? new Date(),\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n timestamp: new Date(),\n },\n ],\n };\n\n if (user?.id) updatePayload.userId = user.id;\n if (project?.id) updatePayload.projectId = project.id;\n if (organization?.id) updatePayload.organizationId = organization.id;\n\n await DiscussionModel.findOneAndUpdate(\n { discussionId },\n { $set: updatePayload },\n { upsert: true, returnDocument: 'after' }\n );\n }\n\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) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const errorStack = err instanceof Error ? err.stack : undefined;\n\n logger.error('AI Chat Stream Error:', {\n message: errorMessage,\n stack: errorStack,\n });\n\n const isAuthError =\n errorMessage.includes('401') ||\n errorMessage.includes('Incorrect API key');\n\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 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.session || {};\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.session || {};\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":";;;;;;;;;;;;;;;;;;;;AA0DA,MAAa,cAAc,OACzB,SACA,UACkB;CAClB,MAAM,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CACjD,MAAM,EAAE,MAAM,YAAY,QAAQ,WAAW,CAAC;CAE9C,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,YACf;GACE,aAAa;GACb,gBAAgB;GAChB,gBAAgBA;GAChB,YAAY,CAAC,mBAAmB,QAAQ;EAC1C,GACA,CAAC,CAAC,IACJ;CACF,SAAS,QAAQ;EACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAC1E;CAEA,IAAI;EACF,MAAM,gBAAgB,MAAMC,cAA4B;GACtD,GAAG;GACH;EACF,CAAC;EAED,IAAI,CAAC,eACH,OAAO,aAAa,2BAA2B,OAAO,cAAc;EAGtE,MAAM,eAAe,eAAsD,EACzE,MAAM,cACR,CAAC;EAED,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;AAYA,MAAa,gBAAgB,OAC3B,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,SAAS,QAAQ,WAAW,CAAC;CAC9C,MAAM,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAEjD,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,YACf;GACE,aAAa;GACb,gBAAgB;GAChB,gBAAgBC;GAChB,YAAY,CAAC,mBAAmB,QAAQ;EAC1C,GACA,CAAC,CAAC,IACJ;CACF,SAAS,QAAQ;EACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAC1E;CAEA,IAAI;EACF,IAAI,OAAc,CAAC;EAEnB,IAAI,SAAS,kBAAkB,UAC7B,OAAO,MAAM,cAAc,UAAU,QAAQ,cAAc;EAG7D,MAAM,gBAAgB,MAAMC,gBAAqC;GAC/D,GAAG;GACH;GACA,oBAAoB,WAAW;GAC/B;EACF,CAAC;EAED,IAAI,CAAC,eACH,OAAO,aAAa,2BAA2B,OAAO,cAAc;EAGtE,MAAM,eAAe,eAEnB,EACA,MAAM,cACR,CAAC;EAED,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;;;;AAgBA,MAAa,0BAA0B,OACrC,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,SAAS,QAAQ,WAAW,CAAC;CAC9C,MAAM,EAAE,aAAa,UAAU,WAAW,SAAS,eAAe,aAChE,QAAQ;CAEV,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,YACf;GACE,aAAa;GACb,gBAAgB;GAChB,gBAAgBC;GAChB,YAAY,CAAC,mBAAmB,QAAQ;EAC1C,GACA,CAAC,CAAC,IACJ;CACF,SAAS,QAAQ;EACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAC1E;CAEA,IAAI;EACF,IAAI,OAAc,CAAC;EAEnB,IAAI,SAAS,gBACX,OAAO,MAAM,cAAc,YAAY,CAAC,GAAG,QAAQ,cAAc;EAGnE,MAAM,gBAAgB,MAAMC,gBAA4C;GACtE;GACA;GACA;GACA,oBAAoB,WAAW;GAC/B;GACA;GACA;EACF,CAAC;EAED,IAAI,CAAC,eACH,OAAO,aAAa,2BAA2B,OAAO,cAAc;EAGtE,MAAM,eACJ,eAAgE,EAC9D,MAAM,cACR,CAAC;EAEH,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;;;;AAgBA,MAAa,+BAA+B,OAC1C,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,SAAS,QAAQ,WAAW,CAAC;CAC9C,MAAM,EAAE,aAAa,WAAW,SAAS,UAAU,YAAY,QAAQ;CAEvE,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,YACf;GACE,aAAa;GACb,gBAAgB;GAChB,gBAAgBC;GAChB,YAAY,CAAC,mBAAmB,QAAQ;EAC1C,GACA,CAAC,CAAC,IACJ;CACF,SAAS,QAAQ;EACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAC1E;CAEA,IAAI;EACF,IAAI,OAAc,CAAC;EAEnB,IAAI,SAAS,gBACX,OAAO,MAAM,cAAc,YAAY,CAAC,GAAG,QAAQ,cAAc;EAGnE,MAAM,gBACJ,MAAMC,qBAAsD;GAC1D;GACA;GACA,oBAAoB,WAAW;GAC/B;GACA;GACA;EACF,CAAC;EAEH,IAAI,CAAC,eACH,OAAO,aAAa,2BAA2B,OAAO,cAAc;EAGtE,MAAM,eACJ,eACE,EACE,MAAM,cACR,CACF;EAEF,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;;;;AAaA,MAAa,kCAAkC,OAC7C,SACA,UACkB;CAClB,MAAM,EAAE,cAAc,SAAS,QAAQ,WAAW,CAAC;CACnD,MAAM,EAAE,aAAa,cAAc,QAAQ;CAE3C,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,YACf;GACE,aAAa;GACb,gBAAgBC;GAChB,YAAY,CAAC,mBAAmB,QAAQ;EAC1C,GACA,CAAC,CAAC,IACJ;CACF,SAAS,QAAQ;EACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAC1E;CAEA,IAAI;EACF,MAAM,OAAc,MAAMC,SACxB,EACE,gBAAgB,cAAc,GAChC,GACA,GACA,GACF;EAEA,MAAM,gBACJ,MAAMC,0BAA4D;GAChE;GACA;GACA,oBAAoB,WAAW;GAC/B;EACF,CAAC;EAEH,IAAI,CAAC,eACH,OAAO,aAAa,2BAA2B,OAAO,cAAc;EAGtE,MAAM,eACJ,eAAwE,EACtE,MAAM,cACR,CAAC;EAEH,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;;;;AAWA,MAAa,WAAW,OACtB,SACA,UACkB;CAClB,MAAM,EAAE,SAAS,SAAS,QAAQ,WAAW,CAAC;CAC9C,MAAM,EAAE,WAAW,QAAQ,QAAQ;CAEnC,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,YACf;GACE,aAAa;GACb,gBAAgB;GAChB,gBAAgBC;GAChB,YAAY,CAAC,mBAAmB,QAAQ;EAC1C,GACA,CAAC,CAAC,IACJ;CACF,SAAS,QAAQ;EACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAC1E;CAEA,IAAI;EACF,IAAI,eAA6B,CAAC;EAClC,IAAI,SAAS,gBACX,eAAe,MAAM,sBAAsB,CAAC,IAAI,GAAG,GAAG,QAAQ,EAAE;EAGlE,MAAM,gBAAgB,MAAMC,WAAsB;GAChD;GACA;GACA;GACA,oBAAoB,WAAW;EACjC,CAAC;EAED,IAAI,CAAC,eACH,OAAO,aAAa,2BAA2B,OAAO,cAAc;EAGtE,MAAM,eAAe,eAAqD,EACxE,MAAM,cACR,CAAC;EAED,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;AASA,MAAa,iBAAiB,OAC5B,SACA,UACkB;CAClB,MAAM,EAAE,WAAW,CAAC,GAAG,iBAAiB,QAAQ;CAChD,MAAM,EAAE,MAAM,SAAS,iBAAiB,QAAQ,WAAW,CAAC;CAG5D,MAAM,OAAO;CAIb,MAAM,UAAU,MAAM,WAAW;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,IAAI,UAAU,QACZ,MAAM,IAAI,UAAU,KAAK,KAAK;CAIlC,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;CAGJ,IAAI;EAEF,IAAI;GACF,WAAW,MAAM,YACf;IACE,aAAa,CAAC;IACd,gBAAgB;IAChB,YAAY,CAAC,QAAQ;GACvB,GACA,CAAC,CAAC,IACJ;EACF,SAAS,OAAO;GACd,QAAQ,MAAM,KAAK;GAQnB,MAAM,IAAI,MACR,uBAAuB,KAAK,UAAU;IALtC,MAAM;IACN,OAAO;IACP,SAAS;GAGwC,CAAC,EAAE,KACtD;GAEA,MAAM,IAAI,IAAI;GACd;EACF;EAGA,MAAM,IAAI,UAAU,gBAAgB,kCAAkC;EACtE,MAAM,IAAI,UAAU,iBAAiB,wBAAwB;EAC7D,MAAM,IAAI,UAAU,cAAc,YAAY;EAC9C,MAAM,IAAI,UAAU,qBAAqB,IAAI;EAE7C,IAAI,MAAM,IAAI,cACZ,MAAM,IAAI,aAAa;EAGzB,MAAM,IAAI,MAAM,iBAAiB;EAIjC,MAAM,eAAe,MAAMC,iBACzB,UACA,UACA,EACE,YAAY,UAAU;GACpB,IAAI,CAAC,MAAM,IAAI,eACb,MAAM,IAAI,MAAM,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,EAAE,KAAK;EAE5D,EACF,CACF;EAIA,MAAM,yBADmB,CAAC,GAAG,QAAQ,EAAE,QACO,EAAE,MAC7C,YAAY,QAAQ,SAAS,MAChC,GAAG;EAMH,KAJE,OAAO,2BAA2B,WAC9B,uBAAuB,MAAM,GAAG,EAAE,SAClC,MAEwB,KAAK,SAAS,UAAU,GAAG;GACvD,MAAM,gBAAqB;IACzB;IACA,MAAM;IACN,UAAU,CACR,GAAG,SAAS,KAAK,SAAS;KACxB,MAAM,IAAI;KACV,SAAS,IAAI;KACb,WAAW,IAAI,6BAAa,IAAI,KAAK;IACvC,EAAE,GACF;KACE,MAAM;KACN,SAAS,aAAa;KACtB,cAAc,aAAa;KAC3B,2BAAW,IAAI,KAAK;IACtB,CACF;GACF;GAEA,IAAI,MAAM,IAAI,cAAc,SAAS,KAAK;GAC1C,IAAI,SAAS,IAAI,cAAc,YAAY,QAAQ;GACnD,IAAI,cAAc,IAAI,cAAc,iBAAiB,aAAa;GAElE,MAAM,gBAAgB,iBACpB,EAAE,aAAa,GACf,EAAE,MAAM,cAAc,GACtB;IAAE,QAAQ;IAAM,gBAAgB;GAAQ,CAC1C;EACF;EAGA,IAAI,CAAC,MAAM,IAAI,eAAe;GAC5B,MAAM,IAAI,MACR,SAAS,KAAK,UAAU;IAAE,MAAM;IAAM,UAAU;GAAa,CAAC,EAAE,KAClE;GACA,MAAM,IAAI,IAAI;EAChB;CACF,SAAS,KAAK;EAIZ,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EACpE,MAAM,aAAa,eAAe,QAAQ,IAAI,QAAQ;EAGtD,OAAO,MAAM,2BAA2B;GACtC,SAAS;GACT,OAAO;EACT,CAAC;EAGD,MAAM,cACJ,aAAa,SAAS,KAAK,KAC3B,aAAa,SAAS,mBAAmB;EAG3C,MAAM,eAAe;GACnB,MAAM,cAAc,kBAAkB;GACtC,OAAO,cAAc,2BAA2B;GAChD,SAAS;EACX;EAGA,IAAI,CAAC,MAAM,IAAI,eAAe;GAC5B,MAAM,IAAI,MACR,uBAAuB,KAAK,UAAU,YAAY,EAAE,KACtD;GACA,MAAM,IAAI,IAAI;EAChB;CACF;AACF;AAQA,MAAa,OAAO,OAClB,SACA,UACkB;CAClB,MAAM,EAAE,WAAW,CAAC,GAAG,iBAAiB,QAAQ;CAChD,MAAM,EAAE,MAAM,SAAS,cAAc,UAAU,QAAQ,WAAW,CAAC;CAEnE,MAAM,OAAO;CAEb,MAAM,UAAU,MAAM,WAAW;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,IAAI,UAAU,QACZ,MAAM,IAAI,UAAU,KAAK,KAAK;CAIlC,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;EACF,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,YACf;IACE,aAAa,CAAC;IACd,gBAAgB;IAChB,YAAY,CAAC,iBAAiB;GAChC,GACA,CAAC,CAAC,IACJ;EACF,SAAS,OAAO;GACd,QAAQ,MAAM,KAAK;GAOnB,MAAM,IAAI,MACR,uBAAuB,KAAK,UAAU;IALtC,MAAM;IACN,OAAO;IACP,SAAS;GAGwC,CAAC,EAAE,KACtD;GACA,MAAM,IAAI,IAAI;GACd;EACF;EAEA,MAAM,IAAI,UAAU,gBAAgB,kCAAkC;EACtE,MAAM,IAAI,UAAU,iBAAiB,wBAAwB;EAC7D,MAAM,IAAI,UAAU,cAAc,YAAY;EAC9C,MAAM,IAAI,UAAU,qBAAqB,IAAI;EAE7C,IAAI,MAAM,IAAI,cACZ,MAAM,IAAI,aAAa;EAGzB,MAAM,IAAI,MAAM,iBAAiB;EAEjC,MAAM,eAAe,mBAAmB;GACtC,WAAW,SAAS,KAAK,OAAO,QAAQ,EAAE,IAAI;GAC9C,gBAAgB,cAAc,KAAK,OAAO,aAAa,EAAE,IAAI;GAC7D,QAAQ,MAAM,KAAK,OAAO,KAAK,EAAE,IAAI;GACrC,OAAO,SAAS,CAAC;GACjB,SAAS,QAAQ;GACjB,WAAW,WAAW;IACpB,IAAI,CAAC,MAAM,IAAI,eACb,MAAM,IAAI,MAAM,SAAS,KAAK,UAAU,EAAE,OAAO,CAAC,EAAE,KAAK;GAE7D;EACF,CAAC;EAED,MAAM,eAAe,MAAMC,OAAc,UAAU,UAAU;GAC3D,OAAO;GACP,YAAY,UAAU;IACpB,IAAI,CAAC,MAAM,IAAI,eACb,MAAM,IAAI,MAAM,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,EAAE,KAAK;GAE5D;EACF,CAAC;EAGD,MAAM,yBADmB,CAAC,GAAG,QAAQ,EAAE,QACO,EAAE,MAC7C,YAAY,QAAQ,SAAS,MAChC,GAAG;EAMH,KAJE,OAAO,2BAA2B,WAC9B,uBAAuB,MAAM,GAAG,EAAE,SAClC,MAEwB,KAAK,SAAS,UAAU,GAAG;GACvD,MAAM,gBAAqB;IACzB;IACA,MAAM;IACN,UAAU,CACR,GAAG,SAAS,KAAK,SAAS;KACxB,MAAM,IAAI;KACV,SAAS,IAAI;KACb,WAAW,IAAI,6BAAa,IAAI,KAAK;IACvC,EAAE,GACF;KACE,MAAM;KACN,SAAS,aAAa;KACtB,2BAAW,IAAI,KAAK;IACtB,CACF;GACF;GAEA,IAAI,MAAM,IAAI,cAAc,SAAS,KAAK;GAC1C,IAAI,SAAS,IAAI,cAAc,YAAY,QAAQ;GACnD,IAAI,cAAc,IAAI,cAAc,iBAAiB,aAAa;GAElE,MAAM,gBAAgB,iBACpB,EAAE,aAAa,GACf,EAAE,MAAM,cAAc,GACtB;IAAE,QAAQ;IAAM,gBAAgB;GAAQ,CAC1C;EACF;EAEA,IAAI,CAAC,MAAM,IAAI,eAAe;GAC5B,MAAM,IAAI,MACR,SAAS,KAAK,UAAU;IAAE,MAAM;IAAM,UAAU;GAAa,CAAC,EAAE,KAClE;GACA,MAAM,IAAI,IAAI;EAChB;CACF,SAAS,KAAK;EACZ,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EACpE,MAAM,aAAa,eAAe,QAAQ,IAAI,QAAQ;EAEtD,OAAO,MAAM,yBAAyB;GACpC,SAAS;GACT,OAAO;EACT,CAAC;EAED,MAAM,cACJ,aAAa,SAAS,KAAK,KAC3B,aAAa,SAAS,mBAAmB;EAE3C,MAAM,eAAe;GACnB,MAAM,cAAc,kBAAkB;GACtC,OAAO,cAAc,2BAA2B;GAChD,SAAS;EACX;EAEA,IAAI,CAAC,MAAM,IAAI,eAAe;GAC5B,MAAM,IAAI,MACR,uBAAuB,KAAK,UAAU,YAAY,EAAE,KACtD;GACA,MAAM,IAAI,IAAI;EAChB;CACF;AACF;AAcA,MAAa,eAAe,OAC1B,SACA,UACkB;CAClB,MAAM,EAAE,MAAM,YAAY,QAAQ,WAAW,CAAC;CAE9C,MAAM,mBAAmB,SAAS,eAAe,KAC5C,QAAQ,cAAc,KACvB;CAEJ,IAAI;EACF,MAAM,EAAE,MAAM,WAAW,eAAe,aAAa,iBACnD,QAAQ;EAEV,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,YACf;IACE,aAAa;IACb,gBAAgB;IAChB,gBAAgBC;IAChB,YAAY,CAAC,QAAQ;GACvB,GACA,CAAC,CAAC,IACJ;EACF,SAAS,QAAQ;GACf,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;EAC1E;EAcA,MAAM,eACJ,eAA4D,EAC1D,MAdc,MAAMC,eAA8B;GACpD;GACA;GACA,oBAAoB,WAAW;GAC/B;GACA;GACA;EACF,CAAC,KAAM;GACL,gBAAgB;GAChB,WAAW;EACb,EAKE,CAAC;EAEH,OAAO,MAAM,KAAK,YAAY;CAChC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF;;;;;AAgBA,MAAa,iBAAiB,OAC5B,SACA,UACkB;CAClB,MAAM,EAAE,MAAM,UAAU,QAAQ,WAAW,CAAC;CAC5C,MAAM,EAAE,SAAS,aAAa,UAAU,MAAM,MAAM,qBAClD,kCAAkC,OAAO;CAK3C,MAAM,kBAJwB,QAAQ,OAAe,oBAIJ;CAEjD,IAAI,CAAC,MACH,OAAO,aAAa,2BAA2B,OAAO,kBAAkB;CAG1E,IAAI;EACF,MAAM,aAAa,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE;EACxD,MAAM,cAAc,MAAM,gBAAgB,KAAK,SAAS,UAAU,EAC/D,KAAK,WAAW,EAChB,KAAK,IAAI,EACT,MAAM,QAAQ,EACd,KAAK;EAGR,MAAM,uBAA+C,CAAC;EACtD,IAAI,CAAC,mBAAmB,YAAY,SAAS,GAAG;GAC9C,MAAM,MAAM,YAAY,KAAK,MAAW,EAAE,GAAG;GAC7C,MAAM,SAAS,MAAM,gBAAgB,UAAU,CAC7C,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,GAChC,EACE,UAAU,EACR,kBAAkB,EAAE,OAAO,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,EAC5D,EACF,CACF,CAAC;GACD,KAAK,MAAM,KAAK,QACd,qBAAqB,OAAO,EAAE,GAAG,KAAK,EAAE,oBAAoB;EAEhE;EAGA,MAAM,iBAAiB,YAAY,OAChC,MAAM,OAAO,EAAE,MAAM,MAAM,OAAO,KAAK,EAAE,CAC5C;EAGA,IAAI,EAFc,OAAO,SAAS,OAAO,KAAK,iBAG5C,OAAO,aAAa,2BAClB,OACA,mBACF;EAGF,MAAM,aAAa,MAAM,gBAAgB,eAAe,OAAO;EAE/D,MAAM,eAAe,wBAAwB;GAC3C,MAAM,YAAY,KAAK,OAAY;IACjC,GAAG;IACH,IAAI,OAAO,EAAE,OAAO,EAAE,EAAE;IACxB,kBAAkB,kBACd,MAAM,QAAQ,EAAE,QAAQ,IACtB,EAAE,SAAS,SACX,IACD,qBAAqB,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM;GACtD,EAAE;GACF;GACA;GACA,YAAY,iBAAiB,UAAU;GACvC;EACF,CAAC;EAED,OAAO,MAAM,KAAK,YAAmB;CACvC,SAAS,OAAO;EACd,OAAO,aAAa,uBAAuB,OAAO,KAAiB;CACrE;AACF"}
|
|
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","chatUtil.chat","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 { Locale } from '@intlayer/types/allLocales';\nimport type { KeyPath } from '@intlayer/types/keyPath';\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 chatUtil from '@utils/AI/chat';\nimport { createSessionTools } from '@utils/AI/chat/sessionTools';\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\nexport type {\n AIConfig,\n AIOptions,\n AIProvider,\n ChatCompletionRequestMessage,\n} from '@intlayer/ai';\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.session || {};\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.session || {};\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.session || {};\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.session || {};\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.session || {};\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.session || {};\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.session || {};\n\n // Hijack response\n reply.hijack();\n\n // Copy all Fastify-managed headers (including CORS) to the raw response\n // immediately after hijacking, before any early returns.\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 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 console.error(error);\n\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\n reply.raw.end();\n return;\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 reversedMessages = [...messages].reverse();\n const lastUserMessageContent = reversedMessages.find(\n (message) => message.role === 'user'\n )?.content;\n const lastUserMessageNbWords =\n typeof lastUserMessageContent === 'string'\n ? lastUserMessageContent.split(' ').length\n : 0;\n\n if (lastUserMessageNbWords >= 2 || messages.length >= 2) {\n const updatePayload: any = {\n discussionId,\n type: 'doc',\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp ?? new Date(),\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n };\n\n if (user?.id) updatePayload.userId = user.id;\n if (project?.id) updatePayload.projectId = project.id;\n if (organization?.id) updatePayload.organizationId = organization.id;\n\n await DiscussionModel.findOneAndUpdate(\n { discussionId: String(discussionId) },\n { $set: updatePayload },\n { upsert: true, returnDocument: 'after' }\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) {\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 ChatBody = {\n messages: ChatCompletionRequestMessage[];\n discussionId: string;\n};\nexport type ChatResult = ResponseData<chatUtil.ChatResultData>;\n\nexport const chat = async (\n request: FastifyRequest<{ Body: ChatBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { messages = [], discussionId } = request.body;\n const { user, project, organization, roles } = request.session || {};\n\n reply.hijack();\n\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 const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n try {\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: {},\n projectOptions: projectAIOptions,\n accessType: ['registered_user'],\n },\n !!user\n );\n } catch (error) {\n console.error(error);\n\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 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 const sessionTools = createSessionTools({\n projectId: project?.id ? String(project.id) : undefined,\n organizationId: organization?.id ? String(organization.id) : undefined,\n userId: user?.id ? String(user.id) : undefined,\n roles: roles || [],\n session: request.session,\n onAction: (action) => {\n if (!reply.raw.writableEnded) {\n reply.raw.write(`data: ${JSON.stringify({ action })}\\n\\n`);\n }\n },\n });\n\n const fullResponse = await chatUtil.chat(messages, aiConfig, {\n tools: sessionTools,\n onMessage: (chunk) => {\n if (!reply.raw.writableEnded) {\n reply.raw.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n }\n },\n });\n\n const reversedMessages = [...messages].reverse();\n const lastUserMessageContent = reversedMessages.find(\n (message) => message.role === 'user'\n )?.content;\n const lastUserMessageNbWords =\n typeof lastUserMessageContent === 'string'\n ? lastUserMessageContent.split(' ').length\n : 0;\n\n if (lastUserMessageNbWords >= 2 || messages.length >= 2) {\n const updatePayload: any = {\n discussionId,\n type: 'dashboard',\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp ?? new Date(),\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n timestamp: new Date(),\n },\n ],\n };\n\n if (user?.id) updatePayload.userId = user.id;\n if (project?.id) updatePayload.projectId = project.id;\n if (organization?.id) updatePayload.organizationId = organization.id;\n\n await DiscussionModel.findOneAndUpdate(\n { discussionId: String(discussionId) },\n { $set: updatePayload },\n { upsert: true, returnDocument: 'after' }\n );\n }\n\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) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const errorStack = err instanceof Error ? err.stack : undefined;\n\n logger.error('AI Chat Stream Error:', {\n message: errorMessage,\n stack: errorStack,\n });\n\n const isAuthError =\n errorMessage.includes('401') ||\n errorMessage.includes('Incorrect API key');\n\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 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.session || {};\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.session || {};\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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"}
|
|
@@ -3,7 +3,7 @@ import { formatPaginatedResponse, formatResponse } from "../utils/responseData.m
|
|
|
3
3
|
import { ErrorHandler } from "../utils/errors/ErrorHandler.mjs";
|
|
4
4
|
import { countOrganizations, createOrganization, deleteOrganizationById, findOrganizations, getOrganizationById, updateOrganizationById } from "../services/organization.service.mjs";
|
|
5
5
|
import { findProjects } from "../services/project.service.mjs";
|
|
6
|
-
import { createUser, getUserByEmail, getUsersByIds } from "../services/user.service.mjs";
|
|
6
|
+
import { createUser, getUserByEmail, getUsersByIds, updateUserById } from "../services/user.service.mjs";
|
|
7
7
|
import { hasPermission } from "../utils/permissions.mjs";
|
|
8
8
|
import { SessionModel } from "../models/session.model.mjs";
|
|
9
9
|
import { sendEmail } from "../services/email.service.mjs";
|
|
@@ -410,7 +410,7 @@ const updateOrganizationMembersById = async (request, reply) => {
|
|
|
410
410
|
*/
|
|
411
411
|
const deleteOrganization = async (_request, reply) => {
|
|
412
412
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
413
|
-
const { organization, session, roles } = _request.session || {};
|
|
413
|
+
const { organization, session, roles, user } = _request.session || {};
|
|
414
414
|
if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_DEFINED");
|
|
415
415
|
if ((await findProjects({ organizationId: organization.id })).length > 0) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECTS_EXIST", { organizationId: organization.id });
|
|
416
416
|
if (!hasPermission(roles || [], "organization:admin")({
|
|
@@ -426,6 +426,10 @@ const deleteOrganization = async (_request, reply) => {
|
|
|
426
426
|
activeOrganizationId: null,
|
|
427
427
|
activeProjectId: null
|
|
428
428
|
} });
|
|
429
|
+
if (user) await updateUserById(user.id, {
|
|
430
|
+
lastActiveOrganizationId: null,
|
|
431
|
+
lastActiveProjectId: null
|
|
432
|
+
});
|
|
429
433
|
logger.info(`Organization deleted: ${String(deletedOrganization.id)}`);
|
|
430
434
|
const responseData = formatResponse({
|
|
431
435
|
message: t({
|
|
@@ -521,7 +525,7 @@ const deleteOrganizationByIdAdmin = async (request, reply) => {
|
|
|
521
525
|
*/
|
|
522
526
|
const selectOrganization = async (request, reply) => {
|
|
523
527
|
const { organizationId } = request.params;
|
|
524
|
-
const { session, roles } = request.session || {};
|
|
528
|
+
const { session, roles, user } = request.session || {};
|
|
525
529
|
if (!organizationId) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_ID_NOT_FOUND");
|
|
526
530
|
if (typeof session === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "SESSION_NOT_DEFINED");
|
|
527
531
|
try {
|
|
@@ -534,6 +538,10 @@ const selectOrganization = async (request, reply) => {
|
|
|
534
538
|
activeOrganizationId: String(organization.id),
|
|
535
539
|
activeProjectId: null
|
|
536
540
|
} });
|
|
541
|
+
if (user) await updateUserById(user.id, {
|
|
542
|
+
lastActiveOrganizationId: String(organization.id),
|
|
543
|
+
lastActiveProjectId: null
|
|
544
|
+
});
|
|
537
545
|
const responseData = formatResponse({
|
|
538
546
|
message: t({
|
|
539
547
|
en: "Organization retrieved successfully",
|
|
@@ -586,13 +594,17 @@ const selectOrganization = async (request, reply) => {
|
|
|
586
594
|
* Unselect an organization.
|
|
587
595
|
*/
|
|
588
596
|
const unselectOrganization = async (_request, reply) => {
|
|
589
|
-
const { session } = _request.session || {};
|
|
597
|
+
const { session, user } = _request.session || {};
|
|
590
598
|
try {
|
|
591
599
|
if (typeof session === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "SESSION_NOT_DEFINED");
|
|
592
600
|
await SessionModel.updateOne({ _id: session.id }, { $set: {
|
|
593
601
|
activeOrganizationId: null,
|
|
594
602
|
activeProjectId: null
|
|
595
603
|
} });
|
|
604
|
+
if (user) await updateUserById(user.id, {
|
|
605
|
+
lastActiveOrganizationId: null,
|
|
606
|
+
lastActiveProjectId: null
|
|
607
|
+
});
|
|
596
608
|
const responseData = formatResponse({
|
|
597
609
|
message: t({
|
|
598
610
|
en: "Organization unselected successfully",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organization.controller.mjs","names":["organizationService.findOrganizations","organizationService.countOrganizations","organizationService.getOrganizationById","organizationService.createOrganization","organizationService.updateOrganizationById","userService.getUserByEmail","userService.createUser","userService.getUsersByIds","projectService.findProjects","organizationService.deleteOrganizationById"],"sources":["../../../src/controllers/organization.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { SessionModel } from '@models/session.model';\nimport { sendEmail } from '@services/email.service';\nimport * as organizationService from '@services/organization.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getOrganizationFiltersAndPagination,\n type OrganizationFiltersParams,\n} from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n mapOrganizationsToAPI,\n mapOrganizationToAPI,\n} from '@utils/mapper/organization';\nimport { hasPermission } from '@utils/permissions';\nimport { getPlanDetails } from '@utils/plan';\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 { Types } from 'mongoose';\nimport Stripe from 'stripe';\nimport type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n} from '@/types/organization.types';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type GetOrganizationsParams =\n FiltersAndPagination<OrganizationFiltersParams>;\nexport type GetOrganizationsResult = PaginatedResponse<OrganizationAPI>;\n\n/**\n * Retrieves a list of organizations based on filters and pagination.\n */\nexport const getOrganizations = async (\n request: FastifyRequest<{ Querystring: GetOrganizationsParams }>,\n reply: FastifyReply\n) => {\n const { user, roles } = request.session || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getOrganizationFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const organizations = await organizationService.findOrganizations(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.session,\n targetOrganizations: organizations,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await organizationService.countOrganizations(filters);\n\n const responseData = formatPaginatedResponse<OrganizationAPI>({\n data: mapOrganizationsToAPI(organizations),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.code(200).send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetOrganizationParam = { organizationId: string };\nexport type GetOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Retrieves an organization by its ID.\n */\nexport const getOrganization = async (\n request: FastifyRequest<{ Params: GetOrganizationParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { roles } = request.session || {};\n const { organizationId } = request.params;\n\n if (!organizationId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_ID_NOT_FOUND'\n );\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const responseData = formatResponse<OrganizationAPI>({\n data: mapOrganizationToAPI(organization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddOrganizationBody = OrganizationCreationData;\nexport type AddOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Adds a new organization to the database.\n */\nexport const addOrganization = async (\n request: FastifyRequest<{ Body: AddOrganizationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user } = request.session || {};\n const organization = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_DATA_NOT_FOUND'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const newOrganization = await organizationService.createOrganization(\n organization,\n user.id\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization created successfully',\n 'en-GB': 'Organization created successfully',\n fr: 'Organisation créée avec succès',\n es: 'Organización creada con éxito',\n ru: 'Организация успешно создана',\n ja: '組織が正常に作成されました',\n ko: '조직이 성공적으로 생성되었습니다',\n zh: '组织已成功创建',\n de: 'Organisation erfolgreich erstellt',\n ar: 'تم إنشاء المنظمة بنجاح',\n it: 'Organizzazione creata con successo',\n pt: 'Organização criada com sucesso',\n hi: 'संगठन सफलतापूर्वक बनाया गया',\n tr: 'Organizasyon başarıyla oluşturuldu',\n pl: 'Organizacja została pomyślnie utworzona',\n id: 'Organisasi berhasil dibuat',\n vi: 'Tổ chức đã được tạo thành công',\n uk: 'Організацію успішно створено',\n }),\n description: t({\n en: 'Your organization has been created successfully',\n 'en-GB': 'Your organization has been created successfully',\n fr: 'Votre organisation a été créée avec succès',\n es: 'Su organización ha sido creada con éxito',\n ru: 'Ваша организация была успешно создана',\n ja: '組織は正常に作成されました',\n ko: '조직이 성공적으로 생성되었습니다',\n zh: '您的组织已成功创建',\n de: 'Ihre Organisation wurde erfolgreich erstellt',\n ar: 'لقد تم إنشاء منظمتك بنجاح',\n it: 'La tua organizzazione è stata creata con successo',\n pt: 'Sua organização foi criada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक बना लिया गया है',\n tr: 'Organizasyonunuz başarıyla oluşturuldu',\n pl: 'Twoja organizacja została pomyślnie utworzona',\n id: 'Organisasi Anda telah berhasil dibuat',\n vi: 'Tổ chức của bạn đã được tạo thành công',\n uk: 'Вашу організацію успішно створено',\n }),\n data: mapOrganizationToAPI(newOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationBody = Partial<Organization>;\nexport type UpdateOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Updates an existing organization in the database.\n */\nexport const updateOrganization = async (\n request: FastifyRequest<{ Body: UpdateOrganizationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, roles } = request.session || {};\n const organizationFields = request.body;\n\n if (!organizationFields) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:write'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedOrganization =\n await organizationService.updateOrganizationById(\n organization.id,\n organizationFields\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n 'en-GB': 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n ru: 'Организация успешно обновлена',\n ja: '組織が正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '组织已成功更新',\n de: 'Organisation erfolgreich aktualisiert',\n ar: 'تم تحديث المنظمة بنجاح',\n it: 'Organizzazione aggiornata con successo',\n pt: 'Organização atualizada com sucesso',\n hi: 'संगठन सफलतापूर्वक अपडेट किया गया',\n tr: 'Organizasyon başarıyla güncellendi',\n pl: 'Organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi berhasil diperbarui',\n vi: 'Tổ chức đã được cập nhật thành công',\n uk: 'Організацію успішно оновлено',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n 'en-GB': 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n ru: 'Ваша организация была успешно обновлена',\n ja: '組織は正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '您的组织已成功更新',\n de: 'Ihre Organisation wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث منظمتك بنجاح',\n it: 'La tua organizzazione è stata aggiornata con successo',\n pt: 'Sua organização foi atualizada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Organizasyonunuz başarıyla güncellendi',\n pl: 'Twoja organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi Anda telah berhasil diperbarui',\n vi: 'Tổ chức của bạn đã được cập nhật thành công',\n uk: 'Вашу організацію успішно оновлено',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddOrganizationMemberBody = {\n userEmail: string;\n};\nexport type AddOrganizationMemberResult = ResponseData<OrganizationAPI>;\n\n/**\n * Add member to the organization in the database.\n */\nexport const addOrganizationMember = async (\n request: FastifyRequest<{ Body: AddOrganizationMemberBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.session || {};\n const { userEmail } = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n const planType = getPlanDetails(organization.plan);\n\n if (\n planType.numberOfOrganizationUsers &&\n organization.membersIds.length >= planType.numberOfOrganizationUsers\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_USER_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n }\n\n try {\n let newMember = await userService.getUserByEmail(userEmail);\n\n if (!newMember) {\n // Create user if not found\n const newUser = await userService.createUser({ email: userEmail });\n if (!newUser) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_CREATION_FAILED',\n {\n email: userEmail,\n }\n );\n }\n\n newMember = newUser;\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n ...organization,\n membersIds: [...organization.membersIds, newMember.id],\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n 'en-GB': 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n ru: 'Организация успешно обновлена',\n ja: '組織が正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '组织已成功更新',\n de: 'Organisation erfolgreich aktualisiert',\n ar: 'تم تحديث المنظمة بنجاح',\n it: 'Organizzazione aggiornata con successo',\n pt: 'Organização atualizada com sucesso',\n hi: 'संगठन सफलतापूर्वक अपडेट किया गया',\n tr: 'Organizasyon başarıyla güncellendi',\n pl: 'Organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi berhasil diperbarui',\n vi: 'Tổ chức đã được cập nhật thành công',\n uk: 'Організацію успішно оновлено',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n 'en-GB': 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n ru: 'Ваша организация была успешно обновлена',\n ja: '組織は正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '您的组织已成功更新',\n de: 'Ihre Organisation wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث منظمتك بنجاح',\n it: 'La tua organizzazione è stata aggiornata con successo',\n pt: 'Sua organização foi atualizada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Organizasyonunuz başarıyla güncellendi',\n pl: 'Twoja organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi Anda telah berhasil diperbarui',\n vi: 'Tổ chức của bạn đã được cập nhật thành công',\n uk: 'Вашу організацію успішно оновлено',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n await sendEmail({\n type: 'invite',\n to: userEmail,\n username: newMember.email.slice(0, newMember.email.indexOf('@')),\n invitedByUsername: user.name,\n invitedByEmail: user.email,\n organizationName: organization.name,\n inviteLink: `${process.env.APP_URL}/auth/login?email=${newMember.email}`,\n inviteFromIp: request.ip ?? '',\n inviteFromLocation: request.hostname,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationMembersBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersResult = ResponseData<OrganizationAPI>;\n\n/**\n * Update members to the organization in the database.\n */\nexport const updateOrganizationMembers = async (\n request: FastifyRequest<{ Body: UpdateOrganizationMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, roles } = request.session || {};\n const { membersIds, adminsIds } = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (!membersIds) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n }\n\n if (adminsIds?.filter((id) => membersIds?.includes(id)).length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n }\n\n try {\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const existingAdmins = await userService.getUsersByIds(adminsIds!);\n\n if (!existingAdmins) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n membersIds: existingUsers.map((user) => user.id),\n adminsIds: existingAdmins.map((user) => user.id),\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n 'en-GB': 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n ru: 'Организация успешно обновлена',\n ja: '組織が正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '组织已成功更新',\n de: 'Organisation успешно обновлена',\n ar: 'تم تحديث المنظمة بنجاح',\n it: 'Organizzazione aggiornata con successo',\n pt: 'Organização atualizada con sucesso',\n hi: 'संगठन सफलतापूर्वक अपडेट किया गया',\n tr: 'Organizasyon başarıyla güncellendi',\n pl: 'Organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi berhasil diperbarui',\n vi: 'Tổ chức đã được cập nhật thành công',\n uk: 'Організацію успішно оновлено',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n 'en-GB': 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n ru: 'Ваша организация была успешно обновлена',\n ja: '組織は正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '您的组织已成功更新',\n de: 'Ihre Organisation wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث منظمتك بنجاح',\n it: 'La tua organizzazione è stata aggiornata con successo',\n pt: 'Sua organização foi atualizada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Organizasyonunuz başarıyla güncellendi',\n pl: 'Twoja organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi Anda telah berhasil diperbarui',\n vi: 'Tổ chức của bạn đã được cập nhật thành công',\n uk: 'Вашу організацію успішно оновлено',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationMembersByIdParams = { organizationId: string };\nexport type UpdateOrganizationMembersByIdBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersByIdResult = ResponseData<OrganizationAPI>;\n\n/**\n * Admin-only: Update members of any organization by ID\n */\nexport const updateOrganizationMembersById = async (\n request: FastifyRequest<{\n Params: UpdateOrganizationMembersByIdParams;\n Body: UpdateOrganizationMembersByIdBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user } = request.session || {};\n const { organizationId } = request.params;\n const { membersIds, adminsIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (user.role !== 'admin') {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (!membersIds) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n }\n\n try {\n const targetOrganization =\n await organizationService.getOrganizationById(organizationId);\n\n if (!targetOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const finalAdminsIds =\n adminsIds && adminsIds.length > 0\n ? adminsIds\n : targetOrganization.adminsIds;\n const existingAdmins = finalAdminsIds\n ? await userService.getUsersByIds(finalAdminsIds)\n : [];\n\n if (!existingAdmins || existingAdmins.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(targetOrganization.id, {\n membersIds: existingUsers.map((user) => user.id),\n adminsIds: existingAdmins.map((user) => user.id),\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization members updated successfully',\n 'en-GB': 'Organization members updated successfully',\n fr: \"Membres de l'organisation mis à jour avec succès\",\n es: 'Miembros de la organización actualizados con éxito',\n ru: 'Члены организации успешно обновлены',\n ja: '組織メンバーが正常に更新されました',\n ko: '조직 구성원이 성공적으로 업데이트되었습니다',\n zh: '组织成员已成功更新',\n de: 'Organisationsmitglieder erfolgreich aktualisiert',\n ar: 'تم تحديث أعضاء المنظمة بنجاح',\n it: \"Membri dell'organizzazione aggiornati con successo\",\n pt: 'Membros da organização atualizados com sucesso',\n hi: 'संगठन के सदस्य सफलतापूर्वक अपडेट किए गए',\n tr: 'Organizasyon üyeleri başarıyla güncellendi',\n pl: 'Członkowie organizacji zostali pomyślnie zaktualizowani',\n id: 'Anggota organisasi berhasil diperbarui',\n vi: 'Thành viên tổ chức đã được cập nhật thành công',\n uk: 'Члени організації успішно оновлені',\n }),\n description: t({\n en: 'Organization members have been updated successfully',\n 'en-GB': 'Organization members have been updated successfully',\n fr: \"Les membres de l'organisation ont été mis à jour avec succès\",\n es: 'Los miembros de la organización han sido actualizados con éxito',\n ru: 'Члены организации были успешно обновлены',\n ja: '組織メンバーは正常に更新されました',\n ko: '조직 구성원이 성공적으로 업데이트되었습니다',\n zh: '组织成员已成功更新',\n de: 'Organisationsmitglieder wurden erfolgreich aktualisiert',\n ar: 'لقد تم تحديث أعضاء المنظمة بنجاح',\n it: \"I membri dell'organizzazione sono stati aggiornati con successo\",\n pt: 'Os membros da organização foram atualizados com sucesso',\n hi: 'संगठन के सदस्यों को सफलतापूर्वक अपडेट किया गया है',\n tr: 'Organizasyon üyeleri başarıyla güncellendi',\n pl: 'Członkowie organizacji zostali pomyślnie zaktualizowani',\n id: 'Anggota organisasi telah berhasil diperbarui',\n vi: 'Thành viên tổ chức đã được cập nhật thành công',\n uk: 'Члени організації успішно оновлені',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Deletes an organization from the database by its ID.\n */\nexport const deleteOrganization = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const { organization, session, roles } = _request.session || {};\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n const projects = await projectService.findProjects({\n organizationId: organization.id,\n });\n\n if (projects.length > 0) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PROJECTS_EXIST', {\n organizationId: organization.id,\n });\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ..._request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n // Cancel the subscription on Stripe if it exists\n if (organization.plan?.subscriptionId) {\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n }\n\n const deletedOrganization =\n await organizationService.deleteOrganizationById(organization.id);\n\n if (!deletedOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND',\n {\n organizationId: organization.id,\n }\n );\n }\n\n // Update session to set activeOrganizationId\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: null,\n activeProjectId: null,\n },\n }\n );\n\n logger.info(`Organization deleted: ${String(deletedOrganization.id)}`);\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization deleted successfully',\n 'en-GB': 'Organization deleted successfully',\n fr: 'Organisation supprimée avec succès',\n es: 'Organización eliminada con éxito',\n ru: 'Организация успешно удалена',\n ja: '組織が正常に削除されました',\n ko: '조직이 성공적으로 삭제되었습니다',\n zh: '组织已成功删除',\n de: 'Organisation erfolgreich gelöscht',\n ar: 'تم حذف المنظمة بنجاح',\n it: 'Organizzazione eliminata con successo',\n pt: 'Organização excluída com sucesso',\n hi: 'संगठन सफलतापूर्वक हटा दिया गया',\n tr: 'Organizasyon başarıyla silindi',\n pl: 'Organizacja została pomyślnie usunięta',\n id: 'Organisasi berhasil dihapus',\n vi: 'Tổ chức đã được xóa thành công',\n uk: 'Організацію успішно видалено',\n }),\n description: t({\n en: 'Your organization has been deleted successfully',\n 'en-GB': 'Your organization has been deleted successfully',\n fr: 'Votre organisation a été supprimée avec succès',\n es: 'Su organización ha sido eliminada con éxito',\n ru: 'Ваша организация была успешно удалена',\n ja: '組織は正常に削除されました',\n ko: '조직이 성공적으로 삭제되었습니다',\n zh: '您的组织已成功删除',\n de: 'Ihre Organisation wurde erfolgreich gelöscht',\n ar: 'لقد تم حذف منظمتك بنجاح',\n it: 'La tua organizzazione è stata eliminata con successo',\n pt: 'Sua organização foi excluída com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक हटा दिया गया है',\n tr: 'Organizasyonunuz başarıyla silindi',\n pl: 'Twoja organizacja została pomyślnie usunięta',\n id: 'Organisasi Anda telah berhasil dihapus',\n vi: 'Tổ chức của bạn đã được xóa thành công',\n uk: 'Вашу організацію успішно видалено',\n }),\n data: mapOrganizationToAPI(deletedOrganization),\n });\n\n // No need to update session here, as it's a delete operation\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteOrganizationByIdAdminParams = { organizationId: string };\nexport type DeleteOrganizationByIdAdminResult = ResponseData<OrganizationAPI>;\n\n/**\n * Admin-only: Deletes any organization from the database by its ID.\n */\nexport const deleteOrganizationByIdAdmin = async (\n request: FastifyRequest<{ Params: DeleteOrganizationByIdAdminParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organizationId } = request.params;\n const { roles } = request.session || {};\n\n if (\n !hasPermission(roles || [], 'organization:admin')({ ...request.session })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const deletedOrganization =\n await organizationService.deleteOrganizationById(organization.id);\n\n if (!deletedOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const formattedOrganization = mapOrganizationToAPI(deletedOrganization);\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization deleted',\n fr: 'Organisation supprimée',\n es: 'Organización eliminada',\n 'en-GB': 'Organisation deleted',\n de: 'Organisation gelöscht',\n ja: '組織が削除されました',\n ko: '조직이 삭제되었습니다',\n zh: '组织已删除',\n it: 'Organizzazione eliminata',\n pt: 'Organização excluída',\n hi: 'संगठन हटा दिया गया',\n ar: 'تم حذف المنظمة',\n ru: 'Организация удалена',\n tr: 'Organizasyon silindi',\n pl: 'Organizacja usunięta',\n id: 'Organisasi dihapus',\n vi: 'Tổ chức đã bị xóa',\n uk: 'Організацію видалено',\n }),\n data: formattedOrganization,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type SelectOrganizationParam = {\n organizationId: string | Types.ObjectId;\n};\nexport type SelectOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Select an organization.\n */\nexport const selectOrganization = async (\n request: FastifyRequest<{ Params: SelectOrganizationParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organizationId } = request.params;\n const { session, roles } = request.session || {};\n\n if (!organizationId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_ID_NOT_FOUND'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n // Update session to set activeOrganizationId\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: String(organization.id),\n activeProjectId: null,\n },\n }\n );\n\n // No need to update session here, as it's a select operation\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization retrieved successfully',\n 'en-GB': 'Organization retrieved successfully',\n fr: 'Organisation récupérée avec succès',\n es: 'Organización recuperada con éxito',\n ru: 'Организация успешно получена',\n ja: '組織が正常に取得されました',\n ko: '조직이 성공적으로 검색되었습니다',\n zh: '组织已成功检索',\n de: 'Organisation erfolgreich abgerufen',\n ar: 'تم استرداد المنظمة بنجاح',\n it: 'Organizzazione recuperata con successo',\n pt: 'Organização recuperada com sucesso',\n hi: 'संगठन सफलतापूर्वक प्राप्त किया गया',\n tr: 'Organizasyon başarıyla alındı',\n pl: 'Organizacja została pomyślnie pobrana',\n id: 'Organisasi berhasil diambil',\n vi: 'Tổ chức đã được truy xuất thành công',\n uk: 'Організацію успішно отримано',\n }),\n description: t({\n en: 'Your organization has been retrieved successfully',\n 'en-GB': 'Your organization has been retrieved successfully',\n fr: 'Votre organisation a été récupérée avec succès',\n es: 'Su organización ha sido recuperada con éxito',\n ru: 'Ваша организация была успешно получена',\n ja: '組織は正常に取得されました',\n ko: '조직이 성공적으로 검색되었습니다',\n zh: '您的组织已成功检索',\n de: 'Ihre Organisation wurde erfolgreich abgerufen',\n ar: 'لقد تم استرداد منظمتك بنجاح',\n it: 'La tua organizzazione è stata recuperata con successo',\n pt: 'Sua organização foi recuperada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक प्राप्त कर लिया गया है',\n tr: 'Organizasyonunuz başarıyla alındı',\n pl: 'Twoja organizacja została pomyślnie pobrana',\n id: 'Organisasi Anda telah berhasil diambil',\n vi: 'Tổ chức của bạn đã được truy xuất thành công',\n uk: 'Вашу організацію успішно отримано',\n }),\n data: mapOrganizationToAPI(organization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectOrganizationResult = ResponseData<null>;\n\n/**\n * Unselect an organization.\n */\nexport const unselectOrganization = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { session } = _request.session || {};\n try {\n // Update session to clear activeOrganizationId and activeProjectId\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: null,\n activeProjectId: null,\n },\n }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Organization unselected successfully',\n 'en-GB': 'Organization unselected successfully',\n fr: 'Organisation désélectionnée avec succès',\n es: 'Organización deseleccionada con éxito',\n ru: 'Организация успешно снята с выбора',\n ja: '組織の選択が正常に解除されました',\n ko: '조직 선택이 성공적으로 해제되었습니다',\n zh: '组织已成功取消选择',\n de: 'Organisation erfolgreich abgewählt',\n ar: 'تم إلغاء تحديد المنظمة بنجاح',\n it: 'Organizzazione deselezionata con successo',\n pt: 'Organização desmarcada com sucesso',\n hi: 'संगठन सफलतापूर्वक अनसेलेक्ट किया गया',\n tr: 'Organizasyon seçimi başarıyla kaldırıldı',\n pl: 'Wybór organizacji został pomyślnie cofnięty',\n id: 'Organisasi berhasil batal dipilih',\n vi: 'Tổ chức đã được bỏ chọn thành công',\n uk: 'Вибір організації успішно скасовано',\n }),\n description: t({\n en: 'Your organization has been unselected successfully',\n 'en-GB': 'Your organization has been unselected successfully',\n fr: 'Votre organisation a été désélectionnée avec succès',\n es: 'Su organización ha sido deseleccionada con éxito',\n ru: 'Выбор вашей организации был успешно снят',\n ja: '組織の選択は正常に解除されました',\n ko: '조직 선택이 성공적으로 해제되었습니다',\n zh: '您的组织已成功取消选择',\n de: 'Ihre Organisation wurde erfolgreich abgewählt',\n ar: 'لقد تم إلغاء تحديد منظمتك بنجاح',\n it: 'La tua organizzazione è stata deselezionata con successo',\n pt: 'Sua organização foi desmarcada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अनसेलेक्ट कर दिया गया है',\n tr: 'Organizasyonunuzun seçimi başarıyla kaldırıldı',\n pl: 'Wybór Twojej organizacji został pomyślnie cofnięty',\n id: 'Organisasi Anda telah berhasil batal dipilih',\n vi: 'Tổ chức của bạn đã được bỏ chọn thành công',\n uk: 'Вибір вашої організації успішно скасовано',\n }),\n data: null,\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":"organization.controller.mjs","names":["organizationService.findOrganizations","organizationService.countOrganizations","organizationService.getOrganizationById","organizationService.createOrganization","organizationService.updateOrganizationById","userService.getUserByEmail","userService.createUser","userService.getUsersByIds","projectService.findProjects","organizationService.deleteOrganizationById","userService.updateUserById"],"sources":["../../../src/controllers/organization.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { SessionModel } from '@models/session.model';\nimport { sendEmail } from '@services/email.service';\nimport * as organizationService from '@services/organization.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getOrganizationFiltersAndPagination,\n type OrganizationFiltersParams,\n} from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n mapOrganizationsToAPI,\n mapOrganizationToAPI,\n} from '@utils/mapper/organization';\nimport { hasPermission } from '@utils/permissions';\nimport { getPlanDetails } from '@utils/plan';\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 { Types } from 'mongoose';\nimport Stripe from 'stripe';\nimport type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n} from '@/types/organization.types';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type GetOrganizationsParams =\n FiltersAndPagination<OrganizationFiltersParams>;\nexport type GetOrganizationsResult = PaginatedResponse<OrganizationAPI>;\n\n/**\n * Retrieves a list of organizations based on filters and pagination.\n */\nexport const getOrganizations = async (\n request: FastifyRequest<{ Querystring: GetOrganizationsParams }>,\n reply: FastifyReply\n) => {\n const { user, roles } = request.session || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getOrganizationFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const organizations = await organizationService.findOrganizations(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.session,\n targetOrganizations: organizations,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await organizationService.countOrganizations(filters);\n\n const responseData = formatPaginatedResponse<OrganizationAPI>({\n data: mapOrganizationsToAPI(organizations),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.code(200).send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetOrganizationParam = { organizationId: string };\nexport type GetOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Retrieves an organization by its ID.\n */\nexport const getOrganization = async (\n request: FastifyRequest<{ Params: GetOrganizationParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { roles } = request.session || {};\n const { organizationId } = request.params;\n\n if (!organizationId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_ID_NOT_FOUND'\n );\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const responseData = formatResponse<OrganizationAPI>({\n data: mapOrganizationToAPI(organization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddOrganizationBody = OrganizationCreationData;\nexport type AddOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Adds a new organization to the database.\n */\nexport const addOrganization = async (\n request: FastifyRequest<{ Body: AddOrganizationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user } = request.session || {};\n const organization = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_DATA_NOT_FOUND'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const newOrganization = await organizationService.createOrganization(\n organization,\n user.id\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization created successfully',\n 'en-GB': 'Organization created successfully',\n fr: 'Organisation créée avec succès',\n es: 'Organización creada con éxito',\n ru: 'Организация успешно создана',\n ja: '組織が正常に作成されました',\n ko: '조직이 성공적으로 생성되었습니다',\n zh: '组织已成功创建',\n de: 'Organisation erfolgreich erstellt',\n ar: 'تم إنشاء المنظمة بنجاح',\n it: 'Organizzazione creata con successo',\n pt: 'Organização criada com sucesso',\n hi: 'संगठन सफलतापूर्वक बनाया गया',\n tr: 'Organizasyon başarıyla oluşturuldu',\n pl: 'Organizacja została pomyślnie utworzona',\n id: 'Organisasi berhasil dibuat',\n vi: 'Tổ chức đã được tạo thành công',\n uk: 'Організацію успішно створено',\n }),\n description: t({\n en: 'Your organization has been created successfully',\n 'en-GB': 'Your organization has been created successfully',\n fr: 'Votre organisation a été créée avec succès',\n es: 'Su organización ha sido creada con éxito',\n ru: 'Ваша организация была успешно создана',\n ja: '組織は正常に作成されました',\n ko: '조직이 성공적으로 생성되었습니다',\n zh: '您的组织已成功创建',\n de: 'Ihre Organisation wurde erfolgreich erstellt',\n ar: 'لقد تم إنشاء منظمتك بنجاح',\n it: 'La tua organizzazione è stata creata con successo',\n pt: 'Sua organização foi criada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक बना लिया गया है',\n tr: 'Organizasyonunuz başarıyla oluşturuldu',\n pl: 'Twoja organizacja została pomyślnie utworzona',\n id: 'Organisasi Anda telah berhasil dibuat',\n vi: 'Tổ chức của bạn đã được tạo thành công',\n uk: 'Вашу організацію успішно створено',\n }),\n data: mapOrganizationToAPI(newOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationBody = Partial<Organization>;\nexport type UpdateOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Updates an existing organization in the database.\n */\nexport const updateOrganization = async (\n request: FastifyRequest<{ Body: UpdateOrganizationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, roles } = request.session || {};\n const organizationFields = request.body;\n\n if (!organizationFields) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:write'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedOrganization =\n await organizationService.updateOrganizationById(\n organization.id,\n organizationFields\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n 'en-GB': 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n ru: 'Организация успешно обновлена',\n ja: '組織が正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '组织已成功更新',\n de: 'Organisation erfolgreich aktualisiert',\n ar: 'تم تحديث المنظمة بنجاح',\n it: 'Organizzazione aggiornata con successo',\n pt: 'Organização atualizada com sucesso',\n hi: 'संगठन सफलतापूर्वक अपडेट किया गया',\n tr: 'Organizasyon başarıyla güncellendi',\n pl: 'Organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi berhasil diperbarui',\n vi: 'Tổ chức đã được cập nhật thành công',\n uk: 'Організацію успішно оновлено',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n 'en-GB': 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n ru: 'Ваша организация была успешно обновлена',\n ja: '組織は正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '您的组织已成功更新',\n de: 'Ihre Organisation wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث منظمتك بنجاح',\n it: 'La tua organizzazione è stata aggiornata con successo',\n pt: 'Sua organização foi atualizada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Organizasyonunuz başarıyla güncellendi',\n pl: 'Twoja organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi Anda telah berhasil diperbarui',\n vi: 'Tổ chức của bạn đã được cập nhật thành công',\n uk: 'Вашу організацію успішно оновлено',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddOrganizationMemberBody = {\n userEmail: string;\n};\nexport type AddOrganizationMemberResult = ResponseData<OrganizationAPI>;\n\n/**\n * Add member to the organization in the database.\n */\nexport const addOrganizationMember = async (\n request: FastifyRequest<{ Body: AddOrganizationMemberBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.session || {};\n const { userEmail } = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n const planType = getPlanDetails(organization.plan);\n\n if (\n planType.numberOfOrganizationUsers &&\n organization.membersIds.length >= planType.numberOfOrganizationUsers\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_USER_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n }\n\n try {\n let newMember = await userService.getUserByEmail(userEmail);\n\n if (!newMember) {\n // Create user if not found\n const newUser = await userService.createUser({ email: userEmail });\n if (!newUser) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_CREATION_FAILED',\n {\n email: userEmail,\n }\n );\n }\n\n newMember = newUser;\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n ...organization,\n membersIds: [...organization.membersIds, newMember.id],\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n 'en-GB': 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n ru: 'Организация успешно обновлена',\n ja: '組織が正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '组织已成功更新',\n de: 'Organisation erfolgreich aktualisiert',\n ar: 'تم تحديث المنظمة بنجاح',\n it: 'Organizzazione aggiornata con successo',\n pt: 'Organização atualizada com sucesso',\n hi: 'संगठन सफलतापूर्वक अपडेट किया गया',\n tr: 'Organizasyon başarıyla güncellendi',\n pl: 'Organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi berhasil diperbarui',\n vi: 'Tổ chức đã được cập nhật thành công',\n uk: 'Організацію успішно оновлено',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n 'en-GB': 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n ru: 'Ваша организация была успешно обновлена',\n ja: '組織は正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '您的组织已成功更新',\n de: 'Ihre Organisation wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث منظمتك بنجاح',\n it: 'La tua organizzazione è stata aggiornata con successo',\n pt: 'Sua organização foi atualizada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Organizasyonunuz başarıyla güncellendi',\n pl: 'Twoja organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi Anda telah berhasil diperbarui',\n vi: 'Tổ chức của bạn đã được cập nhật thành công',\n uk: 'Вашу організацію успішно оновлено',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n await sendEmail({\n type: 'invite',\n to: userEmail,\n username: newMember.email.slice(0, newMember.email.indexOf('@')),\n invitedByUsername: user.name,\n invitedByEmail: user.email,\n organizationName: organization.name,\n inviteLink: `${process.env.APP_URL}/auth/login?email=${newMember.email}`,\n inviteFromIp: request.ip ?? '',\n inviteFromLocation: request.hostname,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationMembersBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersResult = ResponseData<OrganizationAPI>;\n\n/**\n * Update members to the organization in the database.\n */\nexport const updateOrganizationMembers = async (\n request: FastifyRequest<{ Body: UpdateOrganizationMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, roles } = request.session || {};\n const { membersIds, adminsIds } = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (!membersIds) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n }\n\n if (adminsIds?.filter((id) => membersIds?.includes(id)).length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n }\n\n try {\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const existingAdmins = await userService.getUsersByIds(adminsIds!);\n\n if (!existingAdmins) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n membersIds: existingUsers.map((user) => user.id),\n adminsIds: existingAdmins.map((user) => user.id),\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n 'en-GB': 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n ru: 'Организация успешно обновлена',\n ja: '組織が正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '组织已成功更新',\n de: 'Organisation успешно обновлена',\n ar: 'تم تحديث المنظمة بنجاح',\n it: 'Organizzazione aggiornata con successo',\n pt: 'Organização atualizada con sucesso',\n hi: 'संगठन सफलतापूर्वक अपडेट किया गया',\n tr: 'Organizasyon başarıyla güncellendi',\n pl: 'Organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi berhasil diperbarui',\n vi: 'Tổ chức đã được cập nhật thành công',\n uk: 'Організацію успішно оновлено',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n 'en-GB': 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n ru: 'Ваша организация была успешно обновлена',\n ja: '組織は正常に更新されました',\n ko: '조직이 성공적으로 업데이트되었습니다',\n zh: '您的组织已成功更新',\n de: 'Ihre Organisation wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث منظمتك بنجاح',\n it: 'La tua organizzazione è stata aggiornata con successo',\n pt: 'Sua organização foi atualizada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Organizasyonunuz başarıyla güncellendi',\n pl: 'Twoja organizacja została pomyślnie zaktualizowana',\n id: 'Organisasi Anda telah berhasil diperbarui',\n vi: 'Tổ chức của bạn đã được cập nhật thành công',\n uk: 'Вашу організацію успішно оновлено',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationMembersByIdParams = { organizationId: string };\nexport type UpdateOrganizationMembersByIdBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersByIdResult = ResponseData<OrganizationAPI>;\n\n/**\n * Admin-only: Update members of any organization by ID\n */\nexport const updateOrganizationMembersById = async (\n request: FastifyRequest<{\n Params: UpdateOrganizationMembersByIdParams;\n Body: UpdateOrganizationMembersByIdBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user } = request.session || {};\n const { organizationId } = request.params;\n const { membersIds, adminsIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (user.role !== 'admin') {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (!membersIds) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n }\n\n try {\n const targetOrganization =\n await organizationService.getOrganizationById(organizationId);\n\n if (!targetOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const finalAdminsIds =\n adminsIds && adminsIds.length > 0\n ? adminsIds\n : targetOrganization.adminsIds;\n const existingAdmins = finalAdminsIds\n ? await userService.getUsersByIds(finalAdminsIds)\n : [];\n\n if (!existingAdmins || existingAdmins.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(targetOrganization.id, {\n membersIds: existingUsers.map((user) => user.id),\n adminsIds: existingAdmins.map((user) => user.id),\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization members updated successfully',\n 'en-GB': 'Organization members updated successfully',\n fr: \"Membres de l'organisation mis à jour avec succès\",\n es: 'Miembros de la organización actualizados con éxito',\n ru: 'Члены организации успешно обновлены',\n ja: '組織メンバーが正常に更新されました',\n ko: '조직 구성원이 성공적으로 업데이트되었습니다',\n zh: '组织成员已成功更新',\n de: 'Organisationsmitglieder erfolgreich aktualisiert',\n ar: 'تم تحديث أعضاء المنظمة بنجاح',\n it: \"Membri dell'organizzazione aggiornati con successo\",\n pt: 'Membros da organização atualizados com sucesso',\n hi: 'संगठन के सदस्य सफलतापूर्वक अपडेट किए गए',\n tr: 'Organizasyon üyeleri başarıyla güncellendi',\n pl: 'Członkowie organizacji zostali pomyślnie zaktualizowani',\n id: 'Anggota organisasi berhasil diperbarui',\n vi: 'Thành viên tổ chức đã được cập nhật thành công',\n uk: 'Члени організації успішно оновлені',\n }),\n description: t({\n en: 'Organization members have been updated successfully',\n 'en-GB': 'Organization members have been updated successfully',\n fr: \"Les membres de l'organisation ont été mis à jour avec succès\",\n es: 'Los miembros de la organización han sido actualizados con éxito',\n ru: 'Члены организации были успешно обновлены',\n ja: '組織メンバーは正常に更新されました',\n ko: '조직 구성원이 성공적으로 업데이트되었습니다',\n zh: '组织成员已成功更新',\n de: 'Organisationsmitglieder wurden erfolgreich aktualisiert',\n ar: 'لقد تم تحديث أعضاء المنظمة بنجاح',\n it: \"I membri dell'organizzazione sono stati aggiornati con successo\",\n pt: 'Os membros da organização foram atualizados com sucesso',\n hi: 'संगठन के सदस्यों को सफलतापूर्वक अपडेट किया गया है',\n tr: 'Organizasyon üyeleri başarıyla güncellendi',\n pl: 'Członkowie organizacji zostali pomyślnie zaktualizowani',\n id: 'Anggota organisasi telah berhasil diperbarui',\n vi: 'Thành viên tổ chức đã được cập nhật thành công',\n uk: 'Члени організації успішно оновлені',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Deletes an organization from the database by its ID.\n */\nexport const deleteOrganization = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const { organization, session, roles, user } = _request.session || {};\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n const projects = await projectService.findProjects({\n organizationId: organization.id,\n });\n\n if (projects.length > 0) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PROJECTS_EXIST', {\n organizationId: organization.id,\n });\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ..._request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n // Cancel the subscription on Stripe if it exists\n if (organization.plan?.subscriptionId) {\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n }\n\n const deletedOrganization =\n await organizationService.deleteOrganizationById(organization.id);\n\n if (!deletedOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND',\n {\n organizationId: organization.id,\n }\n );\n }\n\n // Update session to set activeOrganizationId\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: null,\n activeProjectId: null,\n },\n }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveOrganizationId: null,\n lastActiveProjectId: null,\n });\n }\n\n logger.info(`Organization deleted: ${String(deletedOrganization.id)}`);\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization deleted successfully',\n 'en-GB': 'Organization deleted successfully',\n fr: 'Organisation supprimée avec succès',\n es: 'Organización eliminada con éxito',\n ru: 'Организация успешно удалена',\n ja: '組織が正常に削除されました',\n ko: '조직이 성공적으로 삭제되었습니다',\n zh: '组织已成功删除',\n de: 'Organisation erfolgreich gelöscht',\n ar: 'تم حذف المنظمة بنجاح',\n it: 'Organizzazione eliminata con successo',\n pt: 'Organização excluída com sucesso',\n hi: 'संगठन सफलतापूर्वक हटा दिया गया',\n tr: 'Organizasyon başarıyla silindi',\n pl: 'Organizacja została pomyślnie usunięta',\n id: 'Organisasi berhasil dihapus',\n vi: 'Tổ chức đã được xóa thành công',\n uk: 'Організацію успішно видалено',\n }),\n description: t({\n en: 'Your organization has been deleted successfully',\n 'en-GB': 'Your organization has been deleted successfully',\n fr: 'Votre organisation a été supprimée avec succès',\n es: 'Su organización ha sido eliminada con éxito',\n ru: 'Ваша организация была успешно удалена',\n ja: '組織は正常に削除されました',\n ko: '조직이 성공적으로 삭제되었습니다',\n zh: '您的组织已成功删除',\n de: 'Ihre Organisation wurde erfolgreich gelöscht',\n ar: 'لقد تم حذف منظمتك بنجاح',\n it: 'La tua organizzazione è stata eliminata con successo',\n pt: 'Sua organização foi excluída com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक हटा दिया गया है',\n tr: 'Organizasyonunuz başarıyla silindi',\n pl: 'Twoja organizacja została pomyślnie usunięta',\n id: 'Organisasi Anda telah berhasil dihapus',\n vi: 'Tổ chức của bạn đã được xóa thành công',\n uk: 'Вашу організацію успішно видалено',\n }),\n data: mapOrganizationToAPI(deletedOrganization),\n });\n\n // No need to update session here, as it's a delete operation\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteOrganizationByIdAdminParams = { organizationId: string };\nexport type DeleteOrganizationByIdAdminResult = ResponseData<OrganizationAPI>;\n\n/**\n * Admin-only: Deletes any organization from the database by its ID.\n */\nexport const deleteOrganizationByIdAdmin = async (\n request: FastifyRequest<{ Params: DeleteOrganizationByIdAdminParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organizationId } = request.params;\n const { roles } = request.session || {};\n\n if (\n !hasPermission(roles || [], 'organization:admin')({ ...request.session })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const deletedOrganization =\n await organizationService.deleteOrganizationById(organization.id);\n\n if (!deletedOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const formattedOrganization = mapOrganizationToAPI(deletedOrganization);\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization deleted',\n fr: 'Organisation supprimée',\n es: 'Organización eliminada',\n 'en-GB': 'Organisation deleted',\n de: 'Organisation gelöscht',\n ja: '組織が削除されました',\n ko: '조직이 삭제되었습니다',\n zh: '组织已删除',\n it: 'Organizzazione eliminata',\n pt: 'Organização excluída',\n hi: 'संगठन हटा दिया गया',\n ar: 'تم حذف المنظمة',\n ru: 'Организация удалена',\n tr: 'Organizasyon silindi',\n pl: 'Organizacja usunięta',\n id: 'Organisasi dihapus',\n vi: 'Tổ chức đã bị xóa',\n uk: 'Організацію видалено',\n }),\n data: formattedOrganization,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type SelectOrganizationParam = {\n organizationId: string | Types.ObjectId;\n};\nexport type SelectOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Select an organization.\n */\nexport const selectOrganization = async (\n request: FastifyRequest<{ Params: SelectOrganizationParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organizationId } = request.params;\n const { session, roles, user } = request.session || {};\n\n if (!organizationId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_ID_NOT_FOUND'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.session,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n // Update session to set activeOrganizationId\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: String(organization.id),\n activeProjectId: null,\n },\n }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveOrganizationId: String(organization.id),\n lastActiveProjectId: null,\n });\n }\n\n // No need to update session here, as it's a select operation\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization retrieved successfully',\n 'en-GB': 'Organization retrieved successfully',\n fr: 'Organisation récupérée avec succès',\n es: 'Organización recuperada con éxito',\n ru: 'Организация успешно получена',\n ja: '組織が正常に取得されました',\n ko: '조직이 성공적으로 검색되었습니다',\n zh: '组织已成功检索',\n de: 'Organisation erfolgreich abgerufen',\n ar: 'تم استرداد المنظمة بنجاح',\n it: 'Organizzazione recuperata con successo',\n pt: 'Organização recuperada com sucesso',\n hi: 'संगठन सफलतापूर्वक प्राप्त किया गया',\n tr: 'Organizasyon başarıyla alındı',\n pl: 'Organizacja została pomyślnie pobrana',\n id: 'Organisasi berhasil diambil',\n vi: 'Tổ chức đã được truy xuất thành công',\n uk: 'Організацію успішно отримано',\n }),\n description: t({\n en: 'Your organization has been retrieved successfully',\n 'en-GB': 'Your organization has been retrieved successfully',\n fr: 'Votre organisation a été récupérée avec succès',\n es: 'Su organización ha sido recuperada con éxito',\n ru: 'Ваша организация была успешно получена',\n ja: '組織は正常に取得されました',\n ko: '조직이 성공적으로 검색되었습니다',\n zh: '您的组织已成功检索',\n de: 'Ihre Organisation wurde erfolgreich abgerufen',\n ar: 'لقد تم استرداد منظمتك بنجاح',\n it: 'La tua organizzazione è stata recuperata con successo',\n pt: 'Sua organização foi recuperada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक प्राप्त कर लिया गया है',\n tr: 'Organizasyonunuz başarıyla alındı',\n pl: 'Twoja organizacja została pomyślnie pobrana',\n id: 'Organisasi Anda telah berhasil diambil',\n vi: 'Tổ chức của bạn đã được truy xuất thành công',\n uk: 'Вашу організацію успішно отримано',\n }),\n data: mapOrganizationToAPI(organization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectOrganizationResult = ResponseData<null>;\n\n/**\n * Unselect an organization.\n */\nexport const unselectOrganization = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { session, user } = _request.session || {};\n try {\n // Update session to clear activeOrganizationId and activeProjectId\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: null,\n activeProjectId: null,\n },\n }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveOrganizationId: null,\n lastActiveProjectId: null,\n });\n }\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Organization unselected successfully',\n 'en-GB': 'Organization unselected successfully',\n fr: 'Organisation désélectionnée avec succès',\n es: 'Organización deseleccionada con éxito',\n ru: 'Организация успешно снята с выбора',\n ja: '組織の選択が正常に解除されました',\n ko: '조직 선택이 성공적으로 해제되었습니다',\n zh: '组织已成功取消选择',\n de: 'Organisation erfolgreich abgewählt',\n ar: 'تم إلغاء تحديد المنظمة بنجاح',\n it: 'Organizzazione deselezionata con successo',\n pt: 'Organização desmarcada com sucesso',\n hi: 'संगठन सफलतापूर्वक अनसेलेक्ट किया गया',\n tr: 'Organizasyon seçimi başarıyla kaldırıldı',\n pl: 'Wybór organizacji został pomyślnie cofnięty',\n id: 'Organisasi berhasil batal dipilih',\n vi: 'Tổ chức đã được bỏ chọn thành công',\n uk: 'Вибір організації успішно скасовано',\n }),\n description: t({\n en: 'Your organization has been unselected successfully',\n 'en-GB': 'Your organization has been unselected successfully',\n fr: 'Votre organisation a été désélectionnée avec succès',\n es: 'Su organización ha sido deseleccionada con éxito',\n ru: 'Выбор вашей организации был успешно снят',\n ja: '組織の選択は正常に解除されました',\n ko: '조직 선택이 성공적으로 해제되었습니다',\n zh: '您的组织已成功取消选择',\n de: 'Ihre Organisation wurde erfolgreich abgewählt',\n ar: 'لقد تم إلغاء تحديد منظمتك بنجاح',\n it: 'La tua organizzazione è stata deselezionata con successo',\n pt: 'Sua organização foi desmarcada com sucesso',\n hi: 'आपका संगठन सफलतापूर्वक अनसेलेक्ट कर दिया गया है',\n tr: 'Organizasyonunuzun seçimi başarıyla kaldırıldı',\n pl: 'Wybór Twojej organizacji został pomyślnie cofnięty',\n id: 'Organisasi Anda telah berhasil batal dipilih',\n vi: 'Tổ chức của bạn đã được bỏ chọn thành công',\n uk: 'Вибір вашої організації успішно скасовано',\n }),\n data: null,\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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"}
|
|
@@ -3,7 +3,7 @@ import { formatPaginatedResponse, formatResponse } from "../utils/responseData.m
|
|
|
3
3
|
import { ErrorHandler } from "../utils/errors/ErrorHandler.mjs";
|
|
4
4
|
import { countProjects, createProject, deleteProjectById, findProjects, getProjectById, updateProjectById } from "../services/project.service.mjs";
|
|
5
5
|
import { createDemoDictionaries } from "../services/dictionary.service.mjs";
|
|
6
|
-
import { getUsersByIds } from "../services/user.service.mjs";
|
|
6
|
+
import { getUsersByIds, updateUserById } from "../services/user.service.mjs";
|
|
7
7
|
import { hasPermission } from "../utils/permissions.mjs";
|
|
8
8
|
import { triggerAll, triggerSingleWebhook } from "../services/webhook.service.mjs";
|
|
9
9
|
import { SessionModel } from "../models/session.model.mjs";
|
|
@@ -143,6 +143,10 @@ const updateProject = async (request, reply) => {
|
|
|
143
143
|
activeOrganizationId: String(organization.id),
|
|
144
144
|
activeProjectId: String(project.id)
|
|
145
145
|
} });
|
|
146
|
+
if (user) await updateUserById(user.id, {
|
|
147
|
+
lastActiveOrganizationId: String(organization.id),
|
|
148
|
+
lastActiveProjectId: String(project.id)
|
|
149
|
+
});
|
|
146
150
|
refreshProjectScreenshotIfChanged({
|
|
147
151
|
newApplicationUrl,
|
|
148
152
|
previousApplicationUrl,
|
|
@@ -546,6 +550,7 @@ const deleteProject = async (_request, reply) => {
|
|
|
546
550
|
data: mapProjectToAPI(deletedProject)
|
|
547
551
|
});
|
|
548
552
|
await SessionModel.updateOne({ _id: session.id }, { $set: { activeProjectId: null } });
|
|
553
|
+
if (user) await updateUserById(user.id, { lastActiveProjectId: null });
|
|
549
554
|
return reply.send(responseData);
|
|
550
555
|
} catch (error) {
|
|
551
556
|
return ErrorHandler.handleAppErrorResponse(reply, error);
|
|
@@ -597,7 +602,7 @@ const deleteProjectByIdAdmin = async (request, reply) => {
|
|
|
597
602
|
*/
|
|
598
603
|
const selectProject = async (request, reply) => {
|
|
599
604
|
const { projectId } = request.params;
|
|
600
|
-
const { session, roles } = request.session || {};
|
|
605
|
+
const { session, roles, user } = request.session || {};
|
|
601
606
|
if (!projectId) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_ID_NOT_FOUND");
|
|
602
607
|
if (typeof session === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "SESSION_NOT_DEFINED");
|
|
603
608
|
try {
|
|
@@ -607,6 +612,7 @@ const selectProject = async (request, reply) => {
|
|
|
607
612
|
targetProjects: [project]
|
|
608
613
|
})) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
|
|
609
614
|
await SessionModel.updateOne({ _id: session.id }, { $set: { activeProjectId: String(projectId) } });
|
|
615
|
+
if (user) await updateUserById(user.id, { lastActiveProjectId: String(projectId) });
|
|
610
616
|
const responseData = formatResponse({
|
|
611
617
|
message: t({
|
|
612
618
|
en: "Project selected successfully",
|
|
@@ -659,10 +665,11 @@ const selectProject = async (request, reply) => {
|
|
|
659
665
|
* Unselect a project.
|
|
660
666
|
*/
|
|
661
667
|
const unselectProject = async (_request, reply) => {
|
|
662
|
-
const { session } = _request.session || {};
|
|
668
|
+
const { session, user } = _request.session || {};
|
|
663
669
|
if (typeof session === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "SESSION_NOT_DEFINED");
|
|
664
670
|
try {
|
|
665
671
|
await SessionModel.updateOne({ _id: session.id }, { $set: { activeProjectId: null } });
|
|
672
|
+
if (user) await updateUserById(user.id, { lastActiveProjectId: null });
|
|
666
673
|
const responseData = formatResponse({
|
|
667
674
|
message: t({
|
|
668
675
|
en: "Project unselected successfully",
|