@intlayer/backend 8.10.0 → 8.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10948 -8936
  2. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/interest_of_intlayer.json +1 -14957
  3. package/dist/esm/controllers/ai.controller.mjs +10 -9
  4. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  5. package/dist/esm/controllers/audit.controller.mjs +1 -1
  6. package/dist/esm/controllers/audit.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/organization.controller.mjs +1 -1
  8. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/project.controller.mjs +2 -2
  10. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/showcaseProject.controller.mjs +0 -2
  12. package/dist/esm/controllers/showcaseProject.controller.mjs.map +1 -1
  13. package/dist/esm/controllers/translation.controller.mjs +0 -1
  14. package/dist/esm/controllers/translation.controller.mjs.map +1 -1
  15. package/dist/esm/controllers/user.controller.mjs +0 -6
  16. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  17. package/dist/esm/schemas/account.schema.mjs +3 -2
  18. package/dist/esm/schemas/account.schema.mjs.map +1 -1
  19. package/dist/esm/{models/audit.model.mjs → schemas/audit.schema.mjs} +3 -3
  20. package/dist/esm/schemas/audit.schema.mjs.map +1 -0
  21. package/dist/esm/{models/auditJob.model.mjs → schemas/auditJob.schema.mjs} +3 -3
  22. package/dist/esm/schemas/auditJob.schema.mjs.map +1 -0
  23. package/dist/esm/{models/auditPage.model.mjs → schemas/auditPage.schema.mjs} +3 -3
  24. package/dist/esm/schemas/auditPage.schema.mjs.map +1 -0
  25. package/dist/esm/schemas/cliSessionToken.schema.mjs +3 -2
  26. package/dist/esm/schemas/cliSessionToken.schema.mjs.map +1 -1
  27. package/dist/esm/schemas/dictionary.schema.mjs +3 -2
  28. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  29. package/dist/esm/schemas/discussion.schema.mjs +3 -2
  30. package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
  31. package/dist/esm/schemas/oAuth2.schema.mjs +3 -2
  32. package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
  33. package/dist/esm/schemas/organization.schema.mjs +3 -2
  34. package/dist/esm/schemas/organization.schema.mjs.map +1 -1
  35. package/dist/esm/schemas/project.schema.mjs +3 -2
  36. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  37. package/dist/esm/schemas/session.schema.mjs +3 -2
  38. package/dist/esm/schemas/session.schema.mjs.map +1 -1
  39. package/dist/esm/schemas/showcaseProject.schema.mjs +3 -2
  40. package/dist/esm/schemas/showcaseProject.schema.mjs.map +1 -1
  41. package/dist/esm/schemas/tag.schema.mjs +3 -2
  42. package/dist/esm/schemas/tag.schema.mjs.map +1 -1
  43. package/dist/esm/schemas/user.schema.mjs +3 -2
  44. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  45. package/dist/esm/services/audit/recursiveAudit.service.mjs +2 -2
  46. package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
  47. package/dist/esm/services/bitbucket.service.mjs +1 -1
  48. package/dist/esm/services/bitbucket.service.mjs.map +1 -1
  49. package/dist/esm/services/cliSessionToken.service.mjs +1 -1
  50. package/dist/esm/services/cliSessionToken.service.mjs.map +1 -1
  51. package/dist/esm/services/dictionary.service.mjs +1 -1
  52. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  53. package/dist/esm/services/github.service.mjs +1 -1
  54. package/dist/esm/services/github.service.mjs.map +1 -1
  55. package/dist/esm/services/gitlab.service.mjs +1 -1
  56. package/dist/esm/services/gitlab.service.mjs.map +1 -1
  57. package/dist/esm/services/oAuth2.service.mjs +2 -2
  58. package/dist/esm/services/oAuth2.service.mjs.map +1 -1
  59. package/dist/esm/services/organization.service.mjs +1 -1
  60. package/dist/esm/services/organization.service.mjs.map +1 -1
  61. package/dist/esm/services/project.service.mjs +1 -1
  62. package/dist/esm/services/project.service.mjs.map +1 -1
  63. package/dist/esm/services/projectAccessKey.service.mjs +1 -1
  64. package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
  65. package/dist/esm/services/showcase/showcaseProject.service.mjs +1 -1
  66. package/dist/esm/services/showcase/showcaseProject.service.mjs.map +1 -1
  67. package/dist/esm/services/tag.service.mjs +1 -1
  68. package/dist/esm/services/tag.service.mjs.map +1 -1
  69. package/dist/esm/services/user.service.mjs +1 -1
  70. package/dist/esm/services/user.service.mjs.map +1 -1
  71. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10948 -8936
  72. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/interest_of_intlayer.json +1 -14957
  73. package/dist/esm/utils/AI/auditDictionaryField/index.mjs +9 -0
  74. package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
  75. package/dist/esm/utils/AI/getProjectAIOptions.mjs +20 -0
  76. package/dist/esm/utils/AI/getProjectAIOptions.mjs.map +1 -0
  77. package/dist/esm/utils/errors/ErrorHandler.mjs +40 -8
  78. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  79. package/dist/esm/utils/mapper/project.mjs +7 -1
  80. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  81. package/dist/esm/utils/mongoDB/connectDB.mjs +12 -12
  82. package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
  83. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  84. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  85. package/dist/types/controllers/showcaseProject.controller.d.ts.map +1 -1
  86. package/dist/types/controllers/translation.controller.d.ts.map +1 -1
  87. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  88. package/dist/types/schemas/account.schema.d.ts +3 -2
  89. package/dist/types/schemas/account.schema.d.ts.map +1 -1
  90. package/dist/types/schemas/audit.schema.d.ts +64 -0
  91. package/dist/types/schemas/audit.schema.d.ts.map +1 -0
  92. package/dist/types/schemas/auditJob.schema.d.ts +122 -0
  93. package/dist/types/schemas/auditJob.schema.d.ts.map +1 -0
  94. package/dist/types/schemas/auditPage.schema.d.ts +120 -0
  95. package/dist/types/schemas/auditPage.schema.d.ts.map +1 -0
  96. package/dist/types/schemas/cliSessionToken.schema.d.ts +10 -3
  97. package/dist/types/schemas/cliSessionToken.schema.d.ts.map +1 -1
  98. package/dist/types/schemas/dictionary.schema.d.ts +22 -13
  99. package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
  100. package/dist/types/schemas/discussion.schema.d.ts +19 -14
  101. package/dist/types/schemas/discussion.schema.d.ts.map +1 -1
  102. package/dist/types/schemas/oAuth2.schema.d.ts +13 -3
  103. package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
  104. package/dist/types/schemas/organization.schema.d.ts +7 -6
  105. package/dist/types/schemas/organization.schema.d.ts.map +1 -1
  106. package/dist/types/schemas/plans.schema.d.ts +7 -7
  107. package/dist/types/schemas/project.schema.d.ts +7 -6
  108. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  109. package/dist/types/schemas/session.schema.d.ts +11 -10
  110. package/dist/types/schemas/session.schema.d.ts.map +1 -1
  111. package/dist/types/schemas/showcaseProject.schema.d.ts +16 -15
  112. package/dist/types/schemas/showcaseProject.schema.d.ts.map +1 -1
  113. package/dist/types/schemas/tag.schema.d.ts +9 -8
  114. package/dist/types/schemas/tag.schema.d.ts.map +1 -1
  115. package/dist/types/schemas/user.schema.d.ts +8 -7
  116. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  117. package/dist/types/services/audit/recursiveAudit.service.d.ts +2 -2
  118. package/dist/types/types/project.types.d.ts +2 -1
  119. package/dist/types/types/project.types.d.ts.map +1 -1
  120. package/dist/types/utils/AI/getProjectAIOptions.d.ts +15 -0
  121. package/dist/types/utils/AI/getProjectAIOptions.d.ts.map +1 -0
  122. package/dist/types/utils/errors/ErrorHandler.d.ts +4 -4
  123. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  124. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +3 -3
  125. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  126. package/package.json +5 -5
  127. package/dist/esm/models/account.model.mjs +0 -9
  128. package/dist/esm/models/account.model.mjs.map +0 -1
  129. package/dist/esm/models/audit.model.mjs.map +0 -1
  130. package/dist/esm/models/auditJob.model.mjs.map +0 -1
  131. package/dist/esm/models/auditPage.model.mjs.map +0 -1
  132. package/dist/esm/models/cliSessionToken.model.mjs +0 -9
  133. package/dist/esm/models/cliSessionToken.model.mjs.map +0 -1
  134. package/dist/esm/models/dictionary.model.mjs +0 -9
  135. package/dist/esm/models/dictionary.model.mjs.map +0 -1
  136. package/dist/esm/models/discussion.model.mjs +0 -9
  137. package/dist/esm/models/discussion.model.mjs.map +0 -1
  138. package/dist/esm/models/oAuth2.model.mjs +0 -9
  139. package/dist/esm/models/oAuth2.model.mjs.map +0 -1
  140. package/dist/esm/models/organization.model.mjs +0 -9
  141. package/dist/esm/models/organization.model.mjs.map +0 -1
  142. package/dist/esm/models/project.model.mjs +0 -9
  143. package/dist/esm/models/project.model.mjs.map +0 -1
  144. package/dist/esm/models/session.model.mjs +0 -9
  145. package/dist/esm/models/session.model.mjs.map +0 -1
  146. package/dist/esm/models/showcaseProject.model.mjs +0 -9
  147. package/dist/esm/models/showcaseProject.model.mjs.map +0 -1
  148. package/dist/esm/models/tag.model.mjs +0 -9
  149. package/dist/esm/models/tag.model.mjs.map +0 -1
  150. package/dist/esm/models/user.model.mjs +0 -9
  151. package/dist/esm/models/user.model.mjs.map +0 -1
  152. package/dist/types/models/account.model.d.ts +0 -7
  153. package/dist/types/models/account.model.d.ts.map +0 -1
  154. package/dist/types/models/audit.model.d.ts +0 -18
  155. package/dist/types/models/audit.model.d.ts.map +0 -1
  156. package/dist/types/models/auditJob.model.d.ts +0 -31
  157. package/dist/types/models/auditJob.model.d.ts.map +0 -1
  158. package/dist/types/models/auditPage.model.d.ts +0 -29
  159. package/dist/types/models/auditPage.model.d.ts.map +0 -1
  160. package/dist/types/models/cliSessionToken.model.d.ts +0 -14
  161. package/dist/types/models/cliSessionToken.model.d.ts.map +0 -1
  162. package/dist/types/models/dictionary.model.d.ts +0 -16
  163. package/dist/types/models/dictionary.model.d.ts.map +0 -1
  164. package/dist/types/models/discussion.model.d.ts +0 -12
  165. package/dist/types/models/discussion.model.d.ts.map +0 -1
  166. package/dist/types/models/oAuth2.model.d.ts +0 -18
  167. package/dist/types/models/oAuth2.model.d.ts.map +0 -1
  168. package/dist/types/models/organization.model.d.ts +0 -7
  169. package/dist/types/models/organization.model.d.ts.map +0 -1
  170. package/dist/types/models/project.model.d.ts +0 -7
  171. package/dist/types/models/project.model.d.ts.map +0 -1
  172. package/dist/types/models/session.model.d.ts +0 -7
  173. package/dist/types/models/session.model.d.ts.map +0 -1
  174. package/dist/types/models/showcaseProject.model.d.ts +0 -7
  175. package/dist/types/models/showcaseProject.model.d.ts.map +0 -1
  176. package/dist/types/models/tag.model.d.ts +0 -7
  177. package/dist/types/models/tag.model.d.ts.map +0 -1
  178. package/dist/types/models/user.model.d.ts +0 -7
  179. package/dist/types/models/user.model.d.ts.map +0 -1
