@intlayer/backend 7.5.9 → 7.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/assets/utils/AI/askDocQuestion/PROMPT.md +1 -1
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/ci.json +3080 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/list_projects.json +1 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_fastify.json +9 -0
- package/dist/esm/controllers/ai.controller.mjs +95 -128
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/bitbucket.controller.mjs +77 -0
- package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -0
- package/dist/esm/controllers/dictionary.controller.mjs +106 -198
- package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
- package/dist/esm/controllers/eventListener.controller.mjs +13 -19
- package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
- package/dist/esm/controllers/github.controller.mjs +77 -0
- package/dist/esm/controllers/github.controller.mjs.map +1 -0
- package/dist/esm/controllers/gitlab.controller.mjs +77 -0
- package/dist/esm/controllers/gitlab.controller.mjs.map +1 -0
- package/dist/esm/controllers/newsletter.controller.mjs +30 -60
- package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
- package/dist/esm/controllers/oAuth2.controller.mjs +11 -8
- package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
- package/dist/esm/controllers/organization.controller.mjs +100 -225
- package/dist/esm/controllers/organization.controller.mjs.map +1 -1
- package/dist/esm/controllers/project.controller.mjs +194 -204
- package/dist/esm/controllers/project.controller.mjs.map +1 -1
- package/dist/esm/controllers/projectAccessKey.controller.mjs +38 -71
- package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
- package/dist/esm/controllers/search.controller.mjs +3 -3
- package/dist/esm/controllers/search.controller.mjs.map +1 -1
- package/dist/esm/controllers/stripe.controller.mjs +34 -67
- package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
- package/dist/esm/controllers/tag.controller.mjs +51 -113
- package/dist/esm/controllers/tag.controller.mjs.map +1 -1
- package/dist/esm/controllers/user.controller.mjs +64 -113
- package/dist/esm/controllers/user.controller.mjs.map +1 -1
- package/dist/esm/export.mjs +4 -1
- package/dist/esm/index.mjs +105 -41
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/middlewares/oAuth2.middleware.mjs +19 -14
- package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
- package/dist/esm/middlewares/sessionAuth.middleware.mjs +6 -7
- package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
- package/dist/esm/routes/ai.routes.mjs +19 -15
- package/dist/esm/routes/ai.routes.mjs.map +1 -1
- package/dist/esm/routes/bitbucket.routes.mjs +43 -0
- package/dist/esm/routes/bitbucket.routes.mjs.map +1 -0
- package/dist/esm/routes/dictionary.routes.mjs +10 -10
- package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
- package/dist/esm/routes/eventListener.routes.mjs +3 -3
- package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
- package/dist/esm/routes/github.routes.mjs +43 -0
- package/dist/esm/routes/github.routes.mjs.map +1 -0
- package/dist/esm/routes/gitlab.routes.mjs +43 -0
- package/dist/esm/routes/gitlab.routes.mjs.map +1 -0
- package/dist/esm/routes/newsletter.routes.mjs +5 -5
- package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
- package/dist/esm/routes/organization.routes.mjs +11 -11
- package/dist/esm/routes/organization.routes.mjs.map +1 -1
- package/dist/esm/routes/project.routes.mjs +38 -14
- package/dist/esm/routes/project.routes.mjs.map +1 -1
- package/dist/esm/routes/search.routes.mjs +3 -3
- package/dist/esm/routes/search.routes.mjs.map +1 -1
- package/dist/esm/routes/stripe.routes.mjs +5 -5
- package/dist/esm/routes/stripe.routes.mjs.map +1 -1
- package/dist/esm/routes/tags.routes.mjs +6 -6
- package/dist/esm/routes/tags.routes.mjs.map +1 -1
- package/dist/esm/routes/user.routes.mjs +9 -9
- package/dist/esm/routes/user.routes.mjs.map +1 -1
- package/dist/esm/schemas/project.schema.mjs +70 -1
- package/dist/esm/schemas/project.schema.mjs.map +1 -1
- package/dist/esm/services/bitbucket.service.mjs +173 -0
- package/dist/esm/services/bitbucket.service.mjs.map +1 -0
- package/dist/esm/services/ci.service.mjs +134 -0
- package/dist/esm/services/ci.service.mjs.map +1 -0
- package/dist/esm/services/email.service.mjs +1 -1
- package/dist/esm/services/email.service.mjs.map +1 -1
- package/dist/esm/services/github.service.mjs +218 -0
- package/dist/esm/services/github.service.mjs.map +1 -0
- package/dist/esm/services/gitlab.service.mjs +217 -0
- package/dist/esm/services/gitlab.service.mjs.map +1 -0
- package/dist/esm/services/oAuth2.service.mjs +1 -1
- package/dist/esm/services/subscription.service.mjs +1 -1
- package/dist/esm/services/subscription.service.mjs.map +1 -1
- package/dist/esm/services/webhook.service.mjs +164 -0
- package/dist/esm/services/webhook.service.mjs.map +1 -0
- package/dist/esm/utils/auth/getAuth.mjs +28 -16
- package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
- package/dist/esm/utils/cors.mjs +15 -5
- package/dist/esm/utils/cors.mjs.map +1 -1
- package/dist/esm/utils/errors/ErrorHandler.mjs +32 -4
- package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
- package/dist/esm/utils/errors/ErrorsClass.mjs +1 -1
- package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
- package/dist/esm/utils/errors/errorCodes.mjs +234 -0
- package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs +1 -1
- package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs +1 -1
- package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/mapper/project.mjs +28 -1
- package/dist/esm/utils/mapper/project.mjs.map +1 -1
- package/dist/esm/utils/mongoDB/connectDB.mjs +1 -1
- package/dist/esm/utils/rateLimiter.mjs +40 -30
- package/dist/esm/utils/rateLimiter.mjs.map +1 -1
- package/dist/esm/webhooks/stripe.webhook.mjs +2 -2
- package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
- package/dist/types/controllers/ai.controller.d.ts +29 -12
- package/dist/types/controllers/ai.controller.d.ts.map +1 -1
- package/dist/types/controllers/bitbucket.controller.d.ts +62 -0
- package/dist/types/controllers/bitbucket.controller.d.ts.map +1 -0
- package/dist/types/controllers/dictionary.controller.d.ts +23 -13
- package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
- package/dist/types/controllers/eventListener.controller.d.ts +4 -2
- package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
- package/dist/types/controllers/github.controller.d.ts +63 -0
- package/dist/types/controllers/github.controller.d.ts.map +1 -0
- package/dist/types/controllers/gitlab.controller.d.ts +67 -0
- package/dist/types/controllers/gitlab.controller.d.ts.map +1 -0
- package/dist/types/controllers/newsletter.controller.d.ts +8 -7
- package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
- package/dist/types/controllers/oAuth2.controller.d.ts +4 -2
- package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
- package/dist/types/controllers/organization.controller.d.ts +28 -12
- package/dist/types/controllers/organization.controller.d.ts.map +1 -1
- package/dist/types/controllers/project.controller.d.ts +60 -17
- package/dist/types/controllers/project.controller.d.ts.map +1 -1
- package/dist/types/controllers/projectAccessKey.controller.d.ts +10 -5
- package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
- package/dist/types/controllers/search.controller.d.ts +4 -2
- package/dist/types/controllers/search.controller.d.ts.map +1 -1
- package/dist/types/controllers/stripe.controller.d.ts +11 -12
- package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
- package/dist/types/controllers/tag.controller.d.ts +14 -9
- package/dist/types/controllers/tag.controller.d.ts.map +1 -1
- package/dist/types/controllers/user.controller.d.ts +22 -9
- package/dist/types/controllers/user.controller.d.ts.map +1 -1
- package/dist/types/emails/InviteUserEmail.d.ts +4 -4
- package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
- package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
- package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
- package/dist/types/emails/PasswordChangeConfirmation.d.ts +4 -4
- package/dist/types/emails/ResetUserPassword.d.ts +4 -4
- package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
- package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
- package/dist/types/emails/Welcome.d.ts +4 -4
- package/dist/types/export.d.ts +11 -5
- package/dist/types/middlewares/oAuth2.middleware.d.ts +9 -4
- package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
- package/dist/types/middlewares/sessionAuth.middleware.d.ts +13 -3
- package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
- package/dist/types/models/discussion.model.d.ts +3 -3
- package/dist/types/models/oAuth2.model.d.ts +3 -3
- package/dist/types/routes/ai.routes.d.ts +2 -2
- package/dist/types/routes/ai.routes.d.ts.map +1 -1
- package/dist/types/routes/bitbucket.routes.d.ts +35 -0
- package/dist/types/routes/bitbucket.routes.d.ts.map +1 -0
- package/dist/types/routes/dictionary.routes.d.ts +2 -2
- package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
- package/dist/types/routes/eventListener.routes.d.ts +2 -2
- package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
- package/dist/types/routes/github.routes.d.ts +35 -0
- package/dist/types/routes/github.routes.d.ts.map +1 -0
- package/dist/types/routes/gitlab.routes.d.ts +35 -0
- package/dist/types/routes/gitlab.routes.d.ts.map +1 -0
- package/dist/types/routes/newsletter.routes.d.ts +2 -2
- package/dist/types/routes/newsletter.routes.d.ts.map +1 -1
- package/dist/types/routes/organization.routes.d.ts +2 -2
- package/dist/types/routes/organization.routes.d.ts.map +1 -1
- package/dist/types/routes/project.routes.d.ts +22 -2
- package/dist/types/routes/project.routes.d.ts.map +1 -1
- package/dist/types/routes/search.routes.d.ts +2 -2
- package/dist/types/routes/search.routes.d.ts.map +1 -1
- package/dist/types/routes/stripe.routes.d.ts +2 -2
- package/dist/types/routes/stripe.routes.d.ts.map +1 -1
- package/dist/types/routes/tags.routes.d.ts +2 -2
- package/dist/types/routes/tags.routes.d.ts.map +1 -1
- package/dist/types/routes/user.routes.d.ts +2 -2
- package/dist/types/routes/user.routes.d.ts.map +1 -1
- package/dist/types/schemas/dictionary.schema.d.ts +6 -6
- package/dist/types/schemas/discussion.schema.d.ts +6 -6
- package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
- package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
- package/dist/types/schemas/plans.schema.d.ts +6 -6
- package/dist/types/schemas/project.schema.d.ts +6 -6
- package/dist/types/schemas/project.schema.d.ts.map +1 -1
- package/dist/types/schemas/session.schema.d.ts +6 -6
- package/dist/types/schemas/tag.schema.d.ts +6 -6
- package/dist/types/schemas/user.schema.d.ts +6 -6
- package/dist/types/schemas/user.schema.d.ts.map +1 -1
- package/dist/types/services/bitbucket.service.d.ts +71 -0
- package/dist/types/services/bitbucket.service.d.ts.map +1 -0
- package/dist/types/services/ci.service.d.ts +27 -0
- package/dist/types/services/ci.service.d.ts.map +1 -0
- package/dist/types/services/github.service.d.ts +40 -0
- package/dist/types/services/github.service.d.ts.map +1 -0
- package/dist/types/services/gitlab.service.d.ts +58 -0
- package/dist/types/services/gitlab.service.d.ts.map +1 -0
- package/dist/types/services/webhook.service.d.ts +19 -0
- package/dist/types/services/webhook.service.d.ts.map +1 -0
- package/dist/types/types/project.types.d.ts +46 -5
- package/dist/types/types/project.types.d.ts.map +1 -1
- package/dist/types/types/session.types.d.ts +1 -1
- package/dist/types/types/user.types.d.ts +1 -1
- package/dist/types/utils/AI/auditTag/index.d.ts +1 -1
- package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
- package/dist/types/utils/cors.d.ts +2 -2
- package/dist/types/utils/errors/ErrorHandler.d.ts +31 -3
- package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
- package/dist/types/utils/errors/ErrorsClass.d.ts +1 -1
- package/dist/types/utils/errors/errorCodes.d.ts +234 -0
- package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +6 -3
- package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts +6 -2
- package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +6 -2
- package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts +6 -2
- package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/mapper/project.d.ts.map +1 -1
- package/dist/types/utils/permissions.d.ts +1 -1
- package/dist/types/utils/rateLimiter.d.ts +4 -2
- package/dist/types/utils/rateLimiter.d.ts.map +1 -1
- package/package.json +24 -28
- package/dist/esm/middlewares/request.middleware.mjs +0 -17
- package/dist/esm/middlewares/request.middleware.mjs.map +0 -1
- package/dist/types/middlewares/request.middleware.d.ts +0 -7
- package/dist/types/middlewares/request.middleware.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.controller.mjs","names":["projectService.findProjects","projectService.countProjects","project: ProjectData","projectService.createProject","projectService.updateProjectById","existingUsers: UserAndAdmin[]","userService.getUsersByIds","userMap: UserAndAdmin[]","user","formattedMembers: Types.ObjectId[]","formattedAdmin: Types.ObjectId[]","projectService.getProjectById","projectService.deleteProjectById"],"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { SessionModel } from '@models/session.model';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n 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 { NextFunction, Request } from 'express';\nimport { t } from 'express-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 req: Request<GetProjectsParams>,\n res: ResponseWithSession<GetProjectsResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, roles } = res.locals;\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(req, res);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!organization && !roles.includes('admin')) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n try {\n const projects = await projectService.findProjects(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles,\n 'project:read'\n )({\n ...res.locals,\n targetProjects: projects,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\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 res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request<any, any, AddProjectBody>,\n res: ResponseWithSession<AddProjectResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, user, roles } = res.locals;\n const projectData = req.body;\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (!projectData) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_DATA_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles,\n 'organization:admin'\n )({\n ...res.locals,\n targetOrganizations: [organization],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\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 ErrorHandler.handleGenericErrorResponse(\n res,\n 'PLAN_PROJECT_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n return;\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 fr: 'Projet créé avec succès',\n es: 'Proyecto creado con éxito',\n }),\n description: t({\n en: '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 }),\n data: formattedProject,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request<any, any, UpdateProjectBody>,\n res: ResponseWithSession<UpdateProjectResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, project, user, roles } = res.locals;\n const projectData = req.body;\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_DATA_NOT_FOUND');\n return;\n }\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (String(project.organizationId) !== String(organization.id)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_IN_ORGANIZATION');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedProject = await projectService.updateProjectById(\n project.id,\n projectData\n );\n\n const formattedProject = mapProjectToAPI(updatedProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project updated successfully',\n fr: 'Projet mis à jour avec succès',\n es: 'Proyecto actualizado con éxito',\n }),\n description: t({\n en: '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 }),\n data: formattedProject,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request<any, any, UpdateProjectMembersBody>,\n res: ResponseWithSession<UpdateProjectMembersResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization, roles } = res.locals;\n const { membersIds } = req.body;\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (membersIds?.length === 0) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_MUST_HAVE_MEMBER');\n return;\n }\n\n if (membersIds?.map((el) => el.isAdmin)?.length === 0) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_MUST_HAVE_ADMIN');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\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 fr: 'Membres du projet mis à jour avec succès',\n es: 'Miembros del proyecto actualizados con éxito',\n }),\n description: t({\n en: '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 }),\n data: formattedProject,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type PushProjectConfigurationBody = ProjectConfiguration;\nexport type PushProjectConfigurationResult = ResponseData<ProjectConfiguration>;\n\n/**\n * Pushes a project configuration to the database.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response confirming the deletion.\n */\nexport const pushProjectConfiguration = async (\n req: Request<any, any, PushProjectConfigurationBody>,\n res: ResponseWithSession<PushProjectConfigurationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const projectConfiguration = req.body;\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const projectObject = await projectService.getProjectById(project.id);\n projectObject.configuration = projectConfiguration;\n\n projectObject.save();\n\n if (!projectObject.configuration) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_UPDATE_FAILED', {\n projectId: project.id,\n });\n return;\n }\n\n const responseData = formatResponse<ProjectConfiguration>({\n message: t({\n en: 'Project configuration updated successfully',\n fr: 'Configuration du projet mise à jour avec succès',\n es: 'Configuración del proyecto actualizada con éxito',\n }),\n description: t({\n en: '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 }),\n data: projectObject.configuration,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type DeleteProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Deletes a project from the database by its ID.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response confirming the deletion.\n */\nexport const deleteProject = async (\n _req: Request,\n res: ResponseWithSession<DeleteProjectResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, project, session, roles } = res.locals;\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (typeof session === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:admin'\n )({\n ...res.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const projectToDelete = await projectService.getProjectById(project.id);\n\n if (String(projectToDelete.organizationId) !== String(organization.id)) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n return;\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED', {\n projectId: project.id,\n });\n\n return;\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 fr: 'Projet supprimé avec succès',\n es: 'Proyecto eliminado con éxito',\n }),\n description: t({\n en: '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 }),\n data: mapProjectToAPI(deletedProject),\n });\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request<SelectProjectParam>,\n res: ResponseWithSession<SelectProjectResult>,\n _next: NextFunction\n) => {\n const { projectId } = req.params;\n const { session } = res.locals;\n\n if (!projectId) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_ID_NOT_FOUND');\n return;\n }\n\n if (typeof session === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED');\n return;\n }\n\n try {\n const project = await projectService.getProjectById(projectId);\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: String(projectId) } }\n );\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project selected successfully',\n fr: 'Projet sélectionné avec succès',\n es: 'Proyecto seleccionado con éxito',\n }),\n description: t({\n en: '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 }),\n data: mapProjectToAPI(project),\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type UnselectProjectResult = ResponseData<null>;\n\n/**\n * Unselect a project.\n */\nexport const unselectProject = async (\n _req: Request,\n res: ResponseWithSession<UnselectProjectResult>,\n _next: NextFunction\n) => {\n const { session } = res.locals;\n\n if (typeof session === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED');\n return;\n }\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Project unselected successfully',\n fr: 'Projet désélectionné avec succès',\n es: 'Proyecto deseleccionado con éxito',\n }),\n description: t({\n en: '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 }),\n data: null,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"project.controller.mjs","names":["projectService.findProjects","projectService.countProjects","project: ProjectData","projectService.createProject","projectService.updateProjectById","existingUsers: UserAndAdmin[]","userService.getUsersByIds","userMap: UserAndAdmin[]","user","formattedMembers: Types.ObjectId[]","formattedAdmin: Types.ObjectId[]","projectService.getProjectById","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 * 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, organization, roles } = request.locals || {};\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 if (!organization && !roles?.includes('admin')) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const projects = await projectService.findProjects(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.locals,\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.locals || {};\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.locals,\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 fr: 'Projet créé avec succès',\n es: 'Proyecto creado con éxito',\n }),\n description: t({\n en: '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 }),\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 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, roles } = request.locals || {};\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.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedProject = await projectService.updateProjectById(\n project.id,\n projectData\n );\n\n const formattedProject = mapProjectToAPI(updatedProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project updated successfully',\n fr: 'Projet mis à jour avec succès',\n es: 'Proyecto actualizado con éxito',\n }),\n description: t({\n en: '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 }),\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.locals || {};\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.locals,\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 fr: 'Membres du projet mis à jour avec succès',\n es: 'Miembros del proyecto actualizados con éxito',\n }),\n description: t({\n en: '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 }),\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.locals || {};\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.locals,\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 projectObject.configuration = projectConfiguration;\n\n await projectObject.save();\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 fr: 'Configuration du projet mise à jour avec succès',\n es: 'Configuración del proyecto actualizada con éxito',\n }),\n description: t({\n en: '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 }),\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.locals || {};\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.locals,\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 fr: 'Déclenchement des builds initié',\n es: 'Inicio de los triggers de build',\n }),\n description: t({\n en: '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 }),\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.locals || {};\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.locals,\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 fr: 'Webhook déclenché',\n es: 'Webhook activado',\n }),\n description: t({\n en: `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 }),\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 } = _request.locals || {};\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.locals,\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 fr: 'Projet supprimé avec succès',\n es: 'Proyecto eliminado con éxito',\n }),\n description: t({\n en: '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 }),\n data: mapProjectToAPI(deletedProject),\n });\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type 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 } = request.locals || {};\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 await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: String(projectId) } }\n );\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project selected successfully',\n fr: 'Projet sélectionné avec succès',\n es: 'Proyecto seleccionado con éxito',\n }),\n description: t({\n en: '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 }),\n data: mapProjectToAPI(project),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectProjectResult = ResponseData<null>;\n\n/**\n * Unselect a project.\n */\nexport const unselectProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { session } = _request.locals || {};\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Project unselected successfully',\n fr: 'Projet désélectionné avec succès',\n es: 'Proyecto deseleccionado con éxito',\n }),\n description: t({\n en: '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 }),\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.locals || {};\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);\n\n const responseData = formatResponse<ciService.CIStatus>({\n data: ciStatus,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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.locals || {};\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);\n\n const responseData = formatResponse<{ success: boolean }>({\n message: t({\n en: 'CI configuration installed successfully',\n fr: 'Configuration CI installée avec succès',\n es: 'Configuración CI instalada con éxito',\n }),\n description: t({\n en: '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 }),\n data: { success: true },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
@@ -3,31 +3,22 @@ import { ErrorHandler } from "../utils/errors/ErrorHandler.mjs";
|
|
|
3
3
|
import { hasPermission, intersectPermissions } from "../utils/permissions.mjs";
|
|
4
4
|
import { sendEmail } from "../services/email.service.mjs";
|
|
5
5
|
import { addNewAccessKey as addNewAccessKey$1, deleteAccessKey as deleteAccessKey$1, refreshAccessKey as refreshAccessKey$1 } from "../services/projectAccessKey.service.mjs";
|
|
6
|
-
import { t } from "
|
|
6
|
+
import { t } from "fastify-intlayer";
|
|
7
7
|
|
|
8
8
|
//#region src/controllers/projectAccessKey.controller.ts
|
|
9
9
|
/**
|
|
10
10
|
* Adds a new access key to a project.
|
|
11
11
|
*/
|
|
12
|
-
const addNewAccessKey = async (
|
|
13
|
-
const { user, project, roles, permissions } =
|
|
14
|
-
const { grants, name, expiresAt } =
|
|
15
|
-
if (!project)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!user) {
|
|
20
|
-
ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (!hasPermission(roles, "project:write")({
|
|
24
|
-
...res.locals,
|
|
12
|
+
const addNewAccessKey = async (request, reply) => {
|
|
13
|
+
const { user, project, roles, permissions } = request.locals || {};
|
|
14
|
+
const { grants, name, expiresAt } = request.body;
|
|
15
|
+
if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
|
|
16
|
+
if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
|
|
17
|
+
if (!hasPermission(roles || [], "project:write")({
|
|
18
|
+
...request.locals,
|
|
25
19
|
targetProjectIds: [project.id]
|
|
26
|
-
}))
|
|
27
|
-
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const filteredPermisions = intersectPermissions(permissions, grants);
|
|
20
|
+
})) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
|
|
21
|
+
const filteredPermisions = intersectPermissions(permissions || [], grants);
|
|
31
22
|
try {
|
|
32
23
|
const newAccessKey = await addNewAccessKey$1({
|
|
33
24
|
name,
|
|
@@ -47,53 +38,36 @@ const addNewAccessKey = async (req, res, _next) => {
|
|
|
47
38
|
}),
|
|
48
39
|
data: newAccessKey
|
|
49
40
|
});
|
|
50
|
-
res.json(responseData);
|
|
51
41
|
sendEmail({
|
|
52
42
|
type: "oAuthTokenCreated",
|
|
53
43
|
to: user.email,
|
|
54
44
|
username: user.name,
|
|
55
45
|
applicationName: newAccessKey.name ?? newAccessKey.clientId,
|
|
56
46
|
scopes: newAccessKey.grants,
|
|
57
|
-
tokenDetailsUrl: `${process.env.
|
|
58
|
-
securityLogUrl: `${process.env.
|
|
59
|
-
supportUrl: `${process.env.
|
|
47
|
+
tokenDetailsUrl: `${process.env.APP_URL}/oauth2/token`,
|
|
48
|
+
securityLogUrl: `${process.env.APP_URL}/security-log`,
|
|
49
|
+
supportUrl: `${process.env.APP_URL}/support`
|
|
60
50
|
});
|
|
61
|
-
return;
|
|
51
|
+
return reply.send(responseData);
|
|
62
52
|
} catch (error) {
|
|
63
|
-
ErrorHandler.handleAppErrorResponse(
|
|
64
|
-
return;
|
|
53
|
+
return ErrorHandler.handleAppErrorResponse(reply, error);
|
|
65
54
|
}
|
|
66
55
|
};
|
|
67
56
|
/**
|
|
68
57
|
* Deletes an access key from a project.
|
|
69
58
|
*/
|
|
70
|
-
const deleteAccessKey = async (
|
|
71
|
-
const { user, project, roles } =
|
|
72
|
-
const { clientId } =
|
|
73
|
-
if (!project)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (!clientId) {
|
|
82
|
-
ErrorHandler.handleGenericErrorResponse(res, "CLIENT_ID_NOT_FOUND");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (!hasPermission(roles, "project:write")({
|
|
86
|
-
...res.locals,
|
|
59
|
+
const deleteAccessKey = async (request, reply) => {
|
|
60
|
+
const { user, project, roles } = request.locals || {};
|
|
61
|
+
const { clientId } = request.body;
|
|
62
|
+
if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
|
|
63
|
+
if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
|
|
64
|
+
if (!clientId) return ErrorHandler.handleGenericErrorResponse(reply, "CLIENT_ID_NOT_FOUND");
|
|
65
|
+
if (!hasPermission(roles || [], "project:write")({
|
|
66
|
+
...request.locals,
|
|
87
67
|
targetProjectIds: [project.id]
|
|
88
|
-
}))
|
|
89
|
-
ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
68
|
+
})) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
|
|
92
69
|
try {
|
|
93
|
-
if (!await deleteAccessKey$1(clientId, project, user.id)) {
|
|
94
|
-
ErrorHandler.handleGenericErrorResponse(res, "ACCESS_KEY_NOT_FOUND", { clientId });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
70
|
+
if (!await deleteAccessKey$1(clientId, project, user.id)) return ErrorHandler.handleGenericErrorResponse(reply, "ACCESS_KEY_NOT_FOUND", { clientId });
|
|
97
71
|
const responseData = formatResponse({
|
|
98
72
|
message: t({
|
|
99
73
|
en: "Access key deleted successfully",
|
|
@@ -107,29 +81,24 @@ const deleteAccessKey = async (req, res, _next) => {
|
|
|
107
81
|
}),
|
|
108
82
|
data: null
|
|
109
83
|
});
|
|
110
|
-
|
|
111
|
-
return;
|
|
84
|
+
return reply.send(responseData);
|
|
112
85
|
} catch (error) {
|
|
113
|
-
ErrorHandler.handleAppErrorResponse(
|
|
114
|
-
return;
|
|
86
|
+
return ErrorHandler.handleAppErrorResponse(reply, error);
|
|
115
87
|
}
|
|
116
88
|
};
|
|
117
89
|
/**
|
|
118
90
|
* Refreshes an access key from a project.
|
|
119
91
|
*/
|
|
120
|
-
const refreshAccessKey = async (
|
|
121
|
-
const { user, project, roles } =
|
|
122
|
-
const { clientId } =
|
|
123
|
-
if (!project) ErrorHandler.handleGenericErrorResponse(
|
|
124
|
-
if (!user) ErrorHandler.handleGenericErrorResponse(
|
|
125
|
-
if (!clientId) ErrorHandler.handleGenericErrorResponse(
|
|
126
|
-
if (!hasPermission(roles, "project:write")({
|
|
127
|
-
...
|
|
92
|
+
const refreshAccessKey = async (request, reply) => {
|
|
93
|
+
const { user, project, roles } = request.locals || {};
|
|
94
|
+
const { clientId } = request.body;
|
|
95
|
+
if (!project) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECT_NOT_DEFINED");
|
|
96
|
+
if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
|
|
97
|
+
if (!clientId) return ErrorHandler.handleGenericErrorResponse(reply, "CLIENT_ID_NOT_FOUND");
|
|
98
|
+
if (!hasPermission(roles || [], "project:write")({
|
|
99
|
+
...request.locals,
|
|
128
100
|
targetProjectIds: [project?.id]
|
|
129
|
-
}))
|
|
130
|
-
ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
101
|
+
})) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
|
|
133
102
|
try {
|
|
134
103
|
const newAccessKey = await refreshAccessKey$1(clientId, project?.id, user?.id);
|
|
135
104
|
const responseData = formatResponse({
|
|
@@ -145,11 +114,9 @@ const refreshAccessKey = async (req, res, _next) => {
|
|
|
145
114
|
}),
|
|
146
115
|
data: newAccessKey
|
|
147
116
|
});
|
|
148
|
-
|
|
149
|
-
return;
|
|
117
|
+
return reply.send(responseData);
|
|
150
118
|
} catch (error) {
|
|
151
|
-
ErrorHandler.handleAppErrorResponse(
|
|
152
|
-
return;
|
|
119
|
+
return ErrorHandler.handleAppErrorResponse(reply, error);
|
|
153
120
|
}
|
|
154
121
|
};
|
|
155
122
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectAccessKey.controller.mjs","names":["projectAccessKeyService.addNewAccessKey","projectAccessKeyService.deleteAccessKey","projectAccessKeyService.refreshAccessKey"],"sources":["../../../src/controllers/projectAccessKey.controller.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"projectAccessKey.controller.mjs","names":["projectAccessKeyService.addNewAccessKey","projectAccessKeyService.deleteAccessKey","projectAccessKeyService.refreshAccessKey"],"sources":["../../../src/controllers/projectAccessKey.controller.ts"],"sourcesContent":["import { sendEmail } from '@services/email.service';\nimport * as projectAccessKeyService from '@services/projectAccessKey.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { hasPermission, intersectPermissions } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { AccessKeyData, OAuth2Access } from '@/types/project.types';\n\nexport type AddNewAccessKeyBody = AccessKeyData;\nexport type AddNewAccessKeyResponse = ResponseData<OAuth2Access>;\n\n/**\n * Adds a new access key to a project.\n */\nexport const addNewAccessKey = async (\n request: FastifyRequest<{ Body: AddNewAccessKeyBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles, permissions } = request.locals || {};\n const { grants, name, expiresAt } = request.body;\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [project.id],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n const filteredPermisions = intersectPermissions(permissions || [], grants);\n\n try {\n const newAccessKey = await projectAccessKeyService.addNewAccessKey(\n {\n name,\n expiresAt,\n grants: filteredPermisions,\n },\n project.id,\n user\n );\n\n const responseData = formatResponse<OAuth2Access>({\n message: t({\n en: 'Access key created successfully',\n es: 'Clave de acceso creada con éxito',\n fr: \"Clé d'accès créée avec succès\",\n }),\n description: t({\n en: 'The access key has been created successfully',\n es: 'La clave de acceso ha sido creada con éxito',\n fr: \"La clé d'accès a été créée avec succès\",\n }),\n data: newAccessKey,\n });\n\n sendEmail({\n type: 'oAuthTokenCreated',\n to: user.email,\n username: user.name,\n applicationName: newAccessKey.name ?? newAccessKey.clientId,\n scopes: newAccessKey.grants,\n tokenDetailsUrl: `${process.env.APP_URL}/oauth2/token`,\n securityLogUrl: `${process.env.APP_URL}/security-log`,\n supportUrl: `${process.env.APP_URL}/support`,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteAccessKeyBody = { clientId: string };\nexport type DeleteAccessKeyResponse = ResponseData<null>;\n\n/**\n * Deletes an access key from a project.\n */\nexport const deleteAccessKey = async (\n request: FastifyRequest<{ Body: DeleteAccessKeyBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const { clientId } = request.body;\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!clientId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'CLIENT_ID_NOT_FOUND'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [project.id],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const deletedAccessKey = await projectAccessKeyService.deleteAccessKey(\n clientId,\n project,\n user.id\n );\n\n if (!deletedAccessKey) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ACCESS_KEY_NOT_FOUND',\n {\n clientId,\n }\n );\n }\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Access key deleted successfully',\n es: 'Clave de acceso eliminada con éxito',\n fr: \"Clé d'accès supprimée avec succès\",\n }),\n description: t({\n en: 'The access key has been deleted successfully',\n es: 'La clave de acceso ha sido eliminada con éxito',\n fr: \"La clé d'accès a été supprimée avec succès\",\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 RefreshAccessKeyBody = { clientId: string };\nexport type RefreshAccessKeyResponse = ResponseData<OAuth2Access>;\n\n/**\n * Refreshes an access key from a project.\n */\nexport const refreshAccessKey = async (\n request: FastifyRequest<{ Body: RefreshAccessKeyBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const { clientId } = request.body;\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!clientId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'CLIENT_ID_NOT_FOUND'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [project?.id],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const newAccessKey = await projectAccessKeyService.refreshAccessKey(\n clientId,\n project?.id,\n user?.id\n );\n\n const responseData = formatResponse<OAuth2Access>({\n message: t({\n en: 'Access key refreshed successfully',\n es: 'Clave de acceso actualizada con éxito',\n fr: \"Clé d'accès actualisée avec succès\",\n }),\n description: t({\n en: 'The access key has been refreshed successfully',\n es: 'La clave de acceso ha sido actualizada con éxito',\n fr: \"La clé d'accès a été actualisée avec succès\",\n }),\n data: newAccessKey,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
@@ -2,11 +2,11 @@ import { formatResponse } from "../utils/responseData.mjs";
|
|
|
2
2
|
import { searchChunkReference } from "../utils/AI/askDocQuestion/askDocQuestion.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/controllers/search.controller.ts
|
|
5
|
-
const searchDocUtil = async (
|
|
6
|
-
const { input } =
|
|
5
|
+
const searchDocUtil = async (request, reply) => {
|
|
6
|
+
const { input } = request.query;
|
|
7
7
|
const docFileList = (await searchChunkReference(input, 30, .2)).map((doc) => doc.fileKey);
|
|
8
8
|
const responseData = formatResponse({ data: Array.from(new Set(docFileList)) });
|
|
9
|
-
|
|
9
|
+
return reply.send(responseData);
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.controller.mjs","names":["askDocQuestionUtil.searchChunkReference"],"sources":["../../../src/controllers/search.controller.ts"],"sourcesContent":["import * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type {
|
|
1
|
+
{"version":3,"file":"search.controller.mjs","names":["askDocQuestionUtil.searchChunkReference"],"sources":["../../../src/controllers/search.controller.ts"],"sourcesContent":["import * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport type SearchDocUtilParams = {\n input: string;\n};\nexport type SearchDocUtilResult = ResponseData<string[]>;\n\nexport const searchDocUtil = async (\n request: FastifyRequest<{ Querystring: SearchDocUtilParams }>,\n reply: FastifyReply\n) => {\n const { input } = request.query;\n\n const response = await askDocQuestionUtil.searchChunkReference(\n input,\n 30,\n 0.2\n );\n const docFileList = response.map((doc) => doc.fileKey);\n\n const uniqueDocFileList = Array.from(new Set(docFileList));\n\n const responseData = formatResponse<string[]>({\n data: uniqueDocFileList,\n });\n\n return reply.send(responseData);\n};\n"],"mappings":";;;;AASA,MAAa,gBAAgB,OAC3B,SACA,UACG;CACH,MAAM,EAAE,UAAU,QAAQ;CAO1B,MAAM,eALW,MAAMA,qBACrB,OACA,IACA,GACD,EAC4B,KAAK,QAAQ,IAAI,QAAQ;CAItD,MAAM,eAAe,eAAyB,EAC5C,MAHwB,MAAM,KAAK,IAAI,IAAI,YAAY,CAAC,EAIzD,CAAC;AAEF,QAAO,MAAM,KAAK,aAAa"}
|
|
@@ -3,49 +3,38 @@ import { ErrorHandler } from "../utils/errors/ErrorHandler.mjs";
|
|
|
3
3
|
import { sendEmail } from "../services/email.service.mjs";
|
|
4
4
|
import { retrievePlanInformation } from "../utils/plan.mjs";
|
|
5
5
|
import { cancelSubscription as cancelSubscription$1, getCouponId, getPricing as getPricing$1 } from "../services/subscription.service.mjs";
|
|
6
|
-
import { t } from "
|
|
6
|
+
import { t } from "fastify-intlayer";
|
|
7
7
|
import { Stripe } from "stripe";
|
|
8
8
|
|
|
9
9
|
//#region src/controllers/stripe.controller.ts
|
|
10
10
|
/**
|
|
11
11
|
* Simulate pricing for a given set of prices and a promotion code.
|
|
12
12
|
*
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
13
|
+
* @param request - The request object containing the price IDs and promotion code.
|
|
14
|
+
* @param reply - The response object to send the simulated pricing result.
|
|
15
15
|
*/
|
|
16
|
-
const getPricing = async (
|
|
17
|
-
const { priceIds, promoCode } =
|
|
16
|
+
const getPricing = async (request, reply) => {
|
|
17
|
+
const { priceIds, promoCode } = request.body;
|
|
18
18
|
const formattedPricingResult = formatResponse({ data: await getPricing$1(priceIds, promoCode) });
|
|
19
|
-
|
|
19
|
+
reply.code(200).send(formattedPricingResult);
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
22
22
|
* Handles subscription creation or update with Stripe and returns a ClientSecret.
|
|
23
|
-
* @param req - Express request object.
|
|
24
|
-
* @param res - Express response object.
|
|
25
23
|
*/
|
|
26
|
-
const getSubscription = async (
|
|
24
|
+
const getSubscription = async (request, reply) => {
|
|
27
25
|
try {
|
|
28
26
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
29
|
-
const { organization, user } =
|
|
30
|
-
const { priceId, promoCode } =
|
|
31
|
-
if (!organization)
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (!user) {
|
|
36
|
-
ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
27
|
+
const { organization, user } = request.locals || {};
|
|
28
|
+
const { priceId, promoCode } = request.body;
|
|
29
|
+
if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_FOUND");
|
|
30
|
+
if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_FOUND");
|
|
39
31
|
const { period, type } = retrievePlanInformation(priceId);
|
|
40
|
-
if (organization.plan?.subscriptionId && organization.plan?.type === type && organization.plan?.period === period && organization.plan?.status === "active") {
|
|
41
|
-
ErrorHandler.handleGenericErrorResponse(res, "ALREADY_SUBSCRIBED", { organizationId: organization.id });
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
32
|
+
if (organization.plan?.subscriptionId && organization.plan?.type === type && organization.plan?.period === period && organization.plan?.status === "active") return ErrorHandler.handleGenericErrorResponse(reply, "ALREADY_SUBSCRIBED", { organizationId: organization.id });
|
|
44
33
|
let customerId = organization.plan?.customerId;
|
|
45
34
|
if (!customerId) customerId = (await stripe.customers.create({ metadata: {
|
|
46
35
|
organizationId: String(organization.id),
|
|
47
36
|
userId: String(user.id),
|
|
48
|
-
locale:
|
|
37
|
+
locale: request.locals.locale
|
|
49
38
|
} })).id;
|
|
50
39
|
const promoCodeId = promoCode ? await getCouponId(promoCode) : null;
|
|
51
40
|
const discounts = promoCodeId ? [{ coupon: promoCodeId }] : [];
|
|
@@ -57,61 +46,39 @@ const getSubscription = async (req, res) => {
|
|
|
57
46
|
payment_behavior: "default_incomplete",
|
|
58
47
|
discounts
|
|
59
48
|
});
|
|
60
|
-
if (!subscription) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
49
|
+
if (!subscription) return ErrorHandler.handleGenericErrorResponse(reply, "SUBSCRIPTION_CREATION_FAILED", {
|
|
50
|
+
user,
|
|
51
|
+
organization,
|
|
52
|
+
priceId
|
|
53
|
+
});
|
|
68
54
|
const clientSecret = subscription.latest_invoice?.confirmation_secret?.client_secret;
|
|
69
|
-
if (!clientSecret) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
55
|
+
if (!clientSecret) return ErrorHandler.handleGenericErrorResponse(reply, "SUBSCRIPTION_CREATION_FAILED", {
|
|
56
|
+
user,
|
|
57
|
+
organization,
|
|
58
|
+
priceId
|
|
59
|
+
});
|
|
77
60
|
const responseData = formatResponse({ data: {
|
|
78
61
|
subscription,
|
|
79
62
|
clientSecret
|
|
80
63
|
} });
|
|
81
|
-
|
|
82
|
-
return;
|
|
64
|
+
return reply.send(responseData);
|
|
83
65
|
} catch (error) {
|
|
84
|
-
ErrorHandler.handleAppErrorResponse(
|
|
85
|
-
return;
|
|
66
|
+
return ErrorHandler.handleAppErrorResponse(reply, error);
|
|
86
67
|
}
|
|
87
68
|
};
|
|
88
69
|
/**
|
|
89
70
|
* Cancels a subscription for an organization.
|
|
90
|
-
* @param _req - Express request object.
|
|
91
|
-
* @param res - Express response object.
|
|
92
71
|
*/
|
|
93
|
-
const cancelSubscription = async (
|
|
72
|
+
const cancelSubscription = async (_request, reply) => {
|
|
94
73
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
95
74
|
try {
|
|
96
|
-
const { organization, user } =
|
|
97
|
-
if (!organization)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
if (!user) {
|
|
102
|
-
ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (!organization.plan?.subscriptionId) {
|
|
106
|
-
ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_PLAN_NOT_FOUND");
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
75
|
+
const { organization, user } = _request.locals || {};
|
|
76
|
+
if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_FOUND");
|
|
77
|
+
if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_FOUND");
|
|
78
|
+
if (!organization.plan?.subscriptionId) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_PLAN_NOT_FOUND");
|
|
109
79
|
await stripe.subscriptions.cancel(organization.plan.subscriptionId);
|
|
110
80
|
const plan = await cancelSubscription$1(organization.plan.subscriptionId, String(organization.id));
|
|
111
|
-
if (!plan)
|
|
112
|
-
ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_PLAN_NOT_FOUND");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
81
|
+
if (!plan) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_PLAN_NOT_FOUND");
|
|
115
82
|
const formattedPlan = formatResponse({
|
|
116
83
|
message: t({
|
|
117
84
|
en: "Subscription cancelled successfully",
|
|
@@ -125,19 +92,19 @@ const cancelSubscription = async (_req, res) => {
|
|
|
125
92
|
}),
|
|
126
93
|
data: plan
|
|
127
94
|
});
|
|
128
|
-
|
|
95
|
+
reply.send(formattedPlan);
|
|
129
96
|
await sendEmail({
|
|
130
97
|
type: "subscriptionPaymentCancellation",
|
|
131
98
|
to: user.email,
|
|
132
99
|
email: user.email,
|
|
133
100
|
cancellationDate: (/* @__PURE__ */ new Date()).toLocaleDateString(),
|
|
134
|
-
reactivateLink: `${process.env.
|
|
101
|
+
reactivateLink: `${process.env.APP_URL}/pricing`,
|
|
135
102
|
username: user.name,
|
|
136
103
|
organizationName: organization.name,
|
|
137
104
|
planName: plan.type
|
|
138
105
|
});
|
|
139
106
|
} catch (error) {
|
|
140
|
-
ErrorHandler.handleAppErrorResponse(
|
|
107
|
+
return ErrorHandler.handleAppErrorResponse(reply, error);
|
|
141
108
|
}
|
|
142
109
|
};
|
|
143
110
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stripe.controller.mjs","names":["subscriptionService.getPricing","subscriptionService.getCouponId","discounts: Stripe.SubscriptionCreateParams.Discount[]","subscriptionService.cancelSubscription","emailService.sendEmail"],"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as emailService from '@services/email.service';\nimport * as subscriptionService from '@services/subscription.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { Request } from 'express';\nimport { t } from 'express-intlayer';\nimport { Stripe } from 'stripe';\nimport type { Organization } from '@/types/organization.types';\n\nexport type GetPricingBody = {\n priceIds: string[];\n promoCode?: string;\n};\n\nexport type GetPricingResult = ResponseData<subscriptionService.PricingResult>;\n\n/**\n * Simulate pricing for a given set of prices and a promotion code.\n *\n * @param req - The request object containing the price IDs and promotion code.\n * @param res - The response object to send the simulated pricing result.\n */\nexport const getPricing = async (\n req: Request<undefined, undefined, GetPricingBody>,\n res: ResponseWithSession<GetPricingResult>\n) => {\n const { priceIds, promoCode } = req.body;\n\n const pricingResult = await subscriptionService.getPricing(\n priceIds,\n promoCode\n );\n\n const formattedPricingResult =\n formatResponse<subscriptionService.PricingResult>({\n data: pricingResult,\n });\n\n res.status(200).json(formattedPricingResult);\n};\n\nexport type GetCheckoutSessionBody = {\n priceId: string;\n promoCode?: string;\n};\n\nexport type GetCheckoutSessionResult = ResponseData<{\n subscription: Stripe.Response<Stripe.Subscription>;\n clientSecret: string;\n}>;\n\n/**\n * Handles subscription creation or update with Stripe and returns a ClientSecret.\n * @param req - Express request object.\n * @param res - Express response object.\n */\nexport const getSubscription = async (\n req: Request<undefined, undefined, GetCheckoutSessionBody>,\n res: ResponseWithSession<GetCheckoutSessionResult>\n): Promise<void> => {\n try {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n // Extract organization and user from response locals (set by authentication middleware)\n const { organization, user } = res.locals;\n // Get the price ID (Stripe Price ID) from the request body\n const { priceId, promoCode } = req.body;\n\n // Validate that the organization exists\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND');\n return;\n }\n\n // Validate that the user exists\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n const { period, type } = retrievePlanInformation(priceId);\n\n if (\n organization.plan?.subscriptionId &&\n organization.plan?.type === type &&\n organization.plan?.period === period &&\n organization.plan?.status === 'active'\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'ALREADY_SUBSCRIBED', {\n organizationId: organization.id,\n });\n return;\n }\n\n // Attempt to retrieve the Stripe customer ID from the organization's plan\n let customerId = organization.plan?.customerId;\n\n if (!customerId) {\n // If no customer ID exists, create a new Stripe customer for the organization\n const customer = await stripe.customers.create({\n metadata: {\n organizationId: String(organization.id),\n userId: String(user.id),\n // Include the locale for potential localization\n locale: (res.locals as unknown as { locale: Locale }).locale,\n },\n });\n customerId = customer.id;\n }\n\n const promoCodeId = promoCode\n ? await subscriptionService.getCouponId(promoCode)\n : null;\n\n const discounts: Stripe.SubscriptionCreateParams.Discount[] = promoCodeId\n ? [{ coupon: promoCodeId }]\n : [];\n\n // If no subscription exists, create a new one\n const subscription = await stripe.subscriptions.create({\n customer: customerId, // Associate the subscription with the customer\n items: [{ price: priceId }], // Set the price ID for the subscription\n expand: ['latest_invoice.confirmation_secret'],\n payment_settings: {\n payment_method_types: ['card'], // Specify payment method types\n },\n payment_behavior: 'default_incomplete', // Create the subscription in an incomplete state until payment is confirmed\n discounts,\n });\n\n // Handle subscription creation failure\n if (!subscription) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n return;\n }\n\n const clientSecret = (\n subscription.latest_invoice as Stripe.Invoice & {\n confirmation_secret?: { client_secret: string };\n }\n )?.confirmation_secret?.client_secret;\n\n // Handle subscription creation failure\n if (!clientSecret) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n return;\n }\n\n // Prepare the response data with subscription details\n const responseData = formatResponse<GetCheckoutSessionResult['data']>({\n data: { subscription, clientSecret },\n });\n\n // Send the response back to the client\n res.json(responseData);\n\n return;\n } catch (error) {\n // Handle any errors that occur during the process\n\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\ntype CancelSubscriptionData = Organization['plan'];\n\ntype CancelSubscriptionResult = ResponseData<CancelSubscriptionData>;\n\n/**\n * Cancels a subscription for an organization.\n * @param _req - Express request object.\n * @param res - Express response object.\n */\nexport const cancelSubscription = async (\n _req: Request,\n res: ResponseWithSession<CancelSubscriptionResult>\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Extract the organization and user from the response locals\n // These are typically set by authentication middleware earlier in the request pipeline\n const { organization, user } = res.locals;\n\n // Validate that the organization exists\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND');\n return;\n }\n\n // Validate that the user exists\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n // Check if the organization has an active subscription to cancel\n if (!organization.plan?.subscriptionId) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n return;\n }\n\n // Cancel the subscription on Stripe immediately using the subscription ID\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n\n // Update the organization's plan in the database to reflect the cancellation\n const plan = await subscriptionService.cancelSubscription(\n organization.plan.subscriptionId,\n String(organization.id)\n );\n\n // If the plan could not be updated in the database, handle the error\n if (!plan) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n return;\n }\n\n // Prepare a formatted response with a success message and the updated plan data\n const formattedPlan = formatResponse<CancelSubscriptionData>({\n message: t({\n en: 'Subscription cancelled successfully',\n fr: 'Souscription annulée avec succès',\n es: 'Suscripción cancelada con éxito',\n }),\n description: t({\n en: 'Your subscription has been cancelled successfully',\n fr: 'Votre souscription a été annulée avec succès',\n es: 'Su suscripción ha sido cancelada con éxito',\n }),\n data: plan!,\n });\n\n // Send the response back to the client\n res.json(formattedPlan);\n\n await emailService.sendEmail({\n type: 'subscriptionPaymentCancellation',\n to: user.email,\n email: user.email,\n cancellationDate: new Date().toLocaleDateString(),\n reactivateLink: `${process.env.CLIENT_URL}/pricing`,\n username: user.name,\n organizationName: organization.name,\n planName: plan.type,\n });\n } catch (error) {\n // Handle any errors that occur during the cancellation process\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stripe.controller.mjs","names":["subscriptionService.getPricing","subscriptionService.getCouponId","discounts: Stripe.SubscriptionCreateParams.Discount[]","subscriptionService.cancelSubscription","emailService.sendEmail"],"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types';\nimport * as emailService from '@services/email.service';\nimport * as subscriptionService from '@services/subscription.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport { Stripe } from 'stripe';\nimport type { Organization } from '@/types/organization.types';\n\nexport type GetPricingBody = {\n priceIds: string[];\n promoCode?: string;\n};\n\nexport type GetPricingResult = ResponseData<subscriptionService.PricingResult>;\n\n/**\n * Simulate pricing for a given set of prices and a promotion code.\n *\n * @param request - The request object containing the price IDs and promotion code.\n * @param reply - The response object to send the simulated pricing result.\n */\nexport const getPricing = async (\n request: FastifyRequest<{ Body: GetPricingBody }>,\n reply: FastifyReply\n) => {\n const { priceIds, promoCode } = request.body;\n\n const pricingResult = await subscriptionService.getPricing(\n priceIds,\n promoCode\n );\n\n const formattedPricingResult =\n formatResponse<subscriptionService.PricingResult>({\n data: pricingResult,\n });\n\n reply.code(200).send(formattedPricingResult);\n};\n\nexport type GetCheckoutSessionBody = {\n priceId: string;\n promoCode?: string;\n};\n\nexport type GetCheckoutSessionResult = ResponseData<{\n subscription: Stripe.Response<Stripe.Subscription>;\n clientSecret: string;\n}>;\n\n/**\n * Handles subscription creation or update with Stripe and returns a ClientSecret.\n */\nexport const getSubscription = async (\n request: FastifyRequest<{ Body: GetCheckoutSessionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n try {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n // Extract organization and user from request locals (set by authentication middleware)\n const { organization, user } = request.locals || {};\n // Get the price ID (Stripe Price ID) from the request body\n const { priceId, promoCode } = request.body;\n\n // Validate that the organization exists\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n // Validate that the user exists\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const { period, type } = retrievePlanInformation(priceId);\n\n if (\n organization.plan?.subscriptionId &&\n organization.plan?.type === type &&\n organization.plan?.period === period &&\n organization.plan?.status === 'active'\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ALREADY_SUBSCRIBED',\n {\n organizationId: organization.id,\n }\n );\n }\n\n // Attempt to retrieve the Stripe customer ID from the organization's plan\n let customerId = organization.plan?.customerId;\n\n if (!customerId) {\n // If no customer ID exists, create a new Stripe customer for the organization\n const customer = await stripe.customers.create({\n metadata: {\n organizationId: String(organization.id),\n userId: String(user.id),\n // Include the locale for potential localization\n locale: (request.locals as unknown as { locale: Locale }).locale,\n },\n });\n customerId = customer.id;\n }\n\n const promoCodeId = promoCode\n ? await subscriptionService.getCouponId(promoCode)\n : null;\n\n const discounts: Stripe.SubscriptionCreateParams.Discount[] = promoCodeId\n ? [{ coupon: promoCodeId }]\n : [];\n\n // If no subscription exists, create a new one\n const subscription = await stripe.subscriptions.create({\n customer: customerId, // Associate the subscription with the customer\n items: [{ price: priceId }], // Set the price ID for the subscription\n expand: ['latest_invoice.confirmation_secret'],\n payment_settings: {\n payment_method_types: ['card'], // Specify payment method types\n },\n payment_behavior: 'default_incomplete', // Create the subscription in an incomplete state until payment is confirmed\n discounts,\n });\n\n // Handle subscription creation failure\n if (!subscription) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n }\n\n const clientSecret = (\n subscription.latest_invoice as Stripe.Invoice & {\n confirmation_secret?: { client_secret: string };\n }\n )?.confirmation_secret?.client_secret;\n\n // Handle subscription creation failure\n if (!clientSecret) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n }\n\n // Prepare the response data with subscription details\n const responseData = formatResponse<GetCheckoutSessionResult['data']>({\n data: { subscription, clientSecret },\n });\n\n // Send the response back to the client\n return reply.send(responseData);\n } catch (error) {\n // Handle any errors that occur during the process\n\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype CancelSubscriptionData = Organization['plan'];\nexport type CancelSubscriptionResult = ResponseData<CancelSubscriptionData>;\n\n/**\n * Cancels a subscription for an organization.\n */\nexport const cancelSubscription = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Extract the organization and user from the request locals\n // These are typically set by authentication middleware earlier in the request pipeline\n const { organization, user } = _request.locals || {};\n\n // Validate that the organization exists\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n // Validate that the user exists\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n // Check if the organization has an active subscription to cancel\n if (!organization.plan?.subscriptionId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n }\n\n // Cancel the subscription on Stripe immediately using the subscription ID\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n\n // Update the organization's plan in the database to reflect the cancellation\n const plan = await subscriptionService.cancelSubscription(\n organization.plan.subscriptionId,\n String(organization.id)\n );\n\n // If the plan could not be updated in the database, handle the error\n if (!plan) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n }\n\n // Prepare a formatted response with a success message and the updated plan data\n const formattedPlan = formatResponse<CancelSubscriptionData>({\n message: t({\n en: 'Subscription cancelled successfully',\n fr: 'Souscription annulée avec succès',\n es: 'Suscripción cancelada con éxito',\n }),\n description: t({\n en: 'Your subscription has been cancelled successfully',\n fr: 'Votre souscription a été annulée avec succès',\n es: 'Su suscripción ha sido cancelada con éxito',\n }),\n data: plan!,\n });\n\n // Send the response back to the client\n reply.send(formattedPlan);\n\n await emailService.sendEmail({\n type: 'subscriptionPaymentCancellation',\n to: user.email,\n email: user.email,\n cancellationDate: new Date().toLocaleDateString(),\n reactivateLink: `${process.env.APP_URL}/pricing`,\n username: user.name,\n organizationName: organization.name,\n planName: plan.type,\n });\n } catch (error) {\n // Handle any errors that occur during the cancellation process\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|