@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.
Files changed (45) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10934 -8925
  2. package/dist/esm/controllers/ai.controller.mjs +2 -2
  3. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  4. package/dist/esm/controllers/organization.controller.mjs +16 -4
  5. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  6. package/dist/esm/controllers/project.controller.mjs +10 -3
  7. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  8. package/dist/esm/schemas/user.schema.mjs +8 -0
  9. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  10. package/dist/esm/services/audit/recursiveAudit.service.mjs +1 -1
  11. package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
  12. package/dist/esm/services/cliSessionToken.service.mjs +2 -2
  13. package/dist/esm/services/cliSessionToken.service.mjs.map +1 -1
  14. package/dist/esm/services/dictionary.service.mjs +1 -1
  15. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  16. package/dist/esm/services/oAuth2.service.mjs +3 -3
  17. package/dist/esm/services/oAuth2.service.mjs.map +1 -1
  18. package/dist/esm/services/projectAccessKey.service.mjs +2 -2
  19. package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
  20. package/dist/esm/services/showcase/showcaseProject.service.mjs +1 -1
  21. package/dist/esm/services/showcase/showcaseProject.service.mjs.map +1 -1
  22. package/dist/esm/services/user.service.mjs +1 -1
  23. package/dist/esm/services/user.service.mjs.map +1 -1
  24. package/dist/esm/types/user.types.mjs.map +1 -1
  25. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10934 -8925
  26. package/dist/esm/utils/auth/getAuth.mjs +34 -8
  27. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  28. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  29. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  30. package/dist/types/schemas/dictionary.schema.d.ts +8 -8
  31. package/dist/types/schemas/discussion.schema.d.ts +3 -3
  32. package/dist/types/schemas/organization.schema.d.ts +7 -7
  33. package/dist/types/schemas/plans.schema.d.ts +8 -8
  34. package/dist/types/schemas/project.schema.d.ts +7 -7
  35. package/dist/types/schemas/showcaseProject.schema.d.ts +17 -17
  36. package/dist/types/schemas/tag.schema.d.ts +8 -8
  37. package/dist/types/schemas/user.schema.d.ts +30 -8
  38. package/dist/types/services/showcase/showcaseProject.service.d.ts.map +1 -1
  39. package/dist/types/types/user.types.d.ts +2 -0
  40. package/dist/types/types/user.types.d.ts.map +1 -1
  41. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  42. package/dist/types/utils/errors/ErrorHandler.d.ts +8 -4
  43. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  44. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +4 -4
  45. package/package.json +14 -14