@@ -1 +1 @@
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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"}
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 '@schemas/session.schema';\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 (\n projectConfiguration.ai &&\n !projectConfiguration.ai.apiKey &&\n projectObject.configuration?.ai?.apiKey\n ) {\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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"}
@@ -90,7 +90,6 @@ const submitShowcaseProject = async (request, reply) => {
90
90
  });
91
91
  return reply.send(responseData);
92
92
  } catch (error) {
93
- logger.error("[submitShowcaseProject] Error:", error);
94
93
  return ErrorHandler.handleAppErrorResponse(reply, error);
95
94
  }
96
95
  };
@@ -243,7 +242,6 @@ const updateShowcaseProjectHandler = async (request, reply) => {
243
242
  data: mapShowcaseProjectToAPI(updated, userId)
244
243
  }));
245
244
  } catch (error) {
246
- logger.error("[updateShowcaseProjectHandler] Error:", error);
247
245
  return ErrorHandler.handleAppErrorResponse(reply, error);
248
246
  }
249
247
  };
@@ -1 +1 @@
1
- {"version":3,"file":"showcaseProject.controller.mjs","names":["showcaseProjectService.findShowcaseProjectByUrl","showcaseProjectService.createShowcaseProject","showcaseProjectService.findShowcaseProjects","showcaseProjectService.findShowcaseProjectById","showcaseProjectService.findOtherShowcaseProjects","showcaseProjectService.toggleShowcaseUpvote","showcaseProjectService.toggleShowcaseDownvote","showcaseProjectService.updateShowcaseProject","showcaseProjectService.deleteShowcaseProject","scanShowcaseProjectViaService"],"sources":["../../../src/controllers/showcaseProject.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport * as showcaseProjectService from '@services/showcase/showcaseProject.service';\nimport { scanShowcaseProject as scanShowcaseProjectViaService } from '@services/showcase/showcaseScan.service';\nimport {\n deleteShowcaseScreenshot,\n uploadShowcaseScreenshot,\n} from '@services/showcase/showcaseUploadScreenshot.service';\nimport { verifyGithubRepo } from '@services/showcase/showcaseVerifyGithub.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { getFaviconUrl } from '@utils/getFaviconUrl';\nimport {\n mapShowcaseProjectsToAPI,\n mapShowcaseProjectToAPI,\n} from '@utils/mapper/showcaseProject';\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 { z } from 'zod';\nimport type { ShowcaseProjectAPI } from '@/types/showcaseProject.types';\n\nconst getUserId = (request: FastifyRequest): string | undefined =>\n request.session?.user\n ? String(request.session.user.id ?? (request.session.user as any)._id)\n : undefined;\n\nconst urlSchema = z\n .url()\n .optional()\n .or(z.literal(''))\n .transform((value) => (value === '' ? undefined : value));\n\nconst submitProjectSchema = z.object({\n name: z.string().min(1),\n url: z\n .url()\n .refine((val) => !/github\\.com|gitlab\\.com|bitbucket\\.org/.test(val), {\n message: 'Repository URLs should be placed in the GitHub URL field',\n }),\n githubUrl: urlSchema,\n useCases: z.array(z.string()).max(3).optional(),\n});\nexport type SubmitShowcaseProjectBody = z.input<typeof submitProjectSchema>;\nexport type SubmitShowcaseProjectResult = ResponseData<ShowcaseProjectAPI>;\n\n/**\n * POST /api/showcase-project/submit\n * Submits a new project to the showcase.\n */\nexport const submitShowcaseProject = async (\n request: FastifyRequest<{ Body: SubmitShowcaseProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const parsed = submitProjectSchema.safeParse(request.body);\n\n if (!parsed.success) {\n const message = parsed.error.issues\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join(', ');\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { message }\n );\n }\n\n const validatedData = parsed.data;\n\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n try {\n // Check for existing project with the same URL\n const existing = await showcaseProjectService.findShowcaseProjectByUrl(\n validatedData.url\n );\n\n if (existing) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SHOWCASE_PROJECT_URL_ALREADY_EXISTS',\n { url: validatedData.url }\n );\n }\n\n // Save project to DB immediately — scan is triggered separately by the owner\n // tagline and description will be populated automatically during the scan step\n const logoUrl = getFaviconUrl(validatedData.url);\n const newProject = await showcaseProjectService.createShowcaseProject({\n title: validatedData.name,\n description: '',\n websiteUrl: validatedData.url,\n githubUrl: validatedData.githubUrl,\n logoUrl: logoUrl ?? '',\n tags: validatedData.useCases || [],\n owner: userId,\n status: 'pending_scan',\n });\n\n const responseData = formatResponse<ShowcaseProjectAPI>({\n message: t({\n en: 'Project submitted successfully',\n 'en-GB': 'Project submitted successfully',\n fr: 'Projet soumis avec succès',\n es: 'Proyecto enviado con éxito',\n ru: 'Проект успешно отправлен',\n ja: 'プロジェクトが正常に送信されました',\n ko: '프로젝트가 성공적으로 제출되었습니다',\n zh: '项目已成功提交',\n de: 'Projekt erfolgreich eingereicht',\n ar: 'تم تقديم المشروع بنجاح',\n it: 'Progetto inviato con successo',\n pt: 'Projeto enviado con sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक सबमिट किया गया',\n tr: 'Proje başarıyla gönderildi',\n pl: 'Projekt został pomyślnie przesłany',\n id: 'Proyek berhasil dikirim',\n vi: 'Dự án đã được gửi thành công',\n uk: 'Проєкт успішно надіслано',\n }),\n description: t({\n en: 'Your project has been added to the showcase. Use the Scan button to verify and enrich it.',\n 'en-GB':\n 'Your project has been added to the showcase. Use the Scan button to verify and enrich it.',\n fr: \"Votre projet a été ajouté à la vitrine. Utilisez le bouton Scan pour le vérifier et l'enrichir.\",\n es: 'Su proyecto ha sido añadido al showcase. Use el botón Scan para verificarlo y enriquecerlo.',\n ru: 'Ваш проект был добавлен в витрину. Используйте кнопку Сканировать, чтобы проверить и дополнить его.',\n ja: 'プロジェクトがショーケースに追加されました。「スキャン」ボタンを使用して、検証と充実を行ってください。',\n ko: '프로젝트가 쇼케이스에 추가되었습니다. 스캔 버튼을 사용하여 확인하고 보완하십시오.',\n zh: '您的项目已添加到展示墙。使用“扫描”按钮进行验证和完善。',\n de: 'Ihr Projekt wurde zum Showcase hinzugefügt. Verwenden Sie die Schaltfläche Scan, um es zu überprüfen und zu erweitern.',\n ar: 'تمت إضافة مشروعك إلى المعرض. استخدم زر المسح للتحقق منه وإثرائه.',\n it: 'Il tuo progetto è stato aggiunto alla vetrina. Usa il pulsante Scansiona per verificarlo e arricchirlo.',\n pt: 'Seu projeto foi adicionado ao showcase. Use o botão Scan para verificá-lo e enriquecê-lo.',\n hi: 'आपका प्रोजेक्ट शोकेस में जोड़ दिया गया है। इसे सत्यापित करने और समृद्ध करने के लिए स्कैन बटन का उपयोग करें।',\n tr: 'Projeniz vitrine eklendi. Doğrulamak ve zenginleştirmek için Tara düğmesini kullanın.',\n pl: 'Twój projekt został dodany do witryny. Użyj przycisku Skanuj, aby go zweryfikować i wzbogacić.',\n id: 'Proyek Anda telah ditambahkan ke showcase. Gunakan tombol Pindai untuk memverifikasi dan memperkayanya.',\n vi: 'Dự án của bạn đã được thêm vào showcase. Sử dụng nút Quét để xác minh và làm phong phú nó.',\n uk: 'Ваш проєкт додано до вітрини. Скористайтеся кнопкою Сканувати, щоб перевірити та доповнити його.',\n }),\n data: mapShowcaseProjectToAPI(newProject, userId),\n });\n\n return reply.send(responseData);\n } catch (error) {\n logger.error('[submitShowcaseProject] Error:', error);\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type GetShowcaseProjectsQuery = {\n page?: string;\n pageSize?: string;\n search?: string;\n selectedUseCases?: string | string[];\n isOpenSource?: string;\n};\nexport type GetShowcaseProjectsResult = PaginatedResponse<ShowcaseProjectAPI>;\n\n/**\n * GET /api/showcase-project\n */\nexport const getShowcaseProjects = async (\n request: FastifyRequest<{ Querystring: GetShowcaseProjectsQuery }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n page: pageStr = '1',\n pageSize: pageSizeStr = '20',\n search = '',\n selectedUseCases,\n isOpenSource: isOpenSourceStr = 'false',\n } = request.query;\n\n const page = parseInt(pageStr, 10) || 1;\n const pageSize = parseInt(pageSizeStr, 10) || 20;\n const isOpenSource = isOpenSourceStr === 'true';\n\n const useCasesArray = selectedUseCases\n ? Array.isArray(selectedUseCases)\n ? selectedUseCases\n : [selectedUseCases]\n : [];\n\n try {\n const { data, total_items, total_pages } =\n await showcaseProjectService.findShowcaseProjects({\n search,\n selectedUseCases: useCasesArray,\n isOpenSource,\n page,\n pageSize,\n });\n\n const userId = getUserId(request);\n\n const responseData = formatPaginatedResponse<ShowcaseProjectAPI>({\n data: mapShowcaseProjectsToAPI(data, userId),\n page,\n pageSize,\n totalPages: total_pages,\n totalItems: total_items,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type GetShowcaseProjectByIdParams = { projectId: string };\nexport type GetShowcaseProjectByIdResult = ResponseData<ShowcaseProjectAPI>;\n\n/**\n * GET /api/showcase-project/:projectId\n */\nexport const getShowcaseProjectById = async (\n request: FastifyRequest<{ Params: GetShowcaseProjectByIdParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { projectId } = request.params;\n\n try {\n const project =\n await showcaseProjectService.findShowcaseProjectById(projectId);\n\n const userId = getUserId(request);\n\n return reply.send(\n formatResponse<ShowcaseProjectAPI>({\n data: mapShowcaseProjectToAPI(project, userId),\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type GetOtherShowcaseProjectsQuery = {\n excludeId: string;\n limit?: string;\n};\nexport type GetOtherShowcaseProjectsResult = ResponseData<ShowcaseProjectAPI[]>;\n\n/**\n * GET /api/showcase-project/others?excludeId=...&limit=...\n */\nexport const getOtherShowcaseProjects = async (\n request: FastifyRequest<{ Querystring: GetOtherShowcaseProjectsQuery }>,\n reply: FastifyReply\n): Promise<void> => {\n const { excludeId, limit: limitStr } = request.query;\n const limit = limitStr ? parseInt(limitStr, 10) : 4;\n\n if (!excludeId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SHOWCASE_PROJECT_NOT_FOUND',\n { detail: 'excludeId is required' }\n );\n }\n\n try {\n const projects = await showcaseProjectService.findOtherShowcaseProjects(\n excludeId,\n limit\n );\n\n const userId = getUserId(request);\n\n return reply.send(\n formatResponse<ShowcaseProjectAPI[]>({\n data: mapShowcaseProjectsToAPI(projects, userId),\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ToggleShowcaseUpvoteBody = { projectId: string };\nexport type ToggleShowcaseUpvoteResult = ResponseData<{\n upvotes: number;\n isUpVoted: boolean;\n downvotes: number;\n isDownVoted: boolean;\n}>;\n\n/**\n * POST /api/showcase-project/upvote\n * Requires authentication — userId is read from the session.\n */\nexport const toggleShowcaseUpvote = async (\n request: FastifyRequest<{ Body: ToggleShowcaseUpvoteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const { projectId } = request.body;\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { detail: 'projectId is required' }\n );\n }\n\n try {\n const result = await showcaseProjectService.toggleShowcaseUpvote(\n projectId,\n userId\n );\n\n return reply.send(formatResponse({ data: result }));\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ToggleShowcaseDownvoteBody = { projectId: string };\nexport type ToggleShowcaseDownvoteResult = ResponseData<{\n upvotes: number;\n isUpVoted: boolean;\n downvotes: number;\n isDownVoted: boolean;\n}>;\n\n/**\n * POST /api/showcase-project/downvote\n * Requires authentication — userId is read from the session.\n */\nexport const toggleShowcaseDownvote = async (\n request: FastifyRequest<{ Body: ToggleShowcaseDownvoteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const { projectId } = request.body;\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { detail: 'projectId is required' }\n );\n }\n\n try {\n const result = await showcaseProjectService.toggleShowcaseDownvote(\n projectId,\n userId\n );\n\n return reply.send(formatResponse({ data: result }));\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nconst updateProjectSchema = z.object({\n name: z.string().min(1).max(255).optional(),\n url: z\n .url()\n .refine((val) => !/github\\.com|gitlab\\.com|bitbucket\\.org/.test(val), {\n message: 'Repository URLs should be placed in the GitHub URL field',\n })\n .optional(),\n githubUrl: z\n .string()\n .optional()\n .transform((value) => {\n if (!value) return null;\n if (value.startsWith('http://') || value.startsWith('https://'))\n return value;\n return `https://${value}`;\n }),\n tagline: z.string().min(1).max(500).optional(),\n description: z.string().optional(),\n useCases: z.array(z.string()).max(3).optional(),\n});\n\nexport type UpdateShowcaseProjectBody = z.input<typeof updateProjectSchema>;\nexport type UpdateShowcaseProjectParams = { projectId: string };\nexport type UpdateShowcaseProjectResult = ResponseData<ShowcaseProjectAPI>;\n\n/**\n * PATCH /api/showcase-project/:projectId\n * Updates an existing project. Only the owner can update.\n */\nexport const updateShowcaseProjectHandler = async (\n request: FastifyRequest<{\n Params: UpdateShowcaseProjectParams;\n Body: UpdateShowcaseProjectBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const parsed = updateProjectSchema.safeParse(request.body);\n\n if (!parsed.success) {\n const message = parsed.error.issues\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join(', ');\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { message }\n );\n }\n\n const { projectId } = request.params;\n\n try {\n const project =\n await showcaseProjectService.findShowcaseProjectById(projectId);\n\n if (String(project.owner) !== userId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_ID_MISMATCH');\n }\n\n const { name, url, githubUrl, tagline, useCases } = parsed.data;\n const updates: Parameters<\n typeof showcaseProjectService.updateShowcaseProject\n >[1] = {};\n\n if (name !== undefined) updates.title = name;\n if (url !== undefined) updates.websiteUrl = url;\n if ('githubUrl' in parsed.data) updates.githubUrl = githubUrl;\n if (tagline !== undefined) updates.description = tagline;\n if (useCases !== undefined) updates.tags = useCases;\n\n const updated = await showcaseProjectService.updateShowcaseProject(\n projectId,\n updates\n );\n\n return reply.send(\n formatResponse<ShowcaseProjectAPI>({\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 data: mapShowcaseProjectToAPI(updated, userId),\n })\n );\n } catch (error) {\n logger.error('[updateShowcaseProjectHandler] Error:', error);\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type DeleteShowcaseProjectParams = { projectId: string };\n\n/**\n * DELETE /api/showcase-project/:projectId\n * Deletes a project (DB + R2 screenshot). Only the owner can delete.\n */\nexport const deleteShowcaseProjectHandler = async (\n request: FastifyRequest<{ Params: DeleteShowcaseProjectParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const { projectId } = request.params;\n\n try {\n const project =\n await showcaseProjectService.findShowcaseProjectById(projectId);\n\n if (String(project.owner) !== userId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_ID_MISMATCH');\n }\n\n await Promise.allSettled([\n showcaseProjectService.deleteShowcaseProject(projectId),\n project.imageUrl\n ? deleteShowcaseScreenshot(project.imageUrl)\n : Promise.resolve(),\n ]);\n\n return reply.send(formatResponse({ data: { success: true } }));\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ScanShowcaseProjectParams = { projectId: string };\n\n/**\n * GET /api/showcase-project/:projectId/scan\n * SSE endpoint — streams scan progress to the owner.\n * Requires authentication: only the project owner can trigger the scan.\n */\nexport const scanShowcaseProject = async (\n request: FastifyRequest<{ Params: ScanShowcaseProjectParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n reply.status(401).send({ error: { message: 'Authentication required' } });\n return;\n }\n\n const { projectId } = request.params;\n\n let project: Awaited<\n ReturnType<typeof showcaseProjectService.findShowcaseProjectById>\n >;\n try {\n project = await showcaseProjectService.findShowcaseProjectById(projectId);\n } catch {\n reply.status(404).send({ error: { message: 'Project not found' } });\n return;\n }\n\n if (project.owner !== userId) {\n reply.status(403).send({\n error: { message: 'Only the project owner can trigger a scan' },\n });\n return;\n }\n\n // Hijack the reply and write SSE manually\n reply.hijack();\n const raw = reply.raw;\n const origin = request.headers.origin ?? '*';\n raw.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Access-Control-Allow-Origin': origin,\n 'Access-Control-Allow-Credentials': 'true',\n });\n\n const send = (data: Record<string, unknown>) => {\n raw.write(`data: ${JSON.stringify(data)}\\n\\n`);\n };\n\n // Track the uploaded screenshot URL so we can delete it on failure.\n let uploadedImageUrl: string | null = null;\n\n const cleanup = async (message: string) => {\n await Promise.allSettled([\n showcaseProjectService.deleteShowcaseProject(projectId),\n uploadedImageUrl\n ? deleteShowcaseScreenshot(uploadedImageUrl)\n : Promise.resolve(),\n ]);\n send({ step: 'ERROR', message });\n };\n\n try {\n // ── Step 1: Scan website + take screenshot (single browser session) ────────\n send({ step: 'SCANNING_START' });\n const scanResult = await scanShowcaseProjectViaService(project.websiteUrl);\n\n if (!scanResult.hasIntlayer && !scanResult.libsUsed.includes('intlayer')) {\n await cleanup('Intlayer not detected on this website');\n raw.end();\n return;\n }\n send({ step: 'SCANNING_SUCCESS' });\n\n // ── Step 2: Verify GitHub (if provided) ───────────────────────────────────\n let isOpenSource = false;\n let githubPackageDetails: Record<string, string> = {};\n\n if (project.githubUrl) {\n send({ step: 'VERIFY_GITHUB_START' });\n logger.info(\n `[scanShowcaseProject] Verifying GitHub ${project.githubUrl}...`\n );\n const githubResult = await verifyGithubRepo(project.githubUrl);\n if (githubResult?.isValid) {\n isOpenSource = true;\n githubPackageDetails = githubResult.packageDetails ?? {};\n }\n send({ step: 'VERIFY_GITHUB_SUCCESS' });\n }\n\n // ── Step 3: Upload the screenshot captured during scan ────────────────────\n send({ step: 'SCREENSHOT_START' });\n if (scanResult.screenshotBuffer) {\n uploadedImageUrl = await uploadShowcaseScreenshot(\n scanResult.screenshotBuffer,\n project.websiteUrl\n );\n }\n send({ step: 'SCREENSHOT_SUCCESS' });\n\n // ── Step 4: Merge & save ──────────────────────────────────────────────────\n const mergedPackageDetails: Record<string, string> = {\n ...(scanResult.packageDetails ?? {}),\n ...githubPackageDetails,\n };\n const mergedLibsUsed = Array.from(\n new Set([...scanResult.libsUsed, ...Object.keys(githubPackageDetails)])\n );\n const intlayerVersion =\n mergedPackageDetails.intlayer || scanResult.intlayerVersion;\n\n const updated = await showcaseProjectService.updateShowcaseProject(\n projectId,\n {\n intlayerVersion,\n libsUsed: mergedLibsUsed,\n packageDetails: mergedPackageDetails,\n scanDetails: scanResult.scanDetails,\n imageUrl: uploadedImageUrl ?? project.imageUrl,\n isOpenSource,\n status: 'active',\n lastScanDate: new Date(),\n // Populate tagline/description from page metadata if not already set\n ...((!project.description || project.description === '') &&\n scanResult.metaDescription\n ? { description: scanResult.metaDescription }\n : {}),\n }\n );\n\n send({\n step: 'SUCCESS',\n project: mapShowcaseProjectToAPI(updated, userId),\n });\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Scan failed unexpectedly';\n logger.error('[scanShowcaseProject] Error:', error);\n await cleanup(message);\n }\n\n raw.end();\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"showcaseProject.controller.mjs","names":["showcaseProjectService.findShowcaseProjectByUrl","showcaseProjectService.createShowcaseProject","showcaseProjectService.findShowcaseProjects","showcaseProjectService.findShowcaseProjectById","showcaseProjectService.findOtherShowcaseProjects","showcaseProjectService.toggleShowcaseUpvote","showcaseProjectService.toggleShowcaseDownvote","showcaseProjectService.updateShowcaseProject","showcaseProjectService.deleteShowcaseProject","scanShowcaseProjectViaService"],"sources":["../../../src/controllers/showcaseProject.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport * as showcaseProjectService from '@services/showcase/showcaseProject.service';\nimport { scanShowcaseProject as scanShowcaseProjectViaService } from '@services/showcase/showcaseScan.service';\nimport {\n deleteShowcaseScreenshot,\n uploadShowcaseScreenshot,\n} from '@services/showcase/showcaseUploadScreenshot.service';\nimport { verifyGithubRepo } from '@services/showcase/showcaseVerifyGithub.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { getFaviconUrl } from '@utils/getFaviconUrl';\nimport {\n mapShowcaseProjectsToAPI,\n mapShowcaseProjectToAPI,\n} from '@utils/mapper/showcaseProject';\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 { z } from 'zod';\nimport type { ShowcaseProjectAPI } from '@/types/showcaseProject.types';\n\nconst getUserId = (request: FastifyRequest): string | undefined =>\n request.session?.user\n ? String(request.session.user.id ?? (request.session.user as any)._id)\n : undefined;\n\nconst urlSchema = z\n .url()\n .optional()\n .or(z.literal(''))\n .transform((value) => (value === '' ? undefined : value));\n\nconst submitProjectSchema = z.object({\n name: z.string().min(1),\n url: z\n .url()\n .refine((val) => !/github\\.com|gitlab\\.com|bitbucket\\.org/.test(val), {\n message: 'Repository URLs should be placed in the GitHub URL field',\n }),\n githubUrl: urlSchema,\n useCases: z.array(z.string()).max(3).optional(),\n});\nexport type SubmitShowcaseProjectBody = z.input<typeof submitProjectSchema>;\nexport type SubmitShowcaseProjectResult = ResponseData<ShowcaseProjectAPI>;\n\n/**\n * POST /api/showcase-project/submit\n * Submits a new project to the showcase.\n */\nexport const submitShowcaseProject = async (\n request: FastifyRequest<{ Body: SubmitShowcaseProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const parsed = submitProjectSchema.safeParse(request.body);\n\n if (!parsed.success) {\n const message = parsed.error.issues\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join(', ');\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { message }\n );\n }\n\n const validatedData = parsed.data;\n\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n try {\n // Check for existing project with the same URL\n const existing = await showcaseProjectService.findShowcaseProjectByUrl(\n validatedData.url\n );\n\n if (existing) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SHOWCASE_PROJECT_URL_ALREADY_EXISTS',\n { url: validatedData.url }\n );\n }\n\n // Save project to DB immediately — scan is triggered separately by the owner\n // tagline and description will be populated automatically during the scan step\n const logoUrl = getFaviconUrl(validatedData.url);\n const newProject = await showcaseProjectService.createShowcaseProject({\n title: validatedData.name,\n description: '',\n websiteUrl: validatedData.url,\n githubUrl: validatedData.githubUrl,\n logoUrl: logoUrl ?? '',\n tags: validatedData.useCases || [],\n owner: userId,\n status: 'pending_scan',\n });\n\n const responseData = formatResponse<ShowcaseProjectAPI>({\n message: t({\n en: 'Project submitted successfully',\n 'en-GB': 'Project submitted successfully',\n fr: 'Projet soumis avec succès',\n es: 'Proyecto enviado con éxito',\n ru: 'Проект успешно отправлен',\n ja: 'プロジェクトが正常に送信されました',\n ko: '프로젝트가 성공적으로 제출되었습니다',\n zh: '项目已成功提交',\n de: 'Projekt erfolgreich eingereicht',\n ar: 'تم تقديم المشروع بنجاح',\n it: 'Progetto inviato con successo',\n pt: 'Projeto enviado con sucesso',\n hi: 'प्रोजेक्ट सफलतापूर्वक सबमिट किया गया',\n tr: 'Proje başarıyla gönderildi',\n pl: 'Projekt został pomyślnie przesłany',\n id: 'Proyek berhasil dikirim',\n vi: 'Dự án đã được gửi thành công',\n uk: 'Проєкт успішно надіслано',\n }),\n description: t({\n en: 'Your project has been added to the showcase. Use the Scan button to verify and enrich it.',\n 'en-GB':\n 'Your project has been added to the showcase. Use the Scan button to verify and enrich it.',\n fr: \"Votre projet a été ajouté à la vitrine. Utilisez le bouton Scan pour le vérifier et l'enrichir.\",\n es: 'Su proyecto ha sido añadido al showcase. Use el botón Scan para verificarlo y enriquecerlo.',\n ru: 'Ваш проект был добавлен в витрину. Используйте кнопку Сканировать, чтобы проверить и дополнить его.',\n ja: 'プロジェクトがショーケースに追加されました。「スキャン」ボタンを使用して、検証と充実を行ってください。',\n ko: '프로젝트가 쇼케이스에 추가되었습니다. 스캔 버튼을 사용하여 확인하고 보완하십시오.',\n zh: '您的项目已添加到展示墙。使用“扫描”按钮进行验证和完善。',\n de: 'Ihr Projekt wurde zum Showcase hinzugefügt. Verwenden Sie die Schaltfläche Scan, um es zu überprüfen und zu erweitern.',\n ar: 'تمت إضافة مشروعك إلى المعرض. استخدم زر المسح للتحقق منه وإثرائه.',\n it: 'Il tuo progetto è stato aggiunto alla vetrina. Usa il pulsante Scansiona per verificarlo e arricchirlo.',\n pt: 'Seu projeto foi adicionado ao showcase. Use o botão Scan para verificá-lo e enriquecê-lo.',\n hi: 'आपका प्रोजेक्ट शोकेस में जोड़ दिया गया है। इसे सत्यापित करने और समृद्ध करने के लिए स्कैन बटन का उपयोग करें।',\n tr: 'Projeniz vitrine eklendi. Doğrulamak ve zenginleştirmek için Tara düğmesini kullanın.',\n pl: 'Twój projekt został dodany do witryny. Użyj przycisku Skanuj, aby go zweryfikować i wzbogacić.',\n id: 'Proyek Anda telah ditambahkan ke showcase. Gunakan tombol Pindai untuk memverifikasi dan memperkayanya.',\n vi: 'Dự án của bạn đã được thêm vào showcase. Sử dụng nút Quét để xác minh và làm phong phú nó.',\n uk: 'Ваш проєкт додано до вітрини. Скористайтеся кнопкою Сканувати, щоб перевірити та доповнити його.',\n }),\n data: mapShowcaseProjectToAPI(newProject, userId),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type GetShowcaseProjectsQuery = {\n page?: string;\n pageSize?: string;\n search?: string;\n selectedUseCases?: string | string[];\n isOpenSource?: string;\n};\nexport type GetShowcaseProjectsResult = PaginatedResponse<ShowcaseProjectAPI>;\n\n/**\n * GET /api/showcase-project\n */\nexport const getShowcaseProjects = async (\n request: FastifyRequest<{ Querystring: GetShowcaseProjectsQuery }>,\n reply: FastifyReply\n): Promise<void> => {\n const {\n page: pageStr = '1',\n pageSize: pageSizeStr = '20',\n search = '',\n selectedUseCases,\n isOpenSource: isOpenSourceStr = 'false',\n } = request.query;\n\n const page = parseInt(pageStr, 10) || 1;\n const pageSize = parseInt(pageSizeStr, 10) || 20;\n const isOpenSource = isOpenSourceStr === 'true';\n\n const useCasesArray = selectedUseCases\n ? Array.isArray(selectedUseCases)\n ? selectedUseCases\n : [selectedUseCases]\n : [];\n\n try {\n const { data, total_items, total_pages } =\n await showcaseProjectService.findShowcaseProjects({\n search,\n selectedUseCases: useCasesArray,\n isOpenSource,\n page,\n pageSize,\n });\n\n const userId = getUserId(request);\n\n const responseData = formatPaginatedResponse<ShowcaseProjectAPI>({\n data: mapShowcaseProjectsToAPI(data, userId),\n page,\n pageSize,\n totalPages: total_pages,\n totalItems: total_items,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type GetShowcaseProjectByIdParams = { projectId: string };\nexport type GetShowcaseProjectByIdResult = ResponseData<ShowcaseProjectAPI>;\n\n/**\n * GET /api/showcase-project/:projectId\n */\nexport const getShowcaseProjectById = async (\n request: FastifyRequest<{ Params: GetShowcaseProjectByIdParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { projectId } = request.params;\n\n try {\n const project =\n await showcaseProjectService.findShowcaseProjectById(projectId);\n\n const userId = getUserId(request);\n\n return reply.send(\n formatResponse<ShowcaseProjectAPI>({\n data: mapShowcaseProjectToAPI(project, userId),\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type GetOtherShowcaseProjectsQuery = {\n excludeId: string;\n limit?: string;\n};\nexport type GetOtherShowcaseProjectsResult = ResponseData<ShowcaseProjectAPI[]>;\n\n/**\n * GET /api/showcase-project/others?excludeId=...&limit=...\n */\nexport const getOtherShowcaseProjects = async (\n request: FastifyRequest<{ Querystring: GetOtherShowcaseProjectsQuery }>,\n reply: FastifyReply\n): Promise<void> => {\n const { excludeId, limit: limitStr } = request.query;\n const limit = limitStr ? parseInt(limitStr, 10) : 4;\n\n if (!excludeId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SHOWCASE_PROJECT_NOT_FOUND',\n { detail: 'excludeId is required' }\n );\n }\n\n try {\n const projects = await showcaseProjectService.findOtherShowcaseProjects(\n excludeId,\n limit\n );\n\n const userId = getUserId(request);\n\n return reply.send(\n formatResponse<ShowcaseProjectAPI[]>({\n data: mapShowcaseProjectsToAPI(projects, userId),\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ToggleShowcaseUpvoteBody = { projectId: string };\nexport type ToggleShowcaseUpvoteResult = ResponseData<{\n upvotes: number;\n isUpVoted: boolean;\n downvotes: number;\n isDownVoted: boolean;\n}>;\n\n/**\n * POST /api/showcase-project/upvote\n * Requires authentication — userId is read from the session.\n */\nexport const toggleShowcaseUpvote = async (\n request: FastifyRequest<{ Body: ToggleShowcaseUpvoteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const { projectId } = request.body;\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { detail: 'projectId is required' }\n );\n }\n\n try {\n const result = await showcaseProjectService.toggleShowcaseUpvote(\n projectId,\n userId\n );\n\n return reply.send(formatResponse({ data: result }));\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ToggleShowcaseDownvoteBody = { projectId: string };\nexport type ToggleShowcaseDownvoteResult = ResponseData<{\n upvotes: number;\n isUpVoted: boolean;\n downvotes: number;\n isDownVoted: boolean;\n}>;\n\n/**\n * POST /api/showcase-project/downvote\n * Requires authentication — userId is read from the session.\n */\nexport const toggleShowcaseDownvote = async (\n request: FastifyRequest<{ Body: ToggleShowcaseDownvoteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const { projectId } = request.body;\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { detail: 'projectId is required' }\n );\n }\n\n try {\n const result = await showcaseProjectService.toggleShowcaseDownvote(\n projectId,\n userId\n );\n\n return reply.send(formatResponse({ data: result }));\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nconst updateProjectSchema = z.object({\n name: z.string().min(1).max(255).optional(),\n url: z\n .url()\n .refine((val) => !/github\\.com|gitlab\\.com|bitbucket\\.org/.test(val), {\n message: 'Repository URLs should be placed in the GitHub URL field',\n })\n .optional(),\n githubUrl: z\n .string()\n .optional()\n .transform((value) => {\n if (!value) return null;\n if (value.startsWith('http://') || value.startsWith('https://'))\n return value;\n return `https://${value}`;\n }),\n tagline: z.string().min(1).max(500).optional(),\n description: z.string().optional(),\n useCases: z.array(z.string()).max(3).optional(),\n});\n\nexport type UpdateShowcaseProjectBody = z.input<typeof updateProjectSchema>;\nexport type UpdateShowcaseProjectParams = { projectId: string };\nexport type UpdateShowcaseProjectResult = ResponseData<ShowcaseProjectAPI>;\n\n/**\n * PATCH /api/showcase-project/:projectId\n * Updates an existing project. Only the owner can update.\n */\nexport const updateShowcaseProjectHandler = async (\n request: FastifyRequest<{\n Params: UpdateShowcaseProjectParams;\n Body: UpdateShowcaseProjectBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const parsed = updateProjectSchema.safeParse(request.body);\n\n if (!parsed.success) {\n const message = parsed.error.issues\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join(', ');\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY',\n { message }\n );\n }\n\n const { projectId } = request.params;\n\n try {\n const project =\n await showcaseProjectService.findShowcaseProjectById(projectId);\n\n if (String(project.owner) !== userId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_ID_MISMATCH');\n }\n\n const { name, url, githubUrl, tagline, useCases } = parsed.data;\n const updates: Parameters<\n typeof showcaseProjectService.updateShowcaseProject\n >[1] = {};\n\n if (name !== undefined) updates.title = name;\n if (url !== undefined) updates.websiteUrl = url;\n if ('githubUrl' in parsed.data) updates.githubUrl = githubUrl;\n if (tagline !== undefined) updates.description = tagline;\n if (useCases !== undefined) updates.tags = useCases;\n\n const updated = await showcaseProjectService.updateShowcaseProject(\n projectId,\n updates\n );\n\n return reply.send(\n formatResponse<ShowcaseProjectAPI>({\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 data: mapShowcaseProjectToAPI(updated, userId),\n })\n );\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type DeleteShowcaseProjectParams = { projectId: string };\n\n/**\n * DELETE /api/showcase-project/:projectId\n * Deletes a project (DB + R2 screenshot). Only the owner can delete.\n */\nexport const deleteShowcaseProjectHandler = async (\n request: FastifyRequest<{ Params: DeleteShowcaseProjectParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_NOT_AUTHENTICATED'\n );\n }\n\n const { projectId } = request.params;\n\n try {\n const project =\n await showcaseProjectService.findShowcaseProjectById(projectId);\n\n if (String(project.owner) !== userId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_ID_MISMATCH');\n }\n\n await Promise.allSettled([\n showcaseProjectService.deleteShowcaseProject(projectId),\n project.imageUrl\n ? deleteShowcaseScreenshot(project.imageUrl)\n : Promise.resolve(),\n ]);\n\n return reply.send(formatResponse({ data: { success: true } }));\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ScanShowcaseProjectParams = { projectId: string };\n\n/**\n * GET /api/showcase-project/:projectId/scan\n * SSE endpoint — streams scan progress to the owner.\n * Requires authentication: only the project owner can trigger the scan.\n */\nexport const scanShowcaseProject = async (\n request: FastifyRequest<{ Params: ScanShowcaseProjectParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const userId = getUserId(request);\n\n if (!userId) {\n reply.status(401).send({ error: { message: 'Authentication required' } });\n return;\n }\n\n const { projectId } = request.params;\n\n let project: Awaited<\n ReturnType<typeof showcaseProjectService.findShowcaseProjectById>\n >;\n try {\n project = await showcaseProjectService.findShowcaseProjectById(projectId);\n } catch {\n reply.status(404).send({ error: { message: 'Project not found' } });\n return;\n }\n\n if (project.owner !== userId) {\n reply.status(403).send({\n error: { message: 'Only the project owner can trigger a scan' },\n });\n return;\n }\n\n // Hijack the reply and write SSE manually\n reply.hijack();\n const raw = reply.raw;\n const origin = request.headers.origin ?? '*';\n raw.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Access-Control-Allow-Origin': origin,\n 'Access-Control-Allow-Credentials': 'true',\n });\n\n const send = (data: Record<string, unknown>) => {\n raw.write(`data: ${JSON.stringify(data)}\\n\\n`);\n };\n\n // Track the uploaded screenshot URL so we can delete it on failure.\n let uploadedImageUrl: string | null = null;\n\n const cleanup = async (message: string) => {\n await Promise.allSettled([\n showcaseProjectService.deleteShowcaseProject(projectId),\n uploadedImageUrl\n ? deleteShowcaseScreenshot(uploadedImageUrl)\n : Promise.resolve(),\n ]);\n send({ step: 'ERROR', message });\n };\n\n try {\n // ── Step 1: Scan website + take screenshot (single browser session) ────────\n send({ step: 'SCANNING_START' });\n const scanResult = await scanShowcaseProjectViaService(project.websiteUrl);\n\n if (!scanResult.hasIntlayer && !scanResult.libsUsed.includes('intlayer')) {\n await cleanup('Intlayer not detected on this website');\n raw.end();\n return;\n }\n send({ step: 'SCANNING_SUCCESS' });\n\n // ── Step 2: Verify GitHub (if provided) ───────────────────────────────────\n let isOpenSource = false;\n let githubPackageDetails: Record<string, string> = {};\n\n if (project.githubUrl) {\n send({ step: 'VERIFY_GITHUB_START' });\n logger.info(\n `[scanShowcaseProject] Verifying GitHub ${project.githubUrl}...`\n );\n const githubResult = await verifyGithubRepo(project.githubUrl);\n if (githubResult?.isValid) {\n isOpenSource = true;\n githubPackageDetails = githubResult.packageDetails ?? {};\n }\n send({ step: 'VERIFY_GITHUB_SUCCESS' });\n }\n\n // ── Step 3: Upload the screenshot captured during scan ────────────────────\n send({ step: 'SCREENSHOT_START' });\n if (scanResult.screenshotBuffer) {\n uploadedImageUrl = await uploadShowcaseScreenshot(\n scanResult.screenshotBuffer,\n project.websiteUrl\n );\n }\n send({ step: 'SCREENSHOT_SUCCESS' });\n\n // ── Step 4: Merge & save ──────────────────────────────────────────────────\n const mergedPackageDetails: Record<string, string> = {\n ...(scanResult.packageDetails ?? {}),\n ...githubPackageDetails,\n };\n const mergedLibsUsed = Array.from(\n new Set([...scanResult.libsUsed, ...Object.keys(githubPackageDetails)])\n );\n const intlayerVersion =\n mergedPackageDetails.intlayer || scanResult.intlayerVersion;\n\n const updated = await showcaseProjectService.updateShowcaseProject(\n projectId,\n {\n intlayerVersion,\n libsUsed: mergedLibsUsed,\n packageDetails: mergedPackageDetails,\n scanDetails: scanResult.scanDetails,\n imageUrl: uploadedImageUrl ?? project.imageUrl,\n isOpenSource,\n status: 'active',\n lastScanDate: new Date(),\n // Populate tagline/description from page metadata if not already set\n ...((!project.description || project.description === '') &&\n scanResult.metaDescription\n ? { description: scanResult.metaDescription }\n : {}),\n }\n );\n\n send({\n step: 'SUCCESS',\n project: mapShowcaseProjectToAPI(updated, userId),\n });\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Scan failed unexpectedly';\n logger.error('[scanShowcaseProject] Error:', error);\n await cleanup(message);\n }\n\n raw.end();\n};\n"],"mappings":"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"}
@@ -57,7 +57,6 @@ const translateDictionaries = async (request, reply) => {
57
57
  message: "Translation started"
58
58
  }));
59
59
  } catch (error) {
60
- logger.error("Error in translateDictionaries", error);
61
60
  return ErrorHandler.handleAppErrorResponse(reply, error);
62
61
  }
63
62
  };