@intlayer/backend 8.10.0-canary.1 → 8.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +12966 -1
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/interest_of_intlayer.json +1 -14957
- package/dist/esm/controllers/ai.controller.mjs +10 -9
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/audit.controller.mjs +1 -1
- package/dist/esm/controllers/audit.controller.mjs.map +1 -1
- package/dist/esm/controllers/organization.controller.mjs +17 -5
- package/dist/esm/controllers/organization.controller.mjs.map +1 -1
- package/dist/esm/controllers/project.controller.mjs +12 -5
- package/dist/esm/controllers/project.controller.mjs.map +1 -1
- package/dist/esm/controllers/showcaseProject.controller.mjs +0 -2
- package/dist/esm/controllers/showcaseProject.controller.mjs.map +1 -1
- package/dist/esm/controllers/translation.controller.mjs +0 -1
- package/dist/esm/controllers/translation.controller.mjs.map +1 -1
- package/dist/esm/controllers/user.controller.mjs +0 -6
- package/dist/esm/controllers/user.controller.mjs.map +1 -1
- package/dist/esm/schemas/account.schema.mjs +3 -2
- package/dist/esm/schemas/account.schema.mjs.map +1 -1
- package/dist/esm/{models/audit.model.mjs → schemas/audit.schema.mjs} +3 -3
- package/dist/esm/schemas/audit.schema.mjs.map +1 -0
- package/dist/esm/{models/auditJob.model.mjs → schemas/auditJob.schema.mjs} +3 -3
- package/dist/esm/schemas/auditJob.schema.mjs.map +1 -0
- package/dist/esm/{models/auditPage.model.mjs → schemas/auditPage.schema.mjs} +3 -3
- package/dist/esm/schemas/auditPage.schema.mjs.map +1 -0
- package/dist/esm/schemas/cliSessionToken.schema.mjs +3 -2
- package/dist/esm/schemas/cliSessionToken.schema.mjs.map +1 -1
- package/dist/esm/schemas/dictionary.schema.mjs +3 -2
- package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
- package/dist/esm/schemas/discussion.schema.mjs +3 -2
- package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
- package/dist/esm/schemas/oAuth2.schema.mjs +3 -2
- package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
- package/dist/esm/schemas/organization.schema.mjs +3 -2
- package/dist/esm/schemas/organization.schema.mjs.map +1 -1
- package/dist/esm/schemas/project.schema.mjs +3 -2
- package/dist/esm/schemas/project.schema.mjs.map +1 -1
- package/dist/esm/schemas/session.schema.mjs +3 -2
- package/dist/esm/schemas/session.schema.mjs.map +1 -1
- package/dist/esm/schemas/showcaseProject.schema.mjs +3 -2
- package/dist/esm/schemas/showcaseProject.schema.mjs.map +1 -1
- package/dist/esm/schemas/tag.schema.mjs +3 -2
- package/dist/esm/schemas/tag.schema.mjs.map +1 -1
- package/dist/esm/schemas/user.schema.mjs +11 -2
- package/dist/esm/schemas/user.schema.mjs.map +1 -1
- package/dist/esm/services/audit/recursiveAudit.service.mjs +2 -2
- package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
- package/dist/esm/services/bitbucket.service.mjs +1 -1
- package/dist/esm/services/bitbucket.service.mjs.map +1 -1
- package/dist/esm/services/cliSessionToken.service.mjs +1 -1
- 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/github.service.mjs +1 -1
- package/dist/esm/services/github.service.mjs.map +1 -1
- package/dist/esm/services/gitlab.service.mjs +1 -1
- package/dist/esm/services/gitlab.service.mjs.map +1 -1
- package/dist/esm/services/oAuth2.service.mjs +2 -2
- package/dist/esm/services/oAuth2.service.mjs.map +1 -1
- package/dist/esm/services/organization.service.mjs +1 -1
- package/dist/esm/services/organization.service.mjs.map +1 -1
- package/dist/esm/services/project.service.mjs +1 -1
- package/dist/esm/services/project.service.mjs.map +1 -1
- package/dist/esm/services/projectAccessKey.service.mjs +1 -1
- 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/tag.service.mjs +1 -1
- package/dist/esm/services/tag.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 +12966 -1
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/interest_of_intlayer.json +1 -14957
- package/dist/esm/utils/AI/auditDictionaryField/index.mjs +9 -0
- package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
- package/dist/esm/utils/AI/getProjectAIOptions.mjs +20 -0
- package/dist/esm/utils/AI/getProjectAIOptions.mjs.map +1 -0
- package/dist/esm/utils/auth/getAuth.mjs +34 -8
- package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
- package/dist/esm/utils/errors/ErrorHandler.mjs +40 -8
- package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
- package/dist/esm/utils/mapper/project.mjs +7 -1
- package/dist/esm/utils/mapper/project.mjs.map +1 -1
- package/dist/esm/utils/mongoDB/connectDB.mjs +12 -12
- package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
- package/dist/types/controllers/ai.controller.d.ts.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/controllers/showcaseProject.controller.d.ts.map +1 -1
- package/dist/types/controllers/translation.controller.d.ts.map +1 -1
- package/dist/types/controllers/user.controller.d.ts.map +1 -1
- package/dist/types/schemas/account.schema.d.ts +3 -2
- package/dist/types/schemas/account.schema.d.ts.map +1 -1
- package/dist/types/schemas/audit.schema.d.ts +64 -0
- package/dist/types/schemas/audit.schema.d.ts.map +1 -0
- package/dist/types/schemas/auditJob.schema.d.ts +122 -0
- package/dist/types/schemas/auditJob.schema.d.ts.map +1 -0
- package/dist/types/schemas/auditPage.schema.d.ts +120 -0
- package/dist/types/schemas/auditPage.schema.d.ts.map +1 -0
- package/dist/types/schemas/cliSessionToken.schema.d.ts +10 -3
- package/dist/types/schemas/cliSessionToken.schema.d.ts.map +1 -1
- package/dist/types/schemas/dictionary.schema.d.ts +21 -12
- package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
- package/dist/types/schemas/discussion.schema.d.ts +16 -11
- package/dist/types/schemas/discussion.schema.d.ts.map +1 -1
- package/dist/types/schemas/oAuth2.schema.d.ts +13 -3
- package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
- package/dist/types/schemas/organization.schema.d.ts +8 -7
- package/dist/types/schemas/organization.schema.d.ts.map +1 -1
- package/dist/types/schemas/plans.schema.d.ts +8 -8
- package/dist/types/schemas/project.schema.d.ts +11 -10
- package/dist/types/schemas/project.schema.d.ts.map +1 -1
- package/dist/types/schemas/session.schema.d.ts +11 -10
- package/dist/types/schemas/session.schema.d.ts.map +1 -1
- package/dist/types/schemas/showcaseProject.schema.d.ts +18 -17
- package/dist/types/schemas/showcaseProject.schema.d.ts.map +1 -1
- package/dist/types/schemas/tag.schema.d.ts +10 -9
- package/dist/types/schemas/tag.schema.d.ts.map +1 -1
- package/dist/types/schemas/user.schema.d.ts +32 -9
- package/dist/types/schemas/user.schema.d.ts.map +1 -1
- package/dist/types/services/audit/recursiveAudit.service.d.ts +2 -2
- package/dist/types/types/project.types.d.ts +2 -1
- package/dist/types/types/project.types.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/AI/getProjectAIOptions.d.ts +15 -0
- package/dist/types/utils/AI/getProjectAIOptions.d.ts.map +1 -0
- package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
- package/dist/types/utils/errors/ErrorHandler.d.ts +10 -6
- package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +3 -3
- package/dist/types/utils/mapper/project.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/esm/models/account.model.mjs +0 -9
- package/dist/esm/models/account.model.mjs.map +0 -1
- package/dist/esm/models/audit.model.mjs.map +0 -1
- package/dist/esm/models/auditJob.model.mjs.map +0 -1
- package/dist/esm/models/auditPage.model.mjs.map +0 -1
- package/dist/esm/models/cliSessionToken.model.mjs +0 -9
- package/dist/esm/models/cliSessionToken.model.mjs.map +0 -1
- package/dist/esm/models/dictionary.model.mjs +0 -9
- package/dist/esm/models/dictionary.model.mjs.map +0 -1
- package/dist/esm/models/discussion.model.mjs +0 -9
- package/dist/esm/models/discussion.model.mjs.map +0 -1
- package/dist/esm/models/oAuth2.model.mjs +0 -9
- package/dist/esm/models/oAuth2.model.mjs.map +0 -1
- package/dist/esm/models/organization.model.mjs +0 -9
- package/dist/esm/models/organization.model.mjs.map +0 -1
- package/dist/esm/models/project.model.mjs +0 -9
- package/dist/esm/models/project.model.mjs.map +0 -1
- package/dist/esm/models/session.model.mjs +0 -9
- package/dist/esm/models/session.model.mjs.map +0 -1
- package/dist/esm/models/showcaseProject.model.mjs +0 -9
- package/dist/esm/models/showcaseProject.model.mjs.map +0 -1
- package/dist/esm/models/tag.model.mjs +0 -9
- package/dist/esm/models/tag.model.mjs.map +0 -1
- package/dist/esm/models/user.model.mjs +0 -9
- package/dist/esm/models/user.model.mjs.map +0 -1
- package/dist/types/models/account.model.d.ts +0 -7
- package/dist/types/models/account.model.d.ts.map +0 -1
- package/dist/types/models/audit.model.d.ts +0 -18
- package/dist/types/models/audit.model.d.ts.map +0 -1
- package/dist/types/models/auditJob.model.d.ts +0 -31
- package/dist/types/models/auditJob.model.d.ts.map +0 -1
- package/dist/types/models/auditPage.model.d.ts +0 -29
- package/dist/types/models/auditPage.model.d.ts.map +0 -1
- package/dist/types/models/cliSessionToken.model.d.ts +0 -14
- package/dist/types/models/cliSessionToken.model.d.ts.map +0 -1
- package/dist/types/models/dictionary.model.d.ts +0 -16
- package/dist/types/models/dictionary.model.d.ts.map +0 -1
- package/dist/types/models/discussion.model.d.ts +0 -12
- package/dist/types/models/discussion.model.d.ts.map +0 -1
- package/dist/types/models/oAuth2.model.d.ts +0 -18
- package/dist/types/models/oAuth2.model.d.ts.map +0 -1
- package/dist/types/models/organization.model.d.ts +0 -7
- package/dist/types/models/organization.model.d.ts.map +0 -1
- package/dist/types/models/project.model.d.ts +0 -7
- package/dist/types/models/project.model.d.ts.map +0 -1
- package/dist/types/models/session.model.d.ts +0 -7
- package/dist/types/models/session.model.d.ts.map +0 -1
- package/dist/types/models/showcaseProject.model.d.ts +0 -7
- package/dist/types/models/showcaseProject.model.d.ts.map +0 -1
- package/dist/types/models/tag.model.d.ts +0 -7
- package/dist/types/models/tag.model.d.ts.map +0 -1
- package/dist/types/models/user.model.d.ts +0 -7
- package/dist/types/models/user.model.d.ts.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from "../logger/index.mjs";
|
|
2
|
+
import { DiscussionModel } from "../schemas/discussion.schema.mjs";
|
|
2
3
|
import { formatPaginatedResponse, formatResponse } from "../utils/responseData.mjs";
|
|
3
4
|
import { ErrorHandler } from "../utils/errors/ErrorHandler.mjs";
|
|
4
5
|
import { getDictionariesByTags } from "../services/dictionary.service.mjs";
|
|
@@ -12,16 +13,16 @@ import { aiDefaultOptions as aiDefaultOptions$4, autocomplete as autocomplete$1
|
|
|
12
13
|
import { chat as chat$1 } from "../utils/AI/chat/index.mjs";
|
|
13
14
|
import { createSessionTools } from "../utils/AI/chat/sessionTools.mjs";
|
|
14
15
|
import { aiDefaultOptions as aiDefaultOptions$5, customQuery as customQuery$2 } from "../utils/AI/customQuery/index.mjs";
|
|
16
|
+
import { getProjectAIOptions } from "../utils/AI/getProjectAIOptions.mjs";
|
|
15
17
|
import { aiDefaultOptions as aiDefaultOptions$6, translateJSON as translateJSON$2 } from "../utils/AI/translateJSON/index.mjs";
|
|
16
18
|
import { getDiscussionFiltersAndPagination } from "../utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs";
|
|
17
|
-
import { DiscussionModel } from "../models/discussion.model.mjs";
|
|
18
19
|
import { getAIConfig } from "@intlayer/ai";
|
|
19
20
|
|
|
20
21
|
//#region src/controllers/ai.controller.ts
|
|
21
22
|
const customQuery = async (request, reply) => {
|
|
22
23
|
const { aiOptions, tagsKeys, ...rest } = request.body;
|
|
23
24
|
const { user, project } = request.session || {};
|
|
24
|
-
const projectAIOptions =
|
|
25
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
25
26
|
let aiConfig;
|
|
26
27
|
try {
|
|
27
28
|
aiConfig = await getAIConfig({
|
|
@@ -48,7 +49,7 @@ const customQuery = async (request, reply) => {
|
|
|
48
49
|
const translateJSON = async (request, reply) => {
|
|
49
50
|
const { project, user } = request.session || {};
|
|
50
51
|
const { aiOptions, tagsKeys, ...rest } = request.body;
|
|
51
|
-
const projectAIOptions =
|
|
52
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
52
53
|
let aiConfig;
|
|
53
54
|
try {
|
|
54
55
|
aiConfig = await getAIConfig({
|
|
@@ -82,7 +83,7 @@ const translateJSON = async (request, reply) => {
|
|
|
82
83
|
const auditContentDeclaration = async (request, reply) => {
|
|
83
84
|
const { project, user } = request.session || {};
|
|
84
85
|
const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } = request.body;
|
|
85
|
-
const projectAIOptions =
|
|
86
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
86
87
|
let aiConfig;
|
|
87
88
|
try {
|
|
88
89
|
aiConfig = await getAIConfig({
|
|
@@ -119,7 +120,7 @@ const auditContentDeclaration = async (request, reply) => {
|
|
|
119
120
|
const auditContentDeclarationField = async (request, reply) => {
|
|
120
121
|
const { project, user } = request.session || {};
|
|
121
122
|
const { fileContent, aiOptions, locales, tagsKeys, keyPath } = request.body;
|
|
122
|
-
const projectAIOptions =
|
|
123
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
123
124
|
let aiConfig;
|
|
124
125
|
try {
|
|
125
126
|
aiConfig = await getAIConfig({
|
|
@@ -186,7 +187,7 @@ const auditContentDeclarationMetadata = async (request, reply) => {
|
|
|
186
187
|
const auditTag = async (request, reply) => {
|
|
187
188
|
const { project, user } = request.session || {};
|
|
188
189
|
const { aiOptions, tag } = request.body;
|
|
189
|
-
const projectAIOptions =
|
|
190
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
190
191
|
let aiConfig;
|
|
191
192
|
try {
|
|
192
193
|
aiConfig = await getAIConfig({
|
|
@@ -220,7 +221,7 @@ const askDocQuestion = async (request, reply) => {
|
|
|
220
221
|
reply.hijack();
|
|
221
222
|
const headers = reply.getHeaders();
|
|
222
223
|
for (const [key, value] of Object.entries(headers)) if (value !== void 0) reply.raw.setHeader(key, value);
|
|
223
|
-
const projectAIOptions =
|
|
224
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
224
225
|
let aiConfig;
|
|
225
226
|
try {
|
|
226
227
|
try {
|
|
@@ -304,7 +305,7 @@ const chat = async (request, reply) => {
|
|
|
304
305
|
reply.hijack();
|
|
305
306
|
const headers = reply.getHeaders();
|
|
306
307
|
for (const [key, value] of Object.entries(headers)) if (value !== void 0) reply.raw.setHeader(key, value);
|
|
307
|
-
const projectAIOptions =
|
|
308
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
308
309
|
try {
|
|
309
310
|
let aiConfig;
|
|
310
311
|
try {
|
|
@@ -396,7 +397,7 @@ const chat = async (request, reply) => {
|
|
|
396
397
|
};
|
|
397
398
|
const autocomplete = async (request, reply) => {
|
|
398
399
|
const { user, project } = request.session || {};
|
|
399
|
-
const projectAIOptions =
|
|
400
|
+
const projectAIOptions = await getProjectAIOptions(project);
|
|
400
401
|
try {
|
|
401
402
|
const { text, aiOptions, contextBefore, currentLine, contextAfter } = request.body;
|
|
402
403
|
let aiConfig;
|
|
@@ -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: 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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"}
|
|
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 { DiscussionModel } from '@schemas/discussion.schema';\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 { getProjectAIOptions } from '@utils/AI/getProjectAIOptions';\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 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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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 = await getProjectAIOptions(project);\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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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from "../logger/index.mjs";
|
|
2
|
-
import { AuditModel } from "../
|
|
2
|
+
import { AuditModel } from "../schemas/audit.schema.mjs";
|
|
3
3
|
import { mutateScore } from "../services/audit/analysis/calculateScore.mjs";
|
|
4
4
|
import { runSingleAudit } from "../services/audit/seoAudit.service.mjs";
|
|
5
5
|
import { lookup } from "node:dns/promises";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.controller.mjs","names":["dnsLookup"],"sources":["../../../src/controllers/audit.controller.ts"],"sourcesContent":["import { lookup as dnsLookup } from 'node:dns/promises';\nimport net from 'node:net';\nimport { logger } from '@logger';\nimport { AuditModel } from '@
|
|
1
|
+
{"version":3,"file":"audit.controller.mjs","names":["dnsLookup"],"sources":["../../../src/controllers/audit.controller.ts"],"sourcesContent":["import { lookup as dnsLookup } from 'node:dns/promises';\nimport net from 'node:net';\nimport { logger } from '@logger';\nimport { AuditModel } from '@schemas/audit.schema';\nimport {\n mutateScore,\n type Score,\n} from '@services/audit/analysis/calculateScore';\nimport { runSingleAudit } from '@services/audit/seoAudit.service';\nimport type { AuditEvent } from '@services/audit/types';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\n/**\n * Returns true if the given IP address is private, loopback, or link-local.\n * Covers IPv4 and IPv6.\n */\nconst isPrivateOrReservedIp = (ip: string): boolean => {\n if (net.isIPv4(ip)) {\n const parts = ip.split('.').map(Number);\n const [a, b] = parts;\n // Loopback: 127.0.0.0/8\n if (a === 127) return true;\n // Any address starting with 0\n if (a === 0) return true;\n // Private: 10.0.0.0/8\n if (a === 10) return true;\n // Private: 172.16.0.0/12\n if (a === 172 && b >= 16 && b <= 31) return true;\n // Private: 192.168.0.0/16\n if (a === 192 && b === 168) return true;\n // Link-local: 169.254.0.0/16\n if (a === 169 && b === 254) return true;\n return false;\n }\n\n if (net.isIPv6(ip)) {\n const normalized = ip.toLowerCase();\n // Loopback: ::1\n if (normalized === '::1') return true;\n // Link-local: fe80::/10\n if (\n normalized.startsWith('fe80:') ||\n normalized.startsWith('fe8') ||\n normalized.startsWith('fe9') ||\n normalized.startsWith('fea') ||\n normalized.startsWith('feb')\n )\n return true;\n // IPv4-mapped: ::ffff:x.x.x.x — check the embedded IPv4\n const ipv4MappedMatch = normalized.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (ipv4MappedMatch) return isPrivateOrReservedIp(ipv4MappedMatch[1]);\n return false;\n }\n\n // Unknown format — block it\n return true;\n};\n\nconst sendSSE = (res: FastifyReply, data: AuditEvent) => {\n res.raw.write(`data: ${JSON.stringify(data)}\\n\\n`);\n};\n\n/**\n * GET /api/scan?url=<targetUrl>\n * Streams audit results as Server-Sent Events.\n */\nexport const auditGetHandler = async (\n req: FastifyRequest,\n res: FastifyReply\n) => {\n res.hijack();\n\n const headers = res.getHeaders();\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n res.raw.setHeader(key, value as string | number | readonly string[]);\n }\n }\n\n res.raw.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n res.raw.setHeader('Connection', 'keep-alive');\n res.raw.setHeader('X-Accel-Buffering', 'no');\n\n if ((res.raw as any).flushHeaders) {\n (res.raw as any).flushHeaders();\n }\n\n res.raw.write(': connected\\n\\n');\n\n const { url: targetUrl } = req.query as { url?: string };\n\n let score: Score = { score: 0, totalScore: 0 };\n let currentProgress = 0;\n\n if (!targetUrl) {\n sendSSE(res, { status: 'error', globalError: 'Missing URL parameter' });\n res.raw.end();\n return;\n }\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(targetUrl);\n } catch {\n sendSSE(res, { status: 'error', globalError: 'Invalid URL format' });\n res.raw.end();\n return;\n }\n\n // Only allow http: and https: — block file://, data:, ftp://, etc.\n if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n sendSSE(res, {\n status: 'error',\n globalError: 'Only http and https URLs are allowed',\n });\n res.raw.end();\n return;\n }\n\n // Resolve the hostname and block private / loopback / link-local addresses (SSRF prevention)\n try {\n const { address } = await dnsLookup(parsedUrl.hostname);\n if (isPrivateOrReservedIp(address)) {\n sendSSE(res, {\n status: 'error',\n globalError: 'URL resolves to a private or reserved address',\n });\n res.raw.end();\n return;\n }\n } catch {\n sendSSE(res, {\n status: 'error',\n globalError: 'Could not resolve hostname',\n });\n res.raw.end();\n return;\n }\n\n let aborted = false;\n\n req.raw.on('close', () => {\n logger.info('Client connection closed');\n aborted = true;\n });\n\n try {\n await runSingleAudit(targetUrl, (event) => {\n score = mutateScore(score, event);\n\n if (event.progress !== undefined) {\n currentProgress = event.progress;\n }\n\n if (!aborted) {\n sendSSE(res, {\n ...event,\n progress: currentProgress,\n score: Math.round(\n score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0\n ),\n });\n }\n });\n\n try {\n const url = new URL(targetUrl);\n const domain = url.hostname;\n const finalScore = Math.round(\n score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0\n );\n\n const audit = new AuditModel({ domain, score: finalScore });\n await audit.save();\n logger.info(\n `Audit saved for domain: ${domain} with score: ${finalScore}`\n );\n } catch (dbError) {\n logger.error('Failed to save audit to database:', dbError);\n }\n\n res.raw.end();\n } catch (error) {\n logger.error('Audit GET error:', error);\n if (!aborted) {\n sendSSE(res, {\n globalError:\n error instanceof Error ? error.message : 'Internal server error',\n });\n res.raw.end();\n }\n }\n};\n"],"mappings":"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"}
|
|
@@ -3,9 +3,9 @@ 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
|
-
import { SessionModel } from "../
|
|
8
|
+
import { SessionModel } from "../schemas/session.schema.mjs";
|
|
9
9
|
import { sendEmail } from "../services/email.service.mjs";
|
|
10
10
|
import { getOrganizationFiltersAndPagination } from "../utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs";
|
|
11
11
|
import { mapOrganizationToAPI, mapOrganizationsToAPI } from "../utils/mapper/organization.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",
|