@@ -1 +1 @@
1
- {"version":3,"file":"project.controller.mjs","names":["projectService.findProjects","projectService.countProjects","projectService.createProject","projectService.getProjectById","projectService.updateProjectById","userService.getUsersByIds","webhooksService.triggerAll","webhooksService.triggerSingleWebhook","projectService.deleteProjectById","ciService.getCIStatus","ciService.installCI"],"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { SessionModel } from '@models/session.model';\nimport * as ciService from '@services/ci.service';\nimport { createDemoDictionaries } from '@services/dictionary.service';\nimport { refreshProjectScreenshotIfChanged } from '@services/project/projectScreenshot.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport * as webhooksService from '@services/webhook.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getProjectFiltersAndPagination,\n type ProjectFiltersParams,\n} from '@utils/filtersAndPagination/getProjectFiltersAndPagination';\nimport { mapProjectsToAPI, mapProjectToAPI } from '@utils/mapper/project';\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 type {\n ProjectAPI,\n ProjectConfiguration,\n ProjectCreationData,\n ProjectData,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\n\nexport type GetProjectsParams = FiltersAndPagination<ProjectFiltersParams>;\nexport type GetProjectsResult = PaginatedResponse<ProjectAPI>;\n\n/**\n * Retrieves a list of projects based on filters and pagination.\n */\nexport const getProjects = async (\n request: FastifyRequest<{ Querystring: GetProjectsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.session || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n // When no organization is selected in the session,\n // the filter already scopes organizationId to undefined → DB returns []\n // so we can proceed safely without an early 403.\n\n try {\n const projects = await projectService.findProjects(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n // Skip permission check when there are no projects to protect.\n // An empty result is safe to return without checking roles.\n if (\n projects.length > 0 &&\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.session,\n targetProjects: projects,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await projectService.countProjects(filters);\n\n const formattedProjects = mapProjectsToAPI(projects);\n\n const responseData = formatPaginatedResponse<ProjectAPI>({\n data: formattedProjects,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddProjectBody = ProjectCreationData;\nexport type AddProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Adds a new project to the database.\n */\nexport const addProject = async (\n request: FastifyRequest<{ Body: AddProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.session || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!projectData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\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 const { plan } = organization;\n\n const planType = getPlanDetails(plan);\n\n if (planType.numberOfProjects) {\n const projectCount = await projectService.countProjects({\n organizationId: organization.id,\n });\n\n if (projectCount >= planType.numberOfProjects) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_PROJECT_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n }\n }\n\n const project: ProjectData = {\n membersIds: [user.id],\n adminsIds: [user.id],\n creatorId: user.id,\n organizationId: organization.id,\n ...projectData,\n };\n\n try {\n const newProject = await projectService.createProject(project);\n\n const formattedProject = mapProjectToAPI(newProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project created successfully',\n 'en-GB': 'Project created successfully',\n fr: 'Projet créé avec succès',\n es: 'Proyecto creado con éxito',\n ru: 'Проект успешно создан',\n ja: 'プロジェクトが正常に作成されました',\n ko: '프로젝트가 성공적으로 생성되었습니다',\n zh: '项目已成功创建',\n de: 'Projekt erfolgreich erstellt',\n ar: 'تم إنشاء المشروع بنجاح',\n it: 'Progetto creato con successo',\n pt: 'Projeto criado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक बनाया गया',\n tr: 'Proje başarıyla oluşturuldu',\n pl: 'Projekt został pomyślnie utworzony',\n id: 'Proyek berhasil dibuat',\n vi: 'Dự án đã được tạo thành công',\n uk: 'Проєкт успішно створено',\n }),\n description: t({\n en: 'Your project has been created successfully',\n 'en-GB': 'Your project has been created successfully',\n fr: 'Votre projet a été créé avec succès',\n es: 'Su proyecto ha sido creado con éxito',\n ru: 'Ваш проект был успешно создан',\n ja: 'プロジェクトは正常に作成されました',\n ko: '프로젝트가 성공적으로 생성되었습니다',\n zh: '您的项目已成功创建',\n de: 'Ihr Projekt wurde erfolgreich erstellt',\n ar: 'لقد تم إنشاء مشروعك بنجاح',\n it: 'Il tuo progetto è stato creato con successo',\n pt: 'Seu projeto foi criado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक बना लिया गया है',\n tr: 'Projeniz başarıyla oluşturuldu',\n pl: 'Twój projekt został pomyślnie utworzony',\n id: 'Proyek Anda telah berhasil dibuat',\n vi: 'Dự án của bạn đã được tạo thành công',\n uk: 'Ваш проєкт успішно створено',\n }),\n data: formattedProject,\n });\n\n reply.send(responseData);\n\n // Create mock data once it's done\n try {\n await createDemoDictionaries([formattedProject.id], user.id);\n } catch {\n // Skip if fail\n }\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateProjectBody = Partial<ProjectAPI>;\nexport type UpdateProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Updates an existing project in the database.\n */\nexport const updateProject = async (\n request: FastifyRequest<{ Body: UpdateProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, project, user, session, roles } = request.session || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (String(project.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\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 const existingProject = await projectService.getProjectById(project.id);\n\n const previousApplicationUrl =\n existingProject.configuration?.editor?.applicationURL;\n const newApplicationUrl = projectData.configuration?.editor?.applicationURL;\n\n const updatedProject = await projectService.updateProjectById(\n project.id,\n projectData\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: String(project.id),\n },\n }\n );\n\n // Fire-and-forget screenshot generation\n refreshProjectScreenshotIfChanged({\n newApplicationUrl,\n previousApplicationUrl,\n existingImageUrl: existingProject.imageUrl,\n projectId: project.id.toString(),\n })\n .then((imageUrl) => {\n if (imageUrl !== undefined) {\n projectService\n .updateProjectById(project.id, { imageUrl })\n .catch(() => {});\n }\n })\n .catch(() => {});\n\n const formattedProject = mapProjectToAPI(updatedProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project updated successfully',\n 'en-GB': 'Project updated successfully',\n fr: 'Projet mis à jour avec succès',\n es: 'Proyecto actualizado con éxito',\n ru: 'Проект успешно обновлен',\n ja: 'プロジェクトが正常に更新されました',\n ko: '프로젝트가 성공적으로 업데이트되었습니다',\n zh: '项目已成功更新',\n de: 'Projekt erfolgreich aktualisiert',\n ar: 'تم تحديث المشروع بنجاح',\n it: 'Progetto aggiornato con successo',\n pt: 'Projeto atualizado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक अपडेट किया गया',\n tr: 'Proje başarıyla güncellendi',\n pl: 'Projekt został pomyślnie zaktualizowany',\n id: 'Proyek berhasil diperbarui',\n vi: 'Dự án đã được cập nhật thành công',\n uk: 'Проєкт успішно оновлено',\n }),\n description: t({\n en: 'Your project has been updated successfully',\n 'en-GB': 'Your project has been updated successfully',\n fr: 'Votre projet a été mis à jour avec succès',\n es: 'Su proyecto ha sido actualizado con éxito',\n ru: 'Ваш проект был успешно обновлен',\n ja: 'プロジェクトは正常に更新されました',\n ko: '프로젝트가 성공적으로 업데이트되었습니다',\n zh: '您的项目已成功更新',\n de: 'Ihr Projekt wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث مشروعك بنجاح',\n it: 'Il tuo progetto è stato aggiornato con successo',\n pt: 'Seu projeto foi atualizado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Projeniz başarıyla güncellendi',\n pl: 'Twój projekt został pomyślnie zaktualizowany',\n id: 'Proyek Anda telah berhasil diperbarui',\n vi: 'Dự án của bạn đã được cập nhật thành công',\n uk: 'Ваш проєкт успішно оновлено',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype UserAndAdmin = { user: User; isAdmin: boolean };\nexport type ProjectMemberByIdOption = {\n userId: string | Types.ObjectId;\n isAdmin?: boolean;\n};\n\nexport type UpdateProjectMembersBody = Partial<{\n membersIds: ProjectMemberByIdOption[];\n}>;\nexport type UpdateProjectMembersResult = ResponseData<ProjectAPI>;\n\n/**\n * Update members to the dictionary in the database.\n */\nexport const updateProjectMembers = async (\n request: FastifyRequest<{ Body: UpdateProjectMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, organization, roles } = request.session || {};\n const { membersIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_MEMBER'\n );\n }\n\n if (membersIds?.map((el) => el.isAdmin)?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_ADMIN'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingUsers: UserAndAdmin[] = [];\n\n if (membersIds) {\n const userIdList = membersIds\n ?.filter(\n (member) =>\n // Remove members that are not in the organization\n !organization?.membersIds.includes(member.userId as Types.ObjectId)\n )\n .map((member) => member.userId);\n\n const users = await userService.getUsersByIds(userIdList);\n\n if (users) {\n const userMap: UserAndAdmin[] = users.map((user) => ({\n user,\n isAdmin:\n membersIds.find(\n (member) => String(member.userId) === String(user.id)\n )?.isAdmin ?? false,\n }));\n\n existingUsers.push(...userMap);\n }\n }\n\n const formattedMembers: Types.ObjectId[] = existingUsers.map(\n (user) => user.user.id\n );\n const formattedAdmin: Types.ObjectId[] = existingUsers\n .filter((el) => el.isAdmin)\n .map((user) => user.user.id);\n\n const updatedOrganization = await projectService.updateProjectById(\n project.id,\n {\n ...project,\n membersIds: formattedMembers,\n adminsIds: formattedAdmin,\n }\n );\n\n const formattedProject = mapProjectToAPI(updatedOrganization);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project members updated successfully',\n 'en-GB': 'Project members updated successfully',\n fr: 'Membres du projet mis à jour avec succès',\n es: 'Miembros del proyecto actualizados con éxito',\n ru: 'Члены проекта успешно обновлены',\n ja: 'プロジェクトメンバーが正常に更新されました',\n ko: '프로젝트 구성원이 성공적으로 업데이트되었습니다',\n zh: '项目成员已成功更新',\n de: 'Projektmitglieder erfolgreich aktualisiert',\n ar: 'تم تحديث أعضاء المشروع بنجاح',\n it: 'Membri del progetto aggiornati con successo',\n pt: 'Membros do projeto atualizados com sucesso',\n hi: 'प्रोजेक्ट सदस्य सफलतापूर्वक अपडेट किए गए',\n tr: 'Proje üyeleri başarıyla güncellendi',\n pl: 'Członkowie projektu zostali pomyślnie zaktualizowani',\n id: 'Anggota proyek berhasil diperbarui',\n vi: 'Thành viên dự án đã được cập nhật thành công',\n uk: 'Члени проєкту успішно оновлені',\n }),\n description: t({\n en: 'Your project members have been updated successfully',\n 'en-GB': 'Your project members have been updated successfully',\n fr: 'Les membres de votre projet ont été mis à jour avec succès',\n es: 'Los miembros de su proyecto han sido actualizados con éxito',\n ru: 'Члены вашего проекта были успешно обновлены',\n ja: 'プロジェクトメンバーは正常に更新されました',\n ko: '프로젝트 구성원이 성공적으로 업데이트되었습니다',\n zh: '您的项目成员已成功更新',\n de: 'Ihre Projektmitglieder wurden erfolgreich aktualisiert',\n ar: 'لقد تم تحديث أعضاء مشروعك بنجاح',\n it: 'I membri del tuo progetto sono stati aggiornati con successo',\n pt: 'Os membros do seu projeto foram atualizados com sucesso',\n hi: 'आपके प्रोजेक्ट के सदस्यों को सफलतापूर्वक अपडेट किया गया है',\n tr: 'Proje üyeleriniz başarıyla güncellendi',\n pl: 'Członkowie Twojego projektu zostali pomyślnie zaktualizowani',\n id: 'Anggota proyek Anda telah berhasil diperbarui',\n vi: 'Thành viên dự án của bạn đã được cập nhật thành công',\n uk: 'Члени вашого проєкту успішно оновлені',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushProjectConfigurationBody = ProjectConfiguration;\nexport type PushProjectConfigurationResult = ResponseData<ProjectConfiguration>;\n\n/**\n * Pushes a project configuration to the database.\n */\nexport const pushProjectConfiguration = async (\n request: FastifyRequest<{ Body: PushProjectConfigurationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const projectConfiguration = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectObject = await projectService.getProjectById(project.id);\n\n // Preserve existing API key if not provided in the update\n if (projectConfiguration.ai && projectObject.configuration?.ai?.apiKey) {\n projectConfiguration.ai.apiKey = projectObject.configuration.ai.apiKey;\n }\n\n const previousApplicationUrl =\n projectObject.configuration?.editor?.applicationURL;\n const newApplicationUrl = projectConfiguration.editor?.applicationURL;\n\n projectObject.configuration = projectConfiguration;\n\n await projectObject.save();\n\n // Fire-and-forget screenshot generation\n refreshProjectScreenshotIfChanged({\n newApplicationUrl,\n previousApplicationUrl,\n existingImageUrl: projectObject.imageUrl,\n projectId: project.id.toString(),\n })\n .then((imageUrl) => {\n if (imageUrl !== undefined) {\n projectService\n .updateProjectById(project.id, { imageUrl })\n .catch(() => {});\n }\n })\n .catch(() => {});\n\n if (!projectObject.configuration) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_UPDATE_FAILED',\n {\n projectId: project.id,\n }\n );\n }\n\n const responseData = formatResponse<ProjectConfiguration>({\n message: t({\n en: 'Project configuration updated successfully',\n 'en-GB': 'Project configuration updated successfully',\n fr: 'Configuration du projet mise à jour avec succès',\n es: 'Configuración del proyecto actualizada con éxito',\n ru: 'Конфигурация проекта успешно обновлена',\n ja: 'プロジェクト構成が正常に更新されました',\n ko: '프로젝트 설정이 성공적으로 업데이트되었습니다',\n zh: '项目配置已成功更新',\n de: 'Projektkonfiguration erfolgreich aktualisiert',\n ar: 'تم تحديث تكوين المشروع بنجاح',\n it: 'Configurazione del progetto aggiornata con successo',\n pt: 'Configuração do projeto atualizada com sucesso',\n hi: 'प्रोजेक्ट कॉन्फ़िगरेशन सफलतापूर्वक अपडेट किया गया',\n tr: 'Proje yapılandırması başarıyla güncellendi',\n pl: 'Konfiguracja projektu została pomyślnie zaktualizowana',\n id: 'Konfigurasi proyek berhasil diperbarui',\n vi: 'Cấu hình dự án đã được cập nhật thành công',\n uk: 'Конфігурацію проєкту успішно оновлено',\n }),\n description: t({\n en: 'Your project configuration has been updated successfully',\n 'en-GB': 'Your project configuration has been updated successfully',\n fr: 'La configuration du projet a été mise à jour avec succès',\n es: 'Su configuración del proyecto ha sido actualizada con éxito',\n ru: 'Конфигурация вашего проекта была успешно обновлена',\n ja: 'プロジェクト構成は正常に更新されました',\n ko: '프로젝트 설정이 성공적으로 업데이트되었습니다',\n zh: '您的项目配置已成功更新',\n de: 'Ihre Projektkonfiguration wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث تكوين مشروعك بنجاح',\n it: 'La configurazione del tuo progetto è stata aggiornata con successo',\n pt: 'A configuração do seu projeto foi atualizada com sucesso',\n hi: 'आपका प्रोजेक्ट कॉन्फ़िगरेशन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Proje yapılandırmanız başarıyla güncellendi',\n pl: 'Konfiguracja Twojego projektu została pomyślnie zaktualizowana',\n id: 'Konfigurasi proyek Anda telah berhasil diperbarui',\n vi: 'Cấu hình dự án của bạn đã được cập nhật thành công',\n uk: 'Конфігурацію вашого проєкту успішно оновлено',\n }),\n data: mapProjectToAPI(projectObject) as ProjectConfiguration,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TriggerBuildResult = ResponseData<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n}>;\n\nexport type TriggerWebhookBody = {\n webhookIndex: number;\n};\n\nexport type TriggerWebhookResult = ResponseData<{\n target: string;\n success: boolean;\n message?: string;\n}>;\n\n/**\n * Triggers CI builds for a project (Git provider pipelines and webhooks)\n */\nexport const triggerBuild = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const results = await webhooksService.triggerAll(fullProject);\n\n const responseData = formatResponse<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n }>({\n message: t({\n en: 'Build triggers initiated',\n 'en-GB': 'Build triggers initiated',\n fr: 'Déclenchement des builds initié',\n es: 'Inicio de los triggers de build',\n ru: 'Запуск триггеров сборки инициирован',\n ja: 'ビルドトリガーが開始されました',\n ko: '빌드 트리거가 시작되었습니다',\n zh: '构建触发器已启动',\n de: 'Build-Trigger initiiert',\n ar: 'بدء مشغلات البناء',\n it: 'Trigger di build avviati',\n pt: 'Gatilhos de build iniciados',\n hi: 'बिल्ड ट्रिगर शुरू किए गए',\n tr: 'Build tetikleyicileri başlatıldı',\n pl: 'Zainicjowano wyzwalacze budowania',\n id: 'Pemicu build dimulai',\n vi: 'Đã bắt đầu trình kích hoạt bản dựng',\n uk: 'Запуск тригерів збірки ініційовано',\n }),\n description: t({\n en: 'CI pipelines and webhooks have been triggered',\n 'en-GB': 'CI pipelines and webhooks have been triggered',\n fr: 'Les pipelines CI et webhooks ont été déclenchés',\n es: 'Los pipelines CI y webhooks han sido activados',\n ru: 'CI-конвейеры и вебхуки были запущены',\n ja: 'CIパイプラインとウェブフックがトリガーされました',\n ko: 'CI 파이프라인과 웹후크가 트리거되었습니다',\n zh: 'CI 流水线和 Webhook 已触发',\n de: 'CI-Pipelines und Webhooks wurden ausgelöst',\n ar: 'تم تفعيل أنابيب CI والويب هوك',\n it: 'Le pipeline CI e i webhook sono stati attivati',\n pt: 'Pipelines CI e webhooks foram acionados',\n hi: 'CI पाइपलाइन और वेबहुक ट्रिगर किए गए हैं',\n tr: 'CI hatları ve webhooklar tetiklendi',\n pl: 'Potoki CI i webhooki zostały wyzwolone',\n id: 'Pipa CI dan webhook telah dipicu',\n vi: 'Đường ống CI và webhook đã được kích hoạt',\n uk: 'Конвеєри CI та вебхуки були запущені',\n }),\n data: { results },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n/**\n * Triggers a single webhook by index\n */\nexport const triggerWebhook = async (\n request: FastifyRequest<{ Body: TriggerWebhookBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const { webhookIndex } = request.body;\n\n if (typeof webhookIndex !== 'number' || webhookIndex < 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const result = await webhooksService.triggerSingleWebhook(\n fullProject,\n webhookIndex\n );\n\n const responseData = formatResponse<{\n target: string;\n success: boolean;\n message?: string;\n }>({\n message: t({\n en: 'Webhook triggered',\n 'en-GB': 'Webhook triggered',\n fr: 'Webhook déclenché',\n es: 'Webhook activado',\n ru: 'Вебхук запущен',\n ja: 'ウェブフックがトリガーされました',\n ko: '웹후크가 트리거되었습니다',\n zh: 'Webhook 已触发',\n de: 'Webhook ausgelöst',\n ar: 'تم تفعيل الويب هوك',\n it: 'Webhook attivato',\n pt: 'Webhook acionado',\n hi: 'वेबहुक ट्रिगर किया गया',\n tr: 'Webhook tetiklendi',\n pl: 'Webhook został wyzwolony',\n id: 'Webhook dipicu',\n vi: 'Webhook đã được kích hoạt',\n uk: 'Вебхук запущено',\n }),\n description: t({\n en: `Webhook \"${result.target}\" has been triggered`,\n 'en-GB': `Webhook \"${result.target}\" has been triggered`,\n fr: `Le webhook \"${result.target}\" a été déclenché`,\n es: `El webhook \"${result.target}\" ha sido activado`,\n ru: `Вебхук \"${result.target}\" был запущен`,\n ja: `ウェブフック \"${result.target}\" がトリガーされました`,\n ko: `웹후크 \"${result.target}\"가 트리거되었습니다`,\n zh: `Webhook \"${result.target}\" 已触发`,\n de: `Webhook \"${result.target}\" wurde ausgelöst`,\n ar: `تم تفعيل الويب هوك \"${result.target}\"`,\n it: `Il webhook \"${result.target}\" è stato attivato`,\n pt: `O webhook \"${result.target}\" foi acionado`,\n hi: `वेबहुक \"${result.target}\" ट्रिगर किया गया है`,\n tr: `\"${result.target}\" webhooku tetiklendi`,\n pl: `Webhook \"${result.target}\" został wyzwolony`,\n id: `Webhook \"${result.target}\" telah dipicu`,\n vi: `Webhook \"${result.target}\" đã được kích hoạt`,\n uk: `Вебхук \"${result.target}\" запущено`,\n }),\n data: result,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Deletes a project from the database by its ID.\n */\nexport const deleteProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, project, session, roles } =\n _request.session || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:admin'\n )({\n ..._request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectToDelete = await projectService.getProjectById(project.id);\n\n if (String(projectToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED',\n {\n projectId: project.id,\n }\n );\n }\n\n logger.info(`Project deleted: ${String(deletedProject.id)}`);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project deleted successfully',\n 'en-GB': 'Project deleted successfully',\n fr: 'Projet supprimé avec succès',\n es: 'Proyecto eliminado con éxito',\n ru: 'Проект успешно удален',\n ja: 'プロジェクトが正常に削除されました',\n ko: '프로젝트가 성공적으로 삭제되었습니다',\n zh: '项目已成功删除',\n de: 'Projekt erfolgreich gelöscht',\n ar: 'تم حذف المشروع بنجاح',\n it: 'Progetto eliminato con successo',\n pt: 'Projeto excluído com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक हटा दिया गया',\n tr: 'Proje başarıyla silindi',\n pl: 'Projekt został pomyślnie usunięty',\n id: 'Proyek berhasil dihapus',\n vi: 'Dự án đã được xóa thành công',\n uk: 'Проєкт успішно видалено',\n }),\n description: t({\n en: 'Your project has been deleted successfully',\n 'en-GB': 'Your project has been deleted successfully',\n fr: 'Votre projet a été supprimé avec succès',\n es: 'Su proyecto ha sido eliminado con éxito',\n ru: 'Ваш проект был успешно удален',\n ja: 'プロジェクトは正常に削除されました',\n ko: '프로젝트가 성공적으로 삭제되었습니다',\n zh: '您的项目已成功删除',\n de: 'Ihr Projekt wurde erfolgreich gelöscht',\n ar: 'لقد تم حذف مشروعك بنجاح',\n it: 'Il tuo progetto è stato eliminato con successo',\n pt: 'Seu projeto foi excluído com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक हटा दिया गया है',\n tr: 'Projeniz başarıyla silindi',\n pl: 'Twój projekt został pomyślnie usunięty',\n id: 'Proyek Anda telah berhasil dihapus',\n vi: 'Dự án của bạn đã được xóa thành công',\n uk: 'Ваш проєкт успішно видалено',\n }),\n data: mapProjectToAPI(deletedProject),\n });\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteProjectByIdAdminParams = { projectId: string };\nexport type DeleteProjectByIdAdminResult = ResponseData<ProjectAPI>;\n\n/**\n * Admin-only: Deletes any project from the database by its ID.\n */\nexport const deleteProjectByIdAdmin = async (\n request: FastifyRequest<{ Params: DeleteProjectByIdAdminParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { projectId } = request.params;\n const { roles } = request.session || {};\n\n if (!hasPermission(roles || [], 'project:admin')({ ...request.session })) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const project = await projectService.getProjectById(projectId);\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n const formattedProject = mapProjectToAPI(deletedProject);\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project deleted',\n fr: 'Projet supprimé',\n es: 'Proyecto eliminado',\n 'en-GB': 'Project deleted',\n de: 'Projekt gelöscht',\n ja: 'プロジェクトが削除されました',\n ko: '프로젝트가 삭제되었습니다',\n zh: '项目已删除',\n it: 'Progetto eliminato',\n pt: 'Projeto excluído',\n hi: 'परियोजना हटा दी गई',\n ar: 'تم حذف المشروع',\n ru: 'Проект удален',\n tr: 'Proje silindi',\n pl: 'Projekt usunięty',\n id: 'Proyek dihapus',\n vi: 'Dự án đã bị xóa',\n uk: 'Проєкт видалено',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type SelectProjectParam = { projectId: string | Types.ObjectId };\nexport type SelectProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Select a project.\n */\nexport const selectProject = async (\n request: FastifyRequest<{ Params: SelectProjectParam }>,\n reply: FastifyReply\n) => {\n const { projectId } = request.params;\n const { session, roles } = request.session || {};\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_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 project = await projectService.getProjectById(projectId);\n\n if (\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.session,\n targetProjects: [project],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: String(projectId) } }\n );\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project selected successfully',\n 'en-GB': 'Project selected successfully',\n fr: 'Projet sélectionné avec succès',\n es: 'Proyecto seleccionado con éxito',\n ru: 'Проект успешно выбран',\n ja: 'プロジェクトが正常に選択されました',\n ko: '프로젝트가 성공적으로 선택되었습니다',\n zh: '项目已成功选择',\n de: 'Projekt erfolgreich ausgewählt',\n ar: 'تم اختيار المشروع بنجاح',\n it: 'Progetto selezionato con successo',\n pt: 'Projeto selecionado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक चुना गया',\n tr: 'Proje başarıyla seçildi',\n pl: 'Projekt został pomyślnie wybrany',\n id: 'Proyek berhasil dipilih',\n vi: 'Dự án đã được chọn thành công',\n uk: 'Проєкт успішно вибрано',\n }),\n description: t({\n en: 'Your project has been selected successfully',\n 'en-GB': 'Your project has been selected successfully',\n fr: 'Votre projet a été sélectionné avec succès',\n es: 'Su proyecto ha sido seleccionado con éxito',\n ru: 'Ваш проект был успешно выбран',\n ja: 'プロジェクトは正常に選択されました',\n ko: '프로젝트가 성공적으로 선택되었습니다',\n zh: '您的项目已成功选择',\n de: 'Ihr Projekt wurde erfolgreich ausgewählt',\n ar: 'لقد تم اختيار مشروعك بنجاح',\n it: 'Il tuo progetto è stato selezionato con successo',\n pt: 'Seu projeto foi selecionado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक चुन लिया गया है',\n tr: 'Projeniz başarıyla seçildi',\n pl: 'Twój projekt został pomyślnie wybrany',\n id: 'Proyek Anda telah berhasil dipilih',\n vi: 'Dự án của bạn đã được chọn thành công',\n uk: 'Ваш проєкт успішно вибрано',\n }),\n data: mapProjectToAPI(project),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectProjectResult = ResponseData<null>;\n\n/**\n * Unselect a project.\n */\nexport const unselectProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { session } = _request.session || {};\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Project unselected successfully',\n 'en-GB': 'Project unselected successfully',\n fr: 'Projet désélectionné avec succès',\n es: 'Proyecto deseleccionado con éxito',\n ru: 'Проект успешно снят с выбора',\n ja: 'プロジェクトの選択が正常に解除されました',\n ko: '프로젝트 선택이 성공적으로 해제되었습니다',\n zh: '项目已成功取消选择',\n de: 'Projekt erfolgreich abgewählt',\n ar: 'تم إلغاء تحديد المشروع بنجاح',\n it: 'Progetto deselezionato con successo',\n pt: 'Projeto desmarcado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक अनसेलेक्ट किया गया',\n tr: 'Proje seçimi başarıyla kaldırıldı',\n pl: 'Wybór projektu został pomyślnie cofnięty',\n id: 'Proyek berhasil batal dipilih',\n vi: 'Dự án đã được bỏ chọn thành công',\n uk: 'Вибір проєкту успішно скасовано',\n }),\n description: t({\n en: 'Your project has been unselected successfully',\n 'en-GB': 'Your project has been unselected successfully',\n fr: 'Votre projet a été désélectionné avec succès',\n es: 'Su proyecto ha sido deseleccionado con éxito',\n ru: 'Выбор вашего проекта был успешно снят',\n ja: 'プロジェクトの選択は正常に解除されました',\n ko: '프로젝트 선택이 성공적으로 해제되었습니다',\n zh: '您的项目已成功取消选择',\n de: 'Ihr Projekt wurde erfolgreich abgewählt',\n ar: 'لقد تم إلغاء تحديد مشروعك بنجاح',\n it: 'Il tuo progetto è stato deselezionato con successo',\n pt: 'Seu projeto foi desmarcado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक अनसेलेक्ट कर दिया गया है',\n tr: 'Projenizin seçimi başarıyla kaldırıldı',\n pl: 'Wybór Twojego projektu został pomyślnie cofnięty',\n id: 'Proyek Anda telah berhasil batal dipilih',\n vi: 'Dự án 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\nexport type GetCIConfigurationResult = ResponseData<ciService.CIStatus>;\n\n/**\n * Get CI configuration status for the current project\n */\nexport const getCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const ciStatus = await ciService.getCIStatus(project, user.id);\n\n const responseData = formatResponse<ciService.CIStatus>({\n data: ciStatus,\n });\n\n return reply.send(responseData);\n } catch (error) {\n if ((error as any)?.isAppError) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n return ErrorHandler.handleCustomErrorResponse(\n reply,\n 'CI_CONFIG_ERROR',\n 'CI Configuration Error',\n (error as Error)?.message ?? 'Failed to get CI configuration',\n undefined,\n 500\n );\n }\n};\n\nexport type PushCIConfigurationResult = ResponseData<{ success: boolean }>;\n\n/**\n * Push CI configuration file to the repository\n */\nexport const pushCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n await ciService.installCI(project, user.id);\n\n const responseData = formatResponse<{ success: boolean }>({\n message: t({\n en: 'CI configuration installed successfully',\n 'en-GB': 'CI configuration installed successfully',\n fr: 'Configuration CI installée avec succès',\n es: 'Configuración CI instalada con éxito',\n ru: 'CI-конфигурация успешно установлена',\n ja: 'CI構成が正常にインストールされました',\n ko: 'CI 설정이 성공적으로 설치되었습니다',\n zh: 'CI 配置已成功安装',\n de: 'CI-Konfiguration erfolgreich installiert',\n ar: 'تم تثبيت تكوين CI بنجاح',\n it: 'Configurazione CI installata con successo',\n pt: 'Configuração CI installada com successo',\n hi: 'CI कॉन्फ़िगरेशन सफलतापूर्वक स्थापित किया गया',\n tr: 'CI yapılandırması başarıyla yüklendi',\n pl: 'Konfiguracja CI została pomyślnie zainstalowana',\n id: 'Konfigurasi CI berhasil diinstal',\n vi: 'Cài đặt cấu hình CI thành công',\n uk: 'Конфігурацію CI успішно встановлено',\n }),\n description: t({\n en: 'The CI workflow file has been added to your repository',\n 'en-GB': 'The CI workflow file has been added to your repository',\n fr: 'Le fichier de workflow CI a été ajouté à votre dépôt',\n es: 'El archivo de flujo de trabajo CI se ha agregado a su repositorio',\n ru: 'Файл рабочего процесса CI был добавлен в ваш репозиторий',\n ja: 'CIワークフローファイルがリポジトリに追加されました',\n ko: 'CI 워크플로 파일이 저장소에 추가되었습니다',\n zh: 'CI 工作流文件已添加到您的存储库中',\n de: 'Die CI-Workflow-Datei wurde Ihrem Repository hinzugefügt',\n ar: 'تمت إضافة ملف سير عمل CI إلى مستودعك',\n it: 'Il file del workflow CI è stato aggiunto al tuo repository',\n pt: 'O arquivo de fluxo de trabalho CI foi adicionado ao seu repositório',\n hi: 'CI वर्कफ़्लो फ़ाइल आपके रिपॉजिटरी में जोड़ दी गई है',\n tr: 'CI iş akışı dosyası deponuza eklendi',\n pl: 'Plik przepływu pracy CI został dodany do Twojego repozytorium',\n id: 'File alur kerja CI telah ditambahkan ke repositori Anda',\n vi: 'Tệp quy trình làm việc CI đã được thêm vào kho lưu trữ của bạn',\n uk: 'Файл робочого процесу CI додано до вашого репозиторію',\n }),\n data: { success: true },\n });\n\n return reply.send(responseData);\n } catch (error) {\n if ((error as any)?.isAppError) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n return ErrorHandler.handleCustomErrorResponse(\n reply,\n 'CI_INSTALL_ERROR',\n 'CI Installation Error',\n (error as Error)?.message ?? 'Failed to install CI configuration',\n undefined,\n 500\n );\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"project.controller.mjs","names":["projectService.findProjects","projectService.countProjects","projectService.createProject","projectService.getProjectById","projectService.updateProjectById","userService.updateUserById","userService.getUsersByIds","webhooksService.triggerAll","webhooksService.triggerSingleWebhook","projectService.deleteProjectById","ciService.getCIStatus","ciService.installCI"],"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { SessionModel } from '@models/session.model';\nimport * as ciService from '@services/ci.service';\nimport { createDemoDictionaries } from '@services/dictionary.service';\nimport { refreshProjectScreenshotIfChanged } from '@services/project/projectScreenshot.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport * as webhooksService from '@services/webhook.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getProjectFiltersAndPagination,\n type ProjectFiltersParams,\n} from '@utils/filtersAndPagination/getProjectFiltersAndPagination';\nimport { mapProjectsToAPI, mapProjectToAPI } from '@utils/mapper/project';\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 type {\n ProjectAPI,\n ProjectConfiguration,\n ProjectCreationData,\n ProjectData,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\n\nexport type GetProjectsParams = FiltersAndPagination<ProjectFiltersParams>;\nexport type GetProjectsResult = PaginatedResponse<ProjectAPI>;\n\n/**\n * Retrieves a list of projects based on filters and pagination.\n */\nexport const getProjects = async (\n request: FastifyRequest<{ Querystring: GetProjectsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.session || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n // When no organization is selected in the session,\n // the filter already scopes organizationId to undefined → DB returns []\n // so we can proceed safely without an early 403.\n\n try {\n const projects = await projectService.findProjects(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n // Skip permission check when there are no projects to protect.\n // An empty result is safe to return without checking roles.\n if (\n projects.length > 0 &&\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.session,\n targetProjects: projects,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await projectService.countProjects(filters);\n\n const formattedProjects = mapProjectsToAPI(projects);\n\n const responseData = formatPaginatedResponse<ProjectAPI>({\n data: formattedProjects,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddProjectBody = ProjectCreationData;\nexport type AddProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Adds a new project to the database.\n */\nexport const addProject = async (\n request: FastifyRequest<{ Body: AddProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.session || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!projectData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\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 const { plan } = organization;\n\n const planType = getPlanDetails(plan);\n\n if (planType.numberOfProjects) {\n const projectCount = await projectService.countProjects({\n organizationId: organization.id,\n });\n\n if (projectCount >= planType.numberOfProjects) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_PROJECT_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n }\n }\n\n const project: ProjectData = {\n membersIds: [user.id],\n adminsIds: [user.id],\n creatorId: user.id,\n organizationId: organization.id,\n ...projectData,\n };\n\n try {\n const newProject = await projectService.createProject(project);\n\n const formattedProject = mapProjectToAPI(newProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project created successfully',\n 'en-GB': 'Project created successfully',\n fr: 'Projet créé avec succès',\n es: 'Proyecto creado con éxito',\n ru: 'Проект успешно создан',\n ja: 'プロジェクトが正常に作成されました',\n ko: '프로젝트가 성공적으로 생성되었습니다',\n zh: '项目已成功创建',\n de: 'Projekt erfolgreich erstellt',\n ar: 'تم إنشاء المشروع بنجاح',\n it: 'Progetto creato con successo',\n pt: 'Projeto criado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक बनाया गया',\n tr: 'Proje başarıyla oluşturuldu',\n pl: 'Projekt został pomyślnie utworzony',\n id: 'Proyek berhasil dibuat',\n vi: 'Dự án đã được tạo thành công',\n uk: 'Проєкт успішно створено',\n }),\n description: t({\n en: 'Your project has been created successfully',\n 'en-GB': 'Your project has been created successfully',\n fr: 'Votre projet a été créé avec succès',\n es: 'Su proyecto ha sido creado con éxito',\n ru: 'Ваш проект был успешно создан',\n ja: 'プロジェクトは正常に作成されました',\n ko: '프로젝트가 성공적으로 생성되었습니다',\n zh: '您的项目已成功创建',\n de: 'Ihr Projekt wurde erfolgreich erstellt',\n ar: 'لقد تم إنشاء مشروعك بنجاح',\n it: 'Il tuo progetto è stato creato con successo',\n pt: 'Seu projeto foi criado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक बना लिया गया है',\n tr: 'Projeniz başarıyla oluşturuldu',\n pl: 'Twój projekt został pomyślnie utworzony',\n id: 'Proyek Anda telah berhasil dibuat',\n vi: 'Dự án của bạn đã được tạo thành công',\n uk: 'Ваш проєкт успішно створено',\n }),\n data: formattedProject,\n });\n\n reply.send(responseData);\n\n // Create mock data once it's done\n try {\n await createDemoDictionaries([formattedProject.id], user.id);\n } catch {\n // Skip if fail\n }\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateProjectBody = Partial<ProjectAPI>;\nexport type UpdateProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Updates an existing project in the database.\n */\nexport const updateProject = async (\n request: FastifyRequest<{ Body: UpdateProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, project, user, session, roles } = request.session || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (String(project.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\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 const existingProject = await projectService.getProjectById(project.id);\n\n const previousApplicationUrl =\n existingProject.configuration?.editor?.applicationURL;\n const newApplicationUrl = projectData.configuration?.editor?.applicationURL;\n\n const updatedProject = await projectService.updateProjectById(\n project.id,\n projectData\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: String(project.id),\n },\n }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveOrganizationId: String(organization.id),\n lastActiveProjectId: String(project.id),\n });\n }\n\n // Fire-and-forget screenshot generation\n refreshProjectScreenshotIfChanged({\n newApplicationUrl,\n previousApplicationUrl,\n existingImageUrl: existingProject.imageUrl,\n projectId: project.id.toString(),\n })\n .then((imageUrl) => {\n if (imageUrl !== undefined) {\n projectService\n .updateProjectById(project.id, { imageUrl })\n .catch(() => {});\n }\n })\n .catch(() => {});\n\n const formattedProject = mapProjectToAPI(updatedProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project updated successfully',\n 'en-GB': 'Project updated successfully',\n fr: 'Projet mis à jour avec succès',\n es: 'Proyecto actualizado con éxito',\n ru: 'Проект успешно обновлен',\n ja: 'プロジェクトが正常に更新されました',\n ko: '프로젝트가 성공적으로 업데이트되었습니다',\n zh: '项目已成功更新',\n de: 'Projekt erfolgreich aktualisiert',\n ar: 'تم تحديث المشروع بنجاح',\n it: 'Progetto aggiornato con successo',\n pt: 'Projeto atualizado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक अपडेट किया गया',\n tr: 'Proje başarıyla güncellendi',\n pl: 'Projekt został pomyślnie zaktualizowany',\n id: 'Proyek berhasil diperbarui',\n vi: 'Dự án đã được cập nhật thành công',\n uk: 'Проєкт успішно оновлено',\n }),\n description: t({\n en: 'Your project has been updated successfully',\n 'en-GB': 'Your project has been updated successfully',\n fr: 'Votre projet a été mis à jour avec succès',\n es: 'Su proyecto ha sido actualizado con éxito',\n ru: 'Ваш проект был успешно обновлен',\n ja: 'プロジェクトは正常に更新されました',\n ko: '프로젝트가 성공적으로 업데이트되었습니다',\n zh: '您的项目已成功更新',\n de: 'Ihr Projekt wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث مشروعك بنجاح',\n it: 'Il tuo progetto è stato aggiornato con successo',\n pt: 'Seu projeto foi atualizado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Projeniz başarıyla güncellendi',\n pl: 'Twój projekt został pomyślnie zaktualizowany',\n id: 'Proyek Anda telah berhasil diperbarui',\n vi: 'Dự án của bạn đã được cập nhật thành công',\n uk: 'Ваш проєкт успішно оновлено',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype UserAndAdmin = { user: User; isAdmin: boolean };\nexport type ProjectMemberByIdOption = {\n userId: string | Types.ObjectId;\n isAdmin?: boolean;\n};\n\nexport type UpdateProjectMembersBody = Partial<{\n membersIds: ProjectMemberByIdOption[];\n}>;\nexport type UpdateProjectMembersResult = ResponseData<ProjectAPI>;\n\n/**\n * Update members to the dictionary in the database.\n */\nexport const updateProjectMembers = async (\n request: FastifyRequest<{ Body: UpdateProjectMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, organization, roles } = request.session || {};\n const { membersIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_MEMBER'\n );\n }\n\n if (membersIds?.map((el) => el.isAdmin)?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_ADMIN'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingUsers: UserAndAdmin[] = [];\n\n if (membersIds) {\n const userIdList = membersIds\n ?.filter(\n (member) =>\n // Remove members that are not in the organization\n !organization?.membersIds.includes(member.userId as Types.ObjectId)\n )\n .map((member) => member.userId);\n\n const users = await userService.getUsersByIds(userIdList);\n\n if (users) {\n const userMap: UserAndAdmin[] = users.map((user) => ({\n user,\n isAdmin:\n membersIds.find(\n (member) => String(member.userId) === String(user.id)\n )?.isAdmin ?? false,\n }));\n\n existingUsers.push(...userMap);\n }\n }\n\n const formattedMembers: Types.ObjectId[] = existingUsers.map(\n (user) => user.user.id\n );\n const formattedAdmin: Types.ObjectId[] = existingUsers\n .filter((el) => el.isAdmin)\n .map((user) => user.user.id);\n\n const updatedOrganization = await projectService.updateProjectById(\n project.id,\n {\n ...project,\n membersIds: formattedMembers,\n adminsIds: formattedAdmin,\n }\n );\n\n const formattedProject = mapProjectToAPI(updatedOrganization);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project members updated successfully',\n 'en-GB': 'Project members updated successfully',\n fr: 'Membres du projet mis à jour avec succès',\n es: 'Miembros del proyecto actualizados con éxito',\n ru: 'Члены проекта успешно обновлены',\n ja: 'プロジェクトメンバーが正常に更新されました',\n ko: '프로젝트 구성원이 성공적으로 업데이트되었습니다',\n zh: '项目成员已成功更新',\n de: 'Projektmitglieder erfolgreich aktualisiert',\n ar: 'تم تحديث أعضاء المشروع بنجاح',\n it: 'Membri del progetto aggiornati con successo',\n pt: 'Membros do projeto atualizados com sucesso',\n hi: 'प्रोजेक्ट सदस्य सफलतापूर्वक अपडेट किए गए',\n tr: 'Proje üyeleri başarıyla güncellendi',\n pl: 'Członkowie projektu zostali pomyślnie zaktualizowani',\n id: 'Anggota proyek berhasil diperbarui',\n vi: 'Thành viên dự án đã được cập nhật thành công',\n uk: 'Члени проєкту успішно оновлені',\n }),\n description: t({\n en: 'Your project members have been updated successfully',\n 'en-GB': 'Your project members have been updated successfully',\n fr: 'Les membres de votre projet ont été mis à jour avec succès',\n es: 'Los miembros de su proyecto han sido actualizados con éxito',\n ru: 'Члены вашего проекта были успешно обновлены',\n ja: 'プロジェクトメンバーは正常に更新されました',\n ko: '프로젝트 구성원이 성공적으로 업데이트되었습니다',\n zh: '您的项目成员已成功更新',\n de: 'Ihre Projektmitglieder wurden erfolgreich aktualisiert',\n ar: 'لقد تم تحديث أعضاء مشروعك بنجاح',\n it: 'I membri del tuo progetto sono stati aggiornati con successo',\n pt: 'Os membros do seu projeto foram atualizados com sucesso',\n hi: 'आपके प्रोजेक्ट के सदस्यों को सफलतापूर्वक अपडेट किया गया है',\n tr: 'Proje üyeleriniz başarıyla güncellendi',\n pl: 'Członkowie Twojego projektu zostali pomyślnie zaktualizowani',\n id: 'Anggota proyek Anda telah berhasil diperbarui',\n vi: 'Thành viên dự án của bạn đã được cập nhật thành công',\n uk: 'Члени вашого проєкту успішно оновлені',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushProjectConfigurationBody = ProjectConfiguration;\nexport type PushProjectConfigurationResult = ResponseData<ProjectConfiguration>;\n\n/**\n * Pushes a project configuration to the database.\n */\nexport const pushProjectConfiguration = async (\n request: FastifyRequest<{ Body: PushProjectConfigurationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.session || {};\n const projectConfiguration = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectObject = await projectService.getProjectById(project.id);\n\n // Preserve existing API key if not provided in the update\n if (projectConfiguration.ai && projectObject.configuration?.ai?.apiKey) {\n projectConfiguration.ai.apiKey = projectObject.configuration.ai.apiKey;\n }\n\n const previousApplicationUrl =\n projectObject.configuration?.editor?.applicationURL;\n const newApplicationUrl = projectConfiguration.editor?.applicationURL;\n\n projectObject.configuration = projectConfiguration;\n\n await projectObject.save();\n\n // Fire-and-forget screenshot generation\n refreshProjectScreenshotIfChanged({\n newApplicationUrl,\n previousApplicationUrl,\n existingImageUrl: projectObject.imageUrl,\n projectId: project.id.toString(),\n })\n .then((imageUrl) => {\n if (imageUrl !== undefined) {\n projectService\n .updateProjectById(project.id, { imageUrl })\n .catch(() => {});\n }\n })\n .catch(() => {});\n\n if (!projectObject.configuration) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_UPDATE_FAILED',\n {\n projectId: project.id,\n }\n );\n }\n\n const responseData = formatResponse<ProjectConfiguration>({\n message: t({\n en: 'Project configuration updated successfully',\n 'en-GB': 'Project configuration updated successfully',\n fr: 'Configuration du projet mise à jour avec succès',\n es: 'Configuración del proyecto actualizada con éxito',\n ru: 'Конфигурация проекта успешно обновлена',\n ja: 'プロジェクト構成が正常に更新されました',\n ko: '프로젝트 설정이 성공적으로 업데이트되었습니다',\n zh: '项目配置已成功更新',\n de: 'Projektkonfiguration erfolgreich aktualisiert',\n ar: 'تم تحديث تكوين المشروع بنجاح',\n it: 'Configurazione del progetto aggiornata con successo',\n pt: 'Configuração do projeto atualizada com sucesso',\n hi: 'प्रोजेक्ट कॉन्फ़िगरेशन सफलतापूर्वक अपडेट किया गया',\n tr: 'Proje yapılandırması başarıyla güncellendi',\n pl: 'Konfiguracja projektu została pomyślnie zaktualizowana',\n id: 'Konfigurasi proyek berhasil diperbarui',\n vi: 'Cấu hình dự án đã được cập nhật thành công',\n uk: 'Конфігурацію проєкту успішно оновлено',\n }),\n description: t({\n en: 'Your project configuration has been updated successfully',\n 'en-GB': 'Your project configuration has been updated successfully',\n fr: 'La configuration du projet a été mise à jour avec succès',\n es: 'Su configuración del proyecto ha sido actualizada con éxito',\n ru: 'Конфигурация вашего проекта была успешно обновлена',\n ja: 'プロジェクト構成は正常に更新されました',\n ko: '프로젝트 설정이 성공적으로 업데이트되었습니다',\n zh: '您的项目配置已成功更新',\n de: 'Ihre Projektkonfiguration wurde erfolgreich aktualisiert',\n ar: 'لقد تم تحديث تكوين مشروعك بنجاح',\n it: 'La configurazione del tuo progetto è stata aggiornata con successo',\n pt: 'A configuração do seu projeto foi atualizada com sucesso',\n hi: 'आपका प्रोजेक्ट कॉन्फ़िगरेशन सफलतापूर्वक अपडेट कर दिया गया है',\n tr: 'Proje yapılandırmanız başarıyla güncellendi',\n pl: 'Konfiguracja Twojego projektu została pomyślnie zaktualizowana',\n id: 'Konfigurasi proyek Anda telah berhasil diperbarui',\n vi: 'Cấu hình dự án của bạn đã được cập nhật thành công',\n uk: 'Конфігурацію вашого проєкту успішно оновлено',\n }),\n data: mapProjectToAPI(projectObject) as ProjectConfiguration,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TriggerBuildResult = ResponseData<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n}>;\n\nexport type TriggerWebhookBody = {\n webhookIndex: number;\n};\n\nexport type TriggerWebhookResult = ResponseData<{\n target: string;\n success: boolean;\n message?: string;\n}>;\n\n/**\n * Triggers CI builds for a project (Git provider pipelines and webhooks)\n */\nexport const triggerBuild = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const results = await webhooksService.triggerAll(fullProject);\n\n const responseData = formatResponse<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n }>({\n message: t({\n en: 'Build triggers initiated',\n 'en-GB': 'Build triggers initiated',\n fr: 'Déclenchement des builds initié',\n es: 'Inicio de los triggers de build',\n ru: 'Запуск триггеров сборки инициирован',\n ja: 'ビルドトリガーが開始されました',\n ko: '빌드 트리거가 시작되었습니다',\n zh: '构建触发器已启动',\n de: 'Build-Trigger initiiert',\n ar: 'بدء مشغلات البناء',\n it: 'Trigger di build avviati',\n pt: 'Gatilhos de build iniciados',\n hi: 'बिल्ड ट्रिगर शुरू किए गए',\n tr: 'Build tetikleyicileri başlatıldı',\n pl: 'Zainicjowano wyzwalacze budowania',\n id: 'Pemicu build dimulai',\n vi: 'Đã bắt đầu trình kích hoạt bản dựng',\n uk: 'Запуск тригерів збірки ініційовано',\n }),\n description: t({\n en: 'CI pipelines and webhooks have been triggered',\n 'en-GB': 'CI pipelines and webhooks have been triggered',\n fr: 'Les pipelines CI et webhooks ont été déclenchés',\n es: 'Los pipelines CI y webhooks han sido activados',\n ru: 'CI-конвейеры и вебхуки были запущены',\n ja: 'CIパイプラインとウェブフックがトリガーされました',\n ko: 'CI 파이프라인과 웹후크가 트리거되었습니다',\n zh: 'CI 流水线和 Webhook 已触发',\n de: 'CI-Pipelines und Webhooks wurden ausgelöst',\n ar: 'تم تفعيل أنابيب CI والويب هوك',\n it: 'Le pipeline CI e i webhook sono stati attivati',\n pt: 'Pipelines CI e webhooks foram acionados',\n hi: 'CI पाइपलाइन और वेबहुक ट्रिगर किए गए हैं',\n tr: 'CI hatları ve webhooklar tetiklendi',\n pl: 'Potoki CI i webhooki zostały wyzwolone',\n id: 'Pipa CI dan webhook telah dipicu',\n vi: 'Đường ống CI và webhook đã được kích hoạt',\n uk: 'Конвеєри CI та вебхуки були запущені',\n }),\n data: { results },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n/**\n * Triggers a single webhook by index\n */\nexport const triggerWebhook = async (\n request: FastifyRequest<{ Body: TriggerWebhookBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const { webhookIndex } = request.body;\n\n if (typeof webhookIndex !== 'number' || webhookIndex < 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const result = await webhooksService.triggerSingleWebhook(\n fullProject,\n webhookIndex\n );\n\n const responseData = formatResponse<{\n target: string;\n success: boolean;\n message?: string;\n }>({\n message: t({\n en: 'Webhook triggered',\n 'en-GB': 'Webhook triggered',\n fr: 'Webhook déclenché',\n es: 'Webhook activado',\n ru: 'Вебхук запущен',\n ja: 'ウェブフックがトリガーされました',\n ko: '웹후크가 트리거되었습니다',\n zh: 'Webhook 已触发',\n de: 'Webhook ausgelöst',\n ar: 'تم تفعيل الويب هوك',\n it: 'Webhook attivato',\n pt: 'Webhook acionado',\n hi: 'वेबहुक ट्रिगर किया गया',\n tr: 'Webhook tetiklendi',\n pl: 'Webhook został wyzwolony',\n id: 'Webhook dipicu',\n vi: 'Webhook đã được kích hoạt',\n uk: 'Вебхук запущено',\n }),\n description: t({\n en: `Webhook \"${result.target}\" has been triggered`,\n 'en-GB': `Webhook \"${result.target}\" has been triggered`,\n fr: `Le webhook \"${result.target}\" a été déclenché`,\n es: `El webhook \"${result.target}\" ha sido activado`,\n ru: `Вебхук \"${result.target}\" был запущен`,\n ja: `ウェブフック \"${result.target}\" がトリガーされました`,\n ko: `웹후크 \"${result.target}\"가 트리거되었습니다`,\n zh: `Webhook \"${result.target}\" 已触发`,\n de: `Webhook \"${result.target}\" wurde ausgelöst`,\n ar: `تم تفعيل الويب هوك \"${result.target}\"`,\n it: `Il webhook \"${result.target}\" è stato attivato`,\n pt: `O webhook \"${result.target}\" foi acionado`,\n hi: `वेबहुक \"${result.target}\" ट्रिगर किया गया है`,\n tr: `\"${result.target}\" webhooku tetiklendi`,\n pl: `Webhook \"${result.target}\" został wyzwolony`,\n id: `Webhook \"${result.target}\" telah dipicu`,\n vi: `Webhook \"${result.target}\" đã được kích hoạt`,\n uk: `Вебхук \"${result.target}\" запущено`,\n }),\n data: result,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Deletes a project from the database by its ID.\n */\nexport const deleteProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, project, session, roles } =\n _request.session || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:admin'\n )({\n ..._request.session,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectToDelete = await projectService.getProjectById(project.id);\n\n if (String(projectToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED',\n {\n projectId: project.id,\n }\n );\n }\n\n logger.info(`Project deleted: ${String(deletedProject.id)}`);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project deleted successfully',\n 'en-GB': 'Project deleted successfully',\n fr: 'Projet supprimé avec succès',\n es: 'Proyecto eliminado con éxito',\n ru: 'Проект успешно удален',\n ja: 'プロジェクトが正常に削除されました',\n ko: '프로젝트가 성공적으로 삭제되었습니다',\n zh: '项目已成功删除',\n de: 'Projekt erfolgreich gelöscht',\n ar: 'تم حذف المشروع بنجاح',\n it: 'Progetto eliminato con successo',\n pt: 'Projeto excluído com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक हटा दिया गया',\n tr: 'Proje başarıyla silindi',\n pl: 'Projekt został pomyślnie usunięty',\n id: 'Proyek berhasil dihapus',\n vi: 'Dự án đã được xóa thành công',\n uk: 'Проєкт успішно видалено',\n }),\n description: t({\n en: 'Your project has been deleted successfully',\n 'en-GB': 'Your project has been deleted successfully',\n fr: 'Votre projet a été supprimé avec succès',\n es: 'Su proyecto ha sido eliminado con éxito',\n ru: 'Ваш проект был успешно удален',\n ja: 'プロジェクトは正常に削除されました',\n ko: '프로젝트가 성공적으로 삭제되었습니다',\n zh: '您的项目已成功删除',\n de: 'Ihr Projekt wurde erfolgreich gelöscht',\n ar: 'لقد تم حذف مشروعك بنجاح',\n it: 'Il tuo progetto è stato eliminato con successo',\n pt: 'Seu projeto foi excluído com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक हटा दिया गया है',\n tr: 'Projeniz başarıyla silindi',\n pl: 'Twój projekt został pomyślnie usunięty',\n id: 'Proyek Anda telah berhasil dihapus',\n vi: 'Dự án của bạn đã được xóa thành công',\n uk: 'Ваш проєкт успішно видалено',\n }),\n data: mapProjectToAPI(deletedProject),\n });\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveProjectId: null,\n });\n }\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteProjectByIdAdminParams = { projectId: string };\nexport type DeleteProjectByIdAdminResult = ResponseData<ProjectAPI>;\n\n/**\n * Admin-only: Deletes any project from the database by its ID.\n */\nexport const deleteProjectByIdAdmin = async (\n request: FastifyRequest<{ Params: DeleteProjectByIdAdminParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { projectId } = request.params;\n const { roles } = request.session || {};\n\n if (!hasPermission(roles || [], 'project:admin')({ ...request.session })) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const project = await projectService.getProjectById(projectId);\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n const formattedProject = mapProjectToAPI(deletedProject);\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project deleted',\n fr: 'Projet supprimé',\n es: 'Proyecto eliminado',\n 'en-GB': 'Project deleted',\n de: 'Projekt gelöscht',\n ja: 'プロジェクトが削除されました',\n ko: '프로젝트가 삭제되었습니다',\n zh: '项目已删除',\n it: 'Progetto eliminato',\n pt: 'Projeto excluído',\n hi: 'परियोजना हटा दी गई',\n ar: 'تم حذف المشروع',\n ru: 'Проект удален',\n tr: 'Proje silindi',\n pl: 'Projekt usunięty',\n id: 'Proyek dihapus',\n vi: 'Dự án đã bị xóa',\n uk: 'Проєкт видалено',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type SelectProjectParam = { projectId: string | Types.ObjectId };\nexport type SelectProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Select a project.\n */\nexport const selectProject = async (\n request: FastifyRequest<{ Params: SelectProjectParam }>,\n reply: FastifyReply\n) => {\n const { projectId } = request.params;\n const { session, roles, user } = request.session || {};\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_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 project = await projectService.getProjectById(projectId);\n\n if (\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.session,\n targetProjects: [project],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: String(projectId) } }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveProjectId: String(projectId),\n });\n }\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project selected successfully',\n 'en-GB': 'Project selected successfully',\n fr: 'Projet sélectionné avec succès',\n es: 'Proyecto seleccionado con éxito',\n ru: 'Проект успешно выбран',\n ja: 'プロジェクトが正常に選択されました',\n ko: '프로젝트가 성공적으로 선택되었습니다',\n zh: '项目已成功选择',\n de: 'Projekt erfolgreich ausgewählt',\n ar: 'تم اختيار المشروع بنجاح',\n it: 'Progetto selezionato con successo',\n pt: 'Projeto selecionado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक चुना गया',\n tr: 'Proje başarıyla seçildi',\n pl: 'Projekt został pomyślnie wybrany',\n id: 'Proyek berhasil dipilih',\n vi: 'Dự án đã được chọn thành công',\n uk: 'Проєкт успішно вибрано',\n }),\n description: t({\n en: 'Your project has been selected successfully',\n 'en-GB': 'Your project has been selected successfully',\n fr: 'Votre projet a été sélectionné avec succès',\n es: 'Su proyecto ha sido seleccionado con éxito',\n ru: 'Ваш проект был успешно выбран',\n ja: 'プロジェクトは正常に選択されました',\n ko: '프로젝트가 성공적으로 선택되었습니다',\n zh: '您的项目已成功选择',\n de: 'Ihr Projekt wurde erfolgreich ausgewählt',\n ar: 'لقد تم اختيار مشروعك بنجاح',\n it: 'Il tuo progetto è stato selezionato con successo',\n pt: 'Seu projeto foi selecionado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक चुन लिया गया है',\n tr: 'Projeniz başarıyla seçildi',\n pl: 'Twój projekt został pomyślnie wybrany',\n id: 'Proyek Anda telah berhasil dipilih',\n vi: 'Dự án của bạn đã được chọn thành công',\n uk: 'Ваш проєкт успішно вибрано',\n }),\n data: mapProjectToAPI(project),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectProjectResult = ResponseData<null>;\n\n/**\n * Unselect a project.\n */\nexport const unselectProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { session, user } = _request.session || {};\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n if (user) {\n await userService.updateUserById(user.id, {\n lastActiveProjectId: null,\n });\n }\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Project unselected successfully',\n 'en-GB': 'Project unselected successfully',\n fr: 'Projet désélectionné avec succès',\n es: 'Proyecto deseleccionado con éxito',\n ru: 'Проект успешно снят с выбора',\n ja: 'プロジェクトの選択が正常に解除されました',\n ko: '프로젝트 선택이 성공적으로 해제되었습니다',\n zh: '项目已成功取消选择',\n de: 'Projekt erfolgreich abgewählt',\n ar: 'تم إلغاء تحديد المشروع بنجاح',\n it: 'Progetto deselezionato con successo',\n pt: 'Projeto desmarcado com sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक अनसेलेक्ट किया गया',\n tr: 'Proje seçimi başarıyla kaldırıldı',\n pl: 'Wybór projektu został pomyślnie cofnięty',\n id: 'Proyek berhasil batal dipilih',\n vi: 'Dự án đã được bỏ chọn thành công',\n uk: 'Вибір проєкту успішно скасовано',\n }),\n description: t({\n en: 'Your project has been unselected successfully',\n 'en-GB': 'Your project has been unselected successfully',\n fr: 'Votre projet a été désélectionné avec succès',\n es: 'Su proyecto ha sido deseleccionado con éxito',\n ru: 'Выбор вашего проекта был успешно снят',\n ja: 'プロジェクトの選択は正常に解除されました',\n ko: '프로젝트 선택이 성공적으로 해제되었습니다',\n zh: '您的项目已成功取消选择',\n de: 'Ihr Projekt wurde erfolgreich abgewählt',\n ar: 'لقد تم إلغاء تحديد مشروعك بنجاح',\n it: 'Il tuo progetto è stato deselezionato con successo',\n pt: 'Seu projeto foi desmarcado com sucesso',\n hi: 'आपका प्रोजेक्ट सफलतापूर्वक अनसेलेक्ट कर दिया गया है',\n tr: 'Projenizin seçimi başarıyla kaldırıldı',\n pl: 'Wybór Twojego projektu został pomyślnie cofnięty',\n id: 'Proyek Anda telah berhasil batal dipilih',\n vi: 'Dự án 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\nexport type GetCIConfigurationResult = ResponseData<ciService.CIStatus>;\n\n/**\n * Get CI configuration status for the current project\n */\nexport const getCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const ciStatus = await ciService.getCIStatus(project, user.id);\n\n const responseData = formatResponse<ciService.CIStatus>({\n data: ciStatus,\n });\n\n return reply.send(responseData);\n } catch (error) {\n if ((error as any)?.isAppError) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n return ErrorHandler.handleCustomErrorResponse(\n reply,\n 'CI_CONFIG_ERROR',\n 'CI Configuration Error',\n (error as Error)?.message ?? 'Failed to get CI configuration',\n undefined,\n 500\n );\n }\n};\n\nexport type PushCIConfigurationResult = ResponseData<{ success: boolean }>;\n\n/**\n * Push CI configuration file to the repository\n */\nexport const pushCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.session || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n await ciService.installCI(project, user.id);\n\n const responseData = formatResponse<{ success: boolean }>({\n message: t({\n en: 'CI configuration installed successfully',\n 'en-GB': 'CI configuration installed successfully',\n fr: 'Configuration CI installée avec succès',\n es: 'Configuración CI instalada con éxito',\n ru: 'CI-конфигурация успешно установлена',\n ja: 'CI構成が正常にインストールされました',\n ko: 'CI 설정이 성공적으로 설치되었습니다',\n zh: 'CI 配置已成功安装',\n de: 'CI-Konfiguration erfolgreich installiert',\n ar: 'تم تثبيت تكوين CI بنجاح',\n it: 'Configurazione CI installata con successo',\n pt: 'Configuração CI installada com successo',\n hi: 'CI कॉन्फ़िगरेशन सफलतापूर्वक स्थापित किया गया',\n tr: 'CI yapılandırması başarıyla yüklendi',\n pl: 'Konfiguracja CI została pomyślnie zainstalowana',\n id: 'Konfigurasi CI berhasil diinstal',\n vi: 'Cài đặt cấu hình CI thành công',\n uk: 'Конфігурацію CI успішно встановлено',\n }),\n description: t({\n en: 'The CI workflow file has been added to your repository',\n 'en-GB': 'The CI workflow file has been added to your repository',\n fr: 'Le fichier de workflow CI a été ajouté à votre dépôt',\n es: 'El archivo de flujo de trabajo CI se ha agregado a su repositorio',\n ru: 'Файл рабочего процесса CI был добавлен в ваш репозиторий',\n ja: 'CIワークフローファイルがリポジトリに追加されました',\n ko: 'CI 워크플로 파일이 저장소에 추가되었습니다',\n zh: 'CI 工作流文件已添加到您的存储库中',\n de: 'Die CI-Workflow-Datei wurde Ihrem Repository hinzugefügt',\n ar: 'تمت إضافة ملف سير عمل CI إلى مستودعك',\n it: 'Il file del workflow CI è stato aggiunto al tuo repository',\n pt: 'O arquivo de fluxo de trabalho CI foi adicionado ao seu repositório',\n hi: 'CI वर्कफ़्लो फ़ाइल आपके रिपॉजिटरी में जोड़ दी गई है',\n tr: 'CI iş akışı dosyası deponuza eklendi',\n pl: 'Plik przepływu pracy CI został dodany do Twojego repozytorium',\n id: 'File alur kerja CI telah ditambahkan ke repositori Anda',\n vi: 'Tệp quy trình làm việc CI đã được thêm vào kho lưu trữ của bạn',\n uk: 'Файл робочого процесу CI додано до вашого репозиторію',\n }),\n data: { success: true },\n });\n\n return reply.send(responseData);\n } catch (error) {\n if ((error as any)?.isAppError) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n return ErrorHandler.handleCustomErrorResponse(\n reply,\n 'CI_INSTALL_ERROR',\n 'CI Installation Error',\n (error as Error)?.message ?? 'Failed to install CI configuration',\n undefined,\n 500\n );\n }\n};\n"],"mappings":"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"}
@@ -59,6 +59,14 @@ const userSchema = new Schema({
59
59
  dateOfBirth: {
60
60
  type: Date,
61
61
  required: false
62
+ },
63
+ lastActiveOrganizationId: {
64
+ type: String,
65
+ required: false
66
+ },
67
+ lastActiveProjectId: {
68
+ type: String,
69
+ required: false
62
70
  }
63
71
  }, {
64
72
  timestamps: true,
@@ -1 +1 @@
1
- {"version":3,"file":"user.schema.mjs","names":[],"sources":["../../../src/schemas/user.schema.ts"],"sourcesContent":["import {\n NAMES_MAX_LENGTH,\n NAMES_MIN_LENGTH,\n} from '@utils/validation/validateUser';\nimport { Schema } from 'mongoose';\nimport validator from 'validator';\nimport type { UserSchema } from '@/types/user.types';\n\nexport const userSchema = new Schema<UserSchema>(\n {\n email: {\n type: String,\n required: true,\n unique: true,\n validate: [validator.isEmail, 'Please fill a valid email address'],\n lowercase: true,\n trim: true,\n },\n name: {\n type: String,\n maxlength: NAMES_MAX_LENGTH,\n minlength: NAMES_MIN_LENGTH,\n },\n image: {\n type: String,\n required: false,\n },\n phone: {\n type: String,\n maxlength: 20,\n },\n\n customerId: {\n type: String,\n required: false,\n },\n\n emailsList: {\n type: {\n newsLetter: {\n type: Boolean,\n default: false,\n },\n },\n required: false,\n },\n role: {\n type: String,\n enum: ['admin', 'user'],\n default: 'user',\n required: false,\n },\n lastLoginMethod: {\n type: String,\n enum: ['email', 'google', 'github', 'passkey'],\n required: false,\n },\n lang: {\n type: String,\n required: false,\n },\n dateOfBirth: {\n type: Date,\n required: false,\n },\n },\n {\n timestamps: true,\n\n toJSON: {\n virtuals: true, // keep the automatic `id` getter\n versionKey: false, // drop __v\n transform(_doc, ret: any) {\n const { _id, ...rest } = ret;\n return {\n ...rest,\n id: _id.toString(),\n };\n },\n },\n toObject: {\n virtuals: true,\n transform(_doc, ret: any) {\n const { _id, ...rest } = ret;\n return {\n ...rest,\n id: _id,\n };\n },\n },\n }\n);\n"],"mappings":";;;;;AAQA,MAAa,aAAa,IAAI,OAC5B;CACE,OAAO;EACL,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU,CAAC,UAAU,SAAS,mCAAmC;EACjE,WAAW;EACX,MAAM;CACR;CACA,MAAM;EACJ,MAAM;EACN;EACA;CACF;CACA,OAAO;EACL,MAAM;EACN,UAAU;CACZ;CACA,OAAO;EACL,MAAM;EACN,WAAW;CACb;CAEA,YAAY;EACV,MAAM;EACN,UAAU;CACZ;CAEA,YAAY;EACV,MAAM,EACJ,YAAY;GACV,MAAM;GACN,SAAS;EACX,EACF;EACA,UAAU;CACZ;CACA,MAAM;EACJ,MAAM;EACN,MAAM,CAAC,SAAS,MAAM;EACtB,SAAS;EACT,UAAU;CACZ;CACA,iBAAiB;EACf,MAAM;EACN,MAAM;GAAC;GAAS;GAAU;GAAU;EAAS;EAC7C,UAAU;CACZ;CACA,MAAM;EACJ,MAAM;EACN,UAAU;CACZ;CACA,aAAa;EACX,MAAM;EACN,UAAU;CACZ;AACF,GACA;CACE,YAAY;CAEZ,QAAQ;EACN,UAAU;EACV,YAAY;EACZ,UAAU,MAAM,KAAU;GACxB,MAAM,EAAE,KAAK,GAAG,SAAS;GACzB,OAAO;IACL,GAAG;IACH,IAAI,IAAI,SAAS;GACnB;EACF;CACF;CACA,UAAU;EACR,UAAU;EACV,UAAU,MAAM,KAAU;GACxB,MAAM,EAAE,KAAK,GAAG,SAAS;GACzB,OAAO;IACL,GAAG;IACH,IAAI;GACN;EACF;CACF;AACF,CACF"}
1
+ {"version":3,"file":"user.schema.mjs","names":[],"sources":["../../../src/schemas/user.schema.ts"],"sourcesContent":["import {\n NAMES_MAX_LENGTH,\n NAMES_MIN_LENGTH,\n} from '@utils/validation/validateUser';\nimport { Schema } from 'mongoose';\nimport validator from 'validator';\nimport type { UserSchema } from '@/types/user.types';\n\nexport const userSchema = new Schema<UserSchema>(\n {\n email: {\n type: String,\n required: true,\n unique: true,\n validate: [validator.isEmail, 'Please fill a valid email address'],\n lowercase: true,\n trim: true,\n },\n name: {\n type: String,\n maxlength: NAMES_MAX_LENGTH,\n minlength: NAMES_MIN_LENGTH,\n },\n image: {\n type: String,\n required: false,\n },\n phone: {\n type: String,\n maxlength: 20,\n },\n\n customerId: {\n type: String,\n required: false,\n },\n\n emailsList: {\n type: {\n newsLetter: {\n type: Boolean,\n default: false,\n },\n },\n required: false,\n },\n role: {\n type: String,\n enum: ['admin', 'user'],\n default: 'user',\n required: false,\n },\n lastLoginMethod: {\n type: String,\n enum: ['email', 'google', 'github', 'passkey'],\n required: false,\n },\n lang: {\n type: String,\n required: false,\n },\n dateOfBirth: {\n type: Date,\n required: false,\n },\n lastActiveOrganizationId: {\n type: String,\n required: false,\n },\n lastActiveProjectId: {\n type: String,\n required: false,\n },\n },\n {\n timestamps: true,\n\n toJSON: {\n virtuals: true, // keep the automatic `id` getter\n versionKey: false, // drop __v\n transform(_doc, ret: any) {\n const { _id, ...rest } = ret;\n return {\n ...rest,\n id: _id.toString(),\n };\n },\n },\n toObject: {\n virtuals: true,\n transform(_doc, ret: any) {\n const { _id, ...rest } = ret;\n return {\n ...rest,\n id: _id,\n };\n },\n },\n }\n);\n"],"mappings":";;;;;AAQA,MAAa,aAAa,IAAI,OAC5B;CACE,OAAO;EACL,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU,CAAC,UAAU,SAAS,mCAAmC;EACjE,WAAW;EACX,MAAM;CACR;CACA,MAAM;EACJ,MAAM;EACN;EACA;CACF;CACA,OAAO;EACL,MAAM;EACN,UAAU;CACZ;CACA,OAAO;EACL,MAAM;EACN,WAAW;CACb;CAEA,YAAY;EACV,MAAM;EACN,UAAU;CACZ;CAEA,YAAY;EACV,MAAM,EACJ,YAAY;GACV,MAAM;GACN,SAAS;EACX,EACF;EACA,UAAU;CACZ;CACA,MAAM;EACJ,MAAM;EACN,MAAM,CAAC,SAAS,MAAM;EACtB,SAAS;EACT,UAAU;CACZ;CACA,iBAAiB;EACf,MAAM;EACN,MAAM;GAAC;GAAS;GAAU;GAAU;EAAS;EAC7C,UAAU;CACZ;CACA,MAAM;EACJ,MAAM;EACN,UAAU;CACZ;CACA,aAAa;EACX,MAAM;EACN,UAAU;CACZ;CACA,0BAA0B;EACxB,MAAM;EACN,UAAU;CACZ;CACA,qBAAqB;EACnB,MAAM;EACN,UAAU;CACZ;AACF,GACA;CACE,YAAY;CAEZ,QAAQ;EACN,UAAU;EACV,YAAY;EACZ,UAAU,MAAM,KAAU;GACxB,MAAM,EAAE,KAAK,GAAG,SAAS;GACzB,OAAO;IACL,GAAG;IACH,IAAI,IAAI,SAAS;GACnB;EACF;CACF;CACA,UAAU;EACR,UAAU;EACV,UAAU,MAAM,KAAU;GACxB,MAAM,EAAE,KAAK,GAAG,SAAS;GACzB,OAAO;IACL,GAAG;IACH,IAAI;GACN;EACF;CACF;AACF,CACF"}
@@ -41,7 +41,7 @@ const discoverUrlsFromSitemap = async (targetUrl) => {
41
41
  };
42
42
  const startRecursiveAuditJob = async (targetUrl, userId, urls) => {
43
43
  const existingJob = await AuditJobModel.findOne({
44
- targetUrl,
44
+ targetUrl: String(targetUrl),
45
45
  status: { $in: ["pending", "running"] }
46
46
  });
47
47
  if (existingJob) return existingJob._id.toString();
@@ -1 +1 @@
1
- {"version":3,"file":"recursiveAudit.service.mjs","names":[],"sources":["../../../../src/services/audit/recursiveAudit.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { AuditJobModel, AuditJobStatus } from '@models/auditJob.model';\nimport { AuditPageModel, AuditPageStatus } from '@models/auditPage.model';\nimport { load } from 'cheerio';\nimport { mutateScore, type Score } from './analysis/calculateScore';\nimport { runSingleAudit } from './seoAudit.service';\n\nconst SLEEP_TIME = 30000;\nconst MAX_PAGES = 10;\n\nlet isProcessing = false;\n\n/**\n * Fetches sitemap.xml for the given URL and extracts all <loc> entries.\n * Falls back to [targetUrl] if no sitemap is found.\n */\nexport const discoverUrlsFromSitemap = async (\n targetUrl: string\n): Promise<string[]> => {\n try {\n const { origin } = new URL(targetUrl);\n const sitemapUrl = `${origin}/sitemap.xml`;\n\n const response = await fetch(sitemapUrl, {\n method: 'GET',\n headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SEO-Audit-Bot/1.0)' },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!response.ok) return [targetUrl];\n\n const sitemapContent = await response.text();\n const $ = load(sitemapContent, { xmlMode: true });\n\n const urls: string[] = [];\n\n // Primary <loc> entries\n $('loc').each((_, el) => {\n const url = $(el).text().trim();\n if (url) urls.push(url);\n });\n\n // Alternate hreflang URLs from <xhtml:link rel=\"alternate\" href=\"...\">\n // Cheerio in xmlMode parses these as \"xhtml:link\" elements\n $('xhtml\\\\:link[rel=\"alternate\"], link[rel=\"alternate\"]').each((_, el) => {\n const href = $(el).attr('href')?.trim();\n if (href && href !== 'x-default') urls.push(href);\n });\n\n const uniqueUrls = [...new Set(urls)];\n return uniqueUrls.length > 0 ? uniqueUrls : [targetUrl];\n } catch {\n return [targetUrl];\n }\n};\n\nexport const startRecursiveAuditJob = async (\n targetUrl: string,\n userId?: string,\n urls?: string[]\n): Promise<string> => {\n const existingJob = await AuditJobModel.findOne({\n targetUrl,\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n });\n\n if (existingJob) {\n return (existingJob._id as any).toString();\n }\n\n const pageUrls =\n urls && urls.length > 0\n ? [...new Set(urls)].slice(0, MAX_PAGES)\n : [targetUrl];\n\n const job = await AuditJobModel.create({\n targetUrl,\n userId,\n status: AuditJobStatus.PENDING,\n totalPageCount: pageUrls.length,\n });\n\n for (const url of pageUrls) {\n await AuditPageModel.create({\n jobId: job._id,\n url,\n status: AuditPageStatus.PENDING,\n }).catch(() => {\n /* ignore duplicate key errors */\n });\n }\n\n processAuditJobs().catch((err) => logger.error(err));\n\n return (job._id as any).toString();\n};\n\nexport const cancelAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.CANCELLED,\n });\n return !!result;\n};\n\nexport const pauseAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.PAUSED,\n });\n return !!result;\n};\n\nexport const resumeAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.RUNNING,\n });\n if (!result) return false;\n processAuditJobs().catch((err) => logger.error(err));\n return true;\n};\n\nexport const processAuditJobs = async (): Promise<void> => {\n if (isProcessing) return;\n isProcessing = true;\n\n try {\n while (true) {\n const job = await AuditJobModel.findOne({\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n }).sort({ createdAt: 1 });\n\n if (!job) break;\n\n if (job.status === AuditJobStatus.PENDING) {\n job.status = AuditJobStatus.RUNNING;\n await job.save();\n }\n\n // Re-fetch to detect external cancellation / pause between pages\n const freshJob = await AuditJobModel.findById(job._id);\n if (\n !freshJob ||\n freshJob.status === AuditJobStatus.CANCELLED ||\n freshJob.status === AuditJobStatus.PAUSED\n ) {\n logger.info(\n `Job ${job._id} is ${freshJob?.status ?? 'missing'} — stopping processor`\n );\n break;\n }\n\n const pendingPage = await AuditPageModel.findOne({\n jobId: job._id,\n status: AuditPageStatus.PENDING,\n });\n\n if (!pendingPage) {\n const hasMorePages = await AuditPageModel.exists({\n jobId: job._id,\n status: { $in: [AuditPageStatus.PENDING, AuditPageStatus.RUNNING] },\n });\n\n if (!hasMorePages) {\n job.status = AuditJobStatus.COMPLETED;\n job.progress = 100;\n await job.save();\n }\n break;\n }\n\n pendingPage.status = AuditPageStatus.RUNNING;\n await pendingPage.save();\n\n try {\n const { events } = await runSingleAudit(pendingPage.url, () => {});\n\n // Compute score the same way the single-page SSE controller does\n let score: Score = { score: 0, totalScore: 0 };\n for (const event of events) {\n score = mutateScore(score, event);\n }\n\n pendingPage.status = AuditPageStatus.COMPLETED;\n pendingPage.results = events;\n pendingPage.score = Math.round(\n score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0\n );\n await pendingPage.save();\n\n const totalPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n });\n const completedPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n status: AuditPageStatus.COMPLETED,\n });\n\n job.totalPageCount = totalPages;\n job.completedPageCount = completedPages;\n job.progress = Math.round((completedPages / totalPages) * 100);\n await job.save();\n } catch (err) {\n logger.error(`Failed to audit page ${pendingPage.url}:`, err);\n pendingPage.status = AuditPageStatus.FAILED;\n pendingPage.error = String(err);\n await pendingPage.save();\n }\n\n await new Promise((resolve) => setTimeout(resolve, SLEEP_TIME));\n }\n } finally {\n isProcessing = false;\n }\n};\n\nexport const getAuditJobStatus = async (jobId: string) => {\n const job = await AuditJobModel.findById(jobId);\n if (!job) return null;\n\n const pages = await AuditPageModel.find({ jobId }).select(\n 'url status score error results'\n );\n\n return { job, pages };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"recursiveAudit.service.mjs","names":[],"sources":["../../../../src/services/audit/recursiveAudit.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { AuditJobModel, AuditJobStatus } from '@models/auditJob.model';\nimport { AuditPageModel, AuditPageStatus } from '@models/auditPage.model';\nimport { load } from 'cheerio';\nimport { mutateScore, type Score } from './analysis/calculateScore';\nimport { runSingleAudit } from './seoAudit.service';\n\nconst SLEEP_TIME = 30000;\nconst MAX_PAGES = 10;\n\nlet isProcessing = false;\n\n/**\n * Fetches sitemap.xml for the given URL and extracts all <loc> entries.\n * Falls back to [targetUrl] if no sitemap is found.\n */\nexport const discoverUrlsFromSitemap = async (\n targetUrl: string\n): Promise<string[]> => {\n try {\n const { origin } = new URL(targetUrl);\n const sitemapUrl = `${origin}/sitemap.xml`;\n\n const response = await fetch(sitemapUrl, {\n method: 'GET',\n headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SEO-Audit-Bot/1.0)' },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!response.ok) return [targetUrl];\n\n const sitemapContent = await response.text();\n const $ = load(sitemapContent, { xmlMode: true });\n\n const urls: string[] = [];\n\n // Primary <loc> entries\n $('loc').each((_, el) => {\n const url = $(el).text().trim();\n if (url) urls.push(url);\n });\n\n // Alternate hreflang URLs from <xhtml:link rel=\"alternate\" href=\"...\">\n // Cheerio in xmlMode parses these as \"xhtml:link\" elements\n $('xhtml\\\\:link[rel=\"alternate\"], link[rel=\"alternate\"]').each((_, el) => {\n const href = $(el).attr('href')?.trim();\n if (href && href !== 'x-default') urls.push(href);\n });\n\n const uniqueUrls = [...new Set(urls)];\n return uniqueUrls.length > 0 ? uniqueUrls : [targetUrl];\n } catch {\n return [targetUrl];\n }\n};\n\nexport const startRecursiveAuditJob = async (\n targetUrl: string,\n userId?: string,\n urls?: string[]\n): Promise<string> => {\n const existingJob = await AuditJobModel.findOne({\n targetUrl: String(targetUrl),\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n });\n\n if (existingJob) {\n return (existingJob._id as any).toString();\n }\n\n const pageUrls =\n urls && urls.length > 0\n ? [...new Set(urls)].slice(0, MAX_PAGES)\n : [targetUrl];\n\n const job = await AuditJobModel.create({\n targetUrl,\n userId,\n status: AuditJobStatus.PENDING,\n totalPageCount: pageUrls.length,\n });\n\n for (const url of pageUrls) {\n await AuditPageModel.create({\n jobId: job._id,\n url,\n status: AuditPageStatus.PENDING,\n }).catch(() => {\n /* ignore duplicate key errors */\n });\n }\n\n processAuditJobs().catch((err) => logger.error(err));\n\n return (job._id as any).toString();\n};\n\nexport const cancelAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.CANCELLED,\n });\n return !!result;\n};\n\nexport const pauseAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.PAUSED,\n });\n return !!result;\n};\n\nexport const resumeAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.RUNNING,\n });\n if (!result) return false;\n processAuditJobs().catch((err) => logger.error(err));\n return true;\n};\n\nexport const processAuditJobs = async (): Promise<void> => {\n if (isProcessing) return;\n isProcessing = true;\n\n try {\n while (true) {\n const job = await AuditJobModel.findOne({\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n }).sort({ createdAt: 1 });\n\n if (!job) break;\n\n if (job.status === AuditJobStatus.PENDING) {\n job.status = AuditJobStatus.RUNNING;\n await job.save();\n }\n\n // Re-fetch to detect external cancellation / pause between pages\n const freshJob = await AuditJobModel.findById(job._id);\n if (\n !freshJob ||\n freshJob.status === AuditJobStatus.CANCELLED ||\n freshJob.status === AuditJobStatus.PAUSED\n ) {\n logger.info(\n `Job ${job._id} is ${freshJob?.status ?? 'missing'} — stopping processor`\n );\n break;\n }\n\n const pendingPage = await AuditPageModel.findOne({\n jobId: job._id,\n status: AuditPageStatus.PENDING,\n });\n\n if (!pendingPage) {\n const hasMorePages = await AuditPageModel.exists({\n jobId: job._id,\n status: { $in: [AuditPageStatus.PENDING, AuditPageStatus.RUNNING] },\n });\n\n if (!hasMorePages) {\n job.status = AuditJobStatus.COMPLETED;\n job.progress = 100;\n await job.save();\n }\n break;\n }\n\n pendingPage.status = AuditPageStatus.RUNNING;\n await pendingPage.save();\n\n try {\n const { events } = await runSingleAudit(pendingPage.url, () => {});\n\n // Compute score the same way the single-page SSE controller does\n let score: Score = { score: 0, totalScore: 0 };\n for (const event of events) {\n score = mutateScore(score, event);\n }\n\n pendingPage.status = AuditPageStatus.COMPLETED;\n pendingPage.results = events;\n pendingPage.score = Math.round(\n score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0\n );\n await pendingPage.save();\n\n const totalPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n });\n const completedPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n status: AuditPageStatus.COMPLETED,\n });\n\n job.totalPageCount = totalPages;\n job.completedPageCount = completedPages;\n job.progress = Math.round((completedPages / totalPages) * 100);\n await job.save();\n } catch (err) {\n logger.error(`Failed to audit page ${pendingPage.url}:`, err);\n pendingPage.status = AuditPageStatus.FAILED;\n pendingPage.error = String(err);\n await pendingPage.save();\n }\n\n await new Promise((resolve) => setTimeout(resolve, SLEEP_TIME));\n }\n } finally {\n isProcessing = false;\n }\n};\n\nexport const getAuditJobStatus = async (jobId: string) => {\n const job = await AuditJobModel.findById(jobId);\n if (!job) return null;\n\n const pages = await AuditPageModel.find({ jobId }).select(\n 'url status score error results'\n );\n\n return { job, pages };\n};\n"],"mappings":"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"}
@@ -29,10 +29,10 @@ const createCliSessionToken = async (userId, organizationId, projectId) => {
29
29
  };
30
30
  const getCliSessionTokenContext = async (token) => {
31
31
  if (!isCliSessionToken(token)) throw new GenericError("INVALID_ACCESS_TOKEN");
32
- const stored = await CliSessionTokenModel.findOne({ token });
32
+ const stored = await CliSessionTokenModel.findOne({ token: String(token) });
33
33
  if (!stored) throw new GenericError("INVALID_ACCESS_TOKEN");
34
34
  if (/* @__PURE__ */ new Date() > stored.expiresAt) {
35
- await CliSessionTokenModel.deleteOne({ token });
35
+ await CliSessionTokenModel.deleteOne({ token: String(token) });
36
36
  throw new GenericError("EXPIRED_ACCESS_TOKEN");
37
37
  }
38
38
  const [user, project, organization] = await Promise.all([
@@ -1 +1 @@
1
- {"version":3,"file":"cliSessionToken.service.mjs","names":[],"sources":["../../../src/services/cliSessionToken.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { CliSessionTokenModel } from '@models/cliSessionToken.model';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport type { Types } from 'mongoose';\nimport type { SessionContext } from '@/types/session.types';\n\nexport const CLI_SESSION_TOKEN_PREFIX = 'clisession_';\nconst CLI_SESSION_EXPIRES_MS = 2 * 60 * 60 * 1000; // 2 hours\n\nexport const isCliSessionToken = (token: string): boolean =>\n token.startsWith(CLI_SESSION_TOKEN_PREFIX);\n\nexport const createCliSessionToken = async (\n userId: string | Types.ObjectId,\n organizationId: string,\n projectId: string\n): Promise<{ token: string; expiresAt: Date }> => {\n const token = CLI_SESSION_TOKEN_PREFIX + randomBytes(32).toString('hex');\n const expiresAt = new Date(Date.now() + CLI_SESSION_EXPIRES_MS);\n\n await CliSessionTokenModel.create({\n token,\n userId,\n organizationId,\n projectId,\n expiresAt,\n });\n\n return { token, expiresAt };\n};\n\nexport const getCliSessionTokenContext = async (\n token: string\n): Promise<SessionContext> => {\n if (!isCliSessionToken(token)) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const stored = await CliSessionTokenModel.findOne({ token });\n\n if (!stored) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n if (new Date() > stored.expiresAt) {\n await CliSessionTokenModel.deleteOne({ token });\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n const [user, project, organization] = await Promise.all([\n getUserById(String(stored.userId)),\n getProjectById(stored.projectId),\n getOrganizationById(stored.organizationId),\n ]);\n\n if (!user || !project || !organization) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n return {\n user: mapUserToAPI(user),\n project: mapProjectToAPI(project),\n organization: mapOrganizationToAPI(organization),\n authType: 'session',\n };\n};\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,2BAA2B;AACxC,MAAM,yBAAyB,OAAc;AAE7C,MAAa,qBAAqB,UAChC,MAAM,WAAW,wBAAwB;AAE3C,MAAa,wBAAwB,OACnC,QACA,gBACA,cACgD;CAChD,MAAM,QAAQ,2BAA2B,YAAY,EAAE,EAAE,SAAS,KAAK;CACvE,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,sBAAsB;CAE9D,MAAM,qBAAqB,OAAO;EAChC;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OAAO;EAAE;EAAO;CAAU;AAC5B;AAEA,MAAa,4BAA4B,OACvC,UAC4B;CAC5B,IAAI,CAAC,kBAAkB,KAAK,GAC1B,MAAM,IAAI,aAAa,sBAAsB;CAG/C,MAAM,SAAS,MAAM,qBAAqB,QAAQ,EAAE,MAAM,CAAC;CAE3D,IAAI,CAAC,QACH,MAAM,IAAI,aAAa,sBAAsB;CAG/C,oBAAI,IAAI,KAAK,IAAI,OAAO,WAAW;EACjC,MAAM,qBAAqB,UAAU,EAAE,MAAM,CAAC;EAC9C,MAAM,IAAI,aAAa,sBAAsB;CAC/C;CAEA,MAAM,CAAC,MAAM,SAAS,gBAAgB,MAAM,QAAQ,IAAI;EACtD,YAAY,OAAO,OAAO,MAAM,CAAC;EACjC,eAAe,OAAO,SAAS;EAC/B,oBAAoB,OAAO,cAAc;CAC3C,CAAC;CAED,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cACxB,MAAM,IAAI,aAAa,sBAAsB;CAG/C,OAAO;EACL,MAAM,aAAa,IAAI;EACvB,SAAS,gBAAgB,OAAO;EAChC,cAAc,qBAAqB,YAAY;EAC/C,UAAU;CACZ;AACF"}
1
+ {"version":3,"file":"cliSessionToken.service.mjs","names":[],"sources":["../../../src/services/cliSessionToken.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { CliSessionTokenModel } from '@models/cliSessionToken.model';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport type { Types } from 'mongoose';\nimport type { SessionContext } from '@/types/session.types';\n\nexport const CLI_SESSION_TOKEN_PREFIX = 'clisession_';\nconst CLI_SESSION_EXPIRES_MS = 2 * 60 * 60 * 1000; // 2 hours\n\nexport const isCliSessionToken = (token: string): boolean =>\n token.startsWith(CLI_SESSION_TOKEN_PREFIX);\n\nexport const createCliSessionToken = async (\n userId: string | Types.ObjectId,\n organizationId: string,\n projectId: string\n): Promise<{ token: string; expiresAt: Date }> => {\n const token = CLI_SESSION_TOKEN_PREFIX + randomBytes(32).toString('hex');\n const expiresAt = new Date(Date.now() + CLI_SESSION_EXPIRES_MS);\n\n await CliSessionTokenModel.create({\n token,\n userId,\n organizationId,\n projectId,\n expiresAt,\n });\n\n return { token, expiresAt };\n};\n\nexport const getCliSessionTokenContext = async (\n token: string\n): Promise<SessionContext> => {\n if (!isCliSessionToken(token)) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const stored = await CliSessionTokenModel.findOne({ token: String(token) });\n\n if (!stored) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n if (new Date() > stored.expiresAt) {\n await CliSessionTokenModel.deleteOne({ token: String(token) });\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n const [user, project, organization] = await Promise.all([\n getUserById(String(stored.userId)),\n getProjectById(stored.projectId),\n getOrganizationById(stored.organizationId),\n ]);\n\n if (!user || !project || !organization) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n return {\n user: mapUserToAPI(user),\n project: mapProjectToAPI(project),\n organization: mapOrganizationToAPI(organization),\n authType: 'session',\n };\n};\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,2BAA2B;AACxC,MAAM,yBAAyB,OAAc;AAE7C,MAAa,qBAAqB,UAChC,MAAM,WAAW,wBAAwB;AAE3C,MAAa,wBAAwB,OACnC,QACA,gBACA,cACgD;CAChD,MAAM,QAAQ,2BAA2B,YAAY,EAAE,EAAE,SAAS,KAAK;CACvE,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,sBAAsB;CAE9D,MAAM,qBAAqB,OAAO;EAChC;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OAAO;EAAE;EAAO;CAAU;AAC5B;AAEA,MAAa,4BAA4B,OACvC,UAC4B;CAC5B,IAAI,CAAC,kBAAkB,KAAK,GAC1B,MAAM,IAAI,aAAa,sBAAsB;CAG/C,MAAM,SAAS,MAAM,qBAAqB,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;CAE1E,IAAI,CAAC,QACH,MAAM,IAAI,aAAa,sBAAsB;CAG/C,oBAAI,IAAI,KAAK,IAAI,OAAO,WAAW;EACjC,MAAM,qBAAqB,UAAU,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;EAC7D,MAAM,IAAI,aAAa,sBAAsB;CAC/C;CAEA,MAAM,CAAC,MAAM,SAAS,gBAAgB,MAAM,QAAQ,IAAI;EACtD,YAAY,OAAO,OAAO,MAAM,CAAC;EACjC,eAAe,OAAO,SAAS;EAC/B,oBAAoB,OAAO,cAAc;CAC3C,CAAC;CAED,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cACxB,MAAM,IAAI,aAAa,sBAAsB;CAG/C,OAAO;EACL,MAAM,aAAa,IAAI;EACvB,SAAS,gBAAgB,OAAO;EAChC,cAAc,qBAAqB,YAAY;EAC/C,UAAU;CACZ;AACF"}
@@ -128,7 +128,7 @@ const updateDictionaryById = async (dictionaryId, dictionary) => {
128
128
  */
129
129
  const updateDictionaryByKey = async (dictionaryKey, dictionary, projectId) => {
130
130
  const existing = await DictionaryModel.findOne({
131
- key: dictionaryKey,
131
+ key: String(dictionaryKey),
132
132
  projectIds: projectId
133
133
  });
134
134
  if (!existing) throw new GenericError("DICTIONARY_UPDATE_FAILED", { dictionaryKey });
@@ -1 +1 @@
1
- {"version":3,"file":"dictionary.service.mjs","names":[],"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { DictionaryModel } from '@models/dictionary.model';\nimport { getDemoDictionaries } from '@utils/demoDictionaries';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport { Types } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @param sortOptions - Sorting options.\n * @param includeContent - Whether to include the dictionary content.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>,\n includeContent = true\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Sort if provided (default handled in filter builder)\n ...(sortOptions && Object.keys(sortOptions).length > 0\n ? [{ $sort: sortOptions }]\n : []),\n\n // Stage 3: Skip for pagination\n { $skip: skip },\n\n // Stage 4: Limit the number of documents\n { $limit: limit },\n\n // Stage 5: Project to include/exclude content\n ...(!includeContent ? [{ $project: { content: 0 } }] : []),\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n logger.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'versions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const id = Types.ObjectId.isValid(dictionaryId as string)\n ? new Types.ObjectId(dictionaryId as string)\n : dictionaryId;\n\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: id } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | Types.ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as unknown as Partial<Dictionary>;\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const existing = await DictionaryModel.findOne({\n key: dictionaryKey,\n projectIds: projectId,\n });\n\n if (!existing) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as Partial<Dictionary>;\n\n // Optional: run your validateDictionary on dictionaryToUpdate here\n\n // Apply updated fields onto the existing doc\n Object.assign(existing, dictionaryToUpdate);\n\n // Mongoose cannot track deep Map mutations done via Object.assign, so we\n // must explicitly mark 'content' as modified, otherwise the new versioned\n // content is silently dropped and the document is saved unchanged.\n existing.markModified('content');\n\n // Save – this will trigger timestamps on parent + subdocs\n await existing.save();\n\n return existing;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\n// Function to extract the numeric part of the version\nconst getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n\n const versions = [...(dictionary.content.keys() ?? [])];\n const lastVersion = versions[versions.length - 1];\n\n // Start with the next version number\n let newNumber = getVersionNumber(lastVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (versions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n\n/**\n * Creates demo dictionaries for a project.\n * @param projectIds - List of project IDs.\n * @param creatorId - The ID of the user creating the demo content.\n */\nexport const createDemoDictionaries = async (\n projectIds: string[],\n creatorId: Types.ObjectId | string\n): Promise<void> => {\n const demoDictionaries = getDemoDictionaries(projectIds, creatorId);\n\n for (const dictionary of demoDictionaries) {\n await createDictionary(dictionary);\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dictionary.service.mjs","names":[],"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { DictionaryModel } from '@models/dictionary.model';\nimport { getDemoDictionaries } from '@utils/demoDictionaries';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport { Types } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @param sortOptions - Sorting options.\n * @param includeContent - Whether to include the dictionary content.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>,\n includeContent = true\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Sort if provided (default handled in filter builder)\n ...(sortOptions && Object.keys(sortOptions).length > 0\n ? [{ $sort: sortOptions }]\n : []),\n\n // Stage 3: Skip for pagination\n { $skip: skip },\n\n // Stage 4: Limit the number of documents\n { $limit: limit },\n\n // Stage 5: Project to include/exclude content\n ...(!includeContent ? [{ $project: { content: 0 } }] : []),\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n logger.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'versions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const id = Types.ObjectId.isValid(dictionaryId as string)\n ? new Types.ObjectId(dictionaryId as string)\n : dictionaryId;\n\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: id } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | Types.ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as unknown as Partial<Dictionary>;\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const existing = await DictionaryModel.findOne({\n key: String(dictionaryKey),\n projectIds: projectId,\n });\n\n if (!existing) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as Partial<Dictionary>;\n\n // Optional: run your validateDictionary on dictionaryToUpdate here\n\n // Apply updated fields onto the existing doc\n Object.assign(existing, dictionaryToUpdate);\n\n // Mongoose cannot track deep Map mutations done via Object.assign, so we\n // must explicitly mark 'content' as modified, otherwise the new versioned\n // content is silently dropped and the document is saved unchanged.\n existing.markModified('content');\n\n // Save – this will trigger timestamps on parent + subdocs\n await existing.save();\n\n return existing;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\n// Function to extract the numeric part of the version\nconst getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n\n const versions = [...(dictionary.content.keys() ?? [])];\n const lastVersion = versions[versions.length - 1];\n\n // Start with the next version number\n let newNumber = getVersionNumber(lastVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (versions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n\n/**\n * Creates demo dictionaries for a project.\n * @param projectIds - List of project IDs.\n * @param creatorId - The ID of the user creating the demo content.\n */\nexport const createDemoDictionaries = async (\n projectIds: string[],\n creatorId: Types.ObjectId | string\n): Promise<void> => {\n const demoDictionaries = getDemoDictionaries(projectIds, creatorId);\n\n for (const dictionary of demoDictionaries) {\n await createDictionary(dictionary);\n }\n};\n"],"mappings":"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"}
@@ -30,7 +30,7 @@ const generateClientCredentials = () => {
30
30
  * @returns The an object containing the client, the rights and the project or false if not found
31
31
  */
32
32
  const getClientAndProjectByClientId = async (clientId) => {
33
- const project = await ProjectModel.findOne({ "oAuth2Access.clientId": clientId });
33
+ const project = await ProjectModel.findOne({ "oAuth2Access.clientId": String(clientId) });
34
34
  if (!project) return false;
35
35
  const oAuth2Access = project.oAuth2Access.find((access) => access.clientId === clientId);
36
36
  if (!oAuth2Access) return false;
@@ -127,7 +127,7 @@ const saveToken = async (token, client, user) => {
127
127
  */
128
128
  const extendOAuth2AccessToken = async (accessToken) => {
129
129
  const nextExpiresAt = getTokenExpireAt();
130
- await OAuth2AccessTokenModel.updateOne({ accessToken }, { $set: {
130
+ await OAuth2AccessTokenModel.updateOne({ accessToken: String(accessToken) }, { $set: {
131
131
  accessTokenExpiresAt: nextExpiresAt,
132
132
  expiresIn: nextExpiresAt
133
133
  } });
@@ -140,7 +140,7 @@ const extendOAuth2AccessToken = async (accessToken) => {
140
140
  * @returns The access token or false if not found
141
141
  */
142
142
  const getAccessToken = async (accessToken) => {
143
- const token = await OAuth2AccessTokenModel.findOne({ accessToken });
143
+ const token = await OAuth2AccessTokenModel.findOne({ accessToken: String(accessToken) });
144
144
  if (!token) return false;
145
145
  const currentExpiresAt = token.accessTokenExpiresAt ?? token.expiresIn;
146
146
  if (currentExpiresAt && shouldExtendOAuth2Token(currentExpiresAt)) {