@intlayer/backend 5.7.3 → 5.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
  2. package/dist/cjs/controllers/newsletter.controller.cjs +12 -13
  3. package/dist/cjs/controllers/newsletter.controller.cjs.map +1 -1
  4. package/dist/cjs/controllers/organization.controller.cjs +28 -41
  5. package/dist/cjs/controllers/organization.controller.cjs.map +1 -1
  6. package/dist/cjs/controllers/project.controller.cjs +7 -1
  7. package/dist/cjs/controllers/project.controller.cjs.map +1 -1
  8. package/dist/cjs/controllers/projectAccessKey.controller.cjs +9 -3
  9. package/dist/cjs/controllers/projectAccessKey.controller.cjs.map +1 -1
  10. package/dist/cjs/controllers/stripe.controller.cjs +15 -3
  11. package/dist/cjs/controllers/stripe.controller.cjs.map +1 -1
  12. package/dist/cjs/controllers/tag.controller.cjs +4 -6
  13. package/dist/cjs/controllers/tag.controller.cjs.map +1 -1
  14. package/dist/cjs/controllers/user.controller.cjs +19 -14
  15. package/dist/cjs/controllers/user.controller.cjs.map +1 -1
  16. package/dist/cjs/emails/OAuthTokenCreatedEmail.cjs +5 -7
  17. package/dist/cjs/emails/OAuthTokenCreatedEmail.cjs.map +1 -1
  18. package/dist/cjs/schemas/dictionary.schema.cjs +1 -1
  19. package/dist/cjs/schemas/dictionary.schema.cjs.map +1 -1
  20. package/dist/cjs/schemas/discussion.schema.cjs +1 -1
  21. package/dist/cjs/schemas/discussion.schema.cjs.map +1 -1
  22. package/dist/cjs/schemas/oAuth2.schema.cjs +1 -1
  23. package/dist/cjs/schemas/oAuth2.schema.cjs.map +1 -1
  24. package/dist/cjs/schemas/organization.schema.cjs +1 -1
  25. package/dist/cjs/schemas/organization.schema.cjs.map +1 -1
  26. package/dist/cjs/schemas/plans.schema.cjs +1 -1
  27. package/dist/cjs/schemas/plans.schema.cjs.map +1 -1
  28. package/dist/cjs/schemas/project.schema.cjs +1 -1
  29. package/dist/cjs/schemas/project.schema.cjs.map +1 -1
  30. package/dist/cjs/schemas/session.schema.cjs +1 -1
  31. package/dist/cjs/schemas/session.schema.cjs.map +1 -1
  32. package/dist/cjs/schemas/tag.schema.cjs +1 -1
  33. package/dist/cjs/schemas/tag.schema.cjs.map +1 -1
  34. package/dist/cjs/schemas/user.schema.cjs +1 -1
  35. package/dist/cjs/schemas/user.schema.cjs.map +1 -1
  36. package/dist/cjs/types/organization.types.cjs.map +1 -1
  37. package/dist/cjs/types/project.types.cjs.map +1 -1
  38. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs +3 -0
  39. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs.map +1 -1
  40. package/dist/cjs/utils/auth/getAuth.cjs +8 -5
  41. package/dist/cjs/utils/auth/getAuth.cjs.map +1 -1
  42. package/dist/cjs/utils/errors/errorCodes.cjs +13 -0
  43. package/dist/cjs/utils/errors/errorCodes.cjs.map +1 -1
  44. package/dist/cjs/utils/mapper/project.cjs +1 -2
  45. package/dist/cjs/utils/mapper/project.cjs.map +1 -1
  46. package/dist/cjs/utils/mapper/session.cjs +37 -0
  47. package/dist/cjs/utils/mapper/session.cjs.map +1 -0
  48. package/dist/cjs/utils/permissions.cjs +51 -34
  49. package/dist/cjs/utils/permissions.cjs.map +1 -1
  50. package/dist/cjs/utils/validation/validateArray.cjs +5 -1
  51. package/dist/cjs/utils/validation/validateArray.cjs.map +1 -1
  52. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  53. package/dist/esm/controllers/newsletter.controller.mjs +12 -13
  54. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  55. package/dist/esm/controllers/organization.controller.mjs +28 -41
  56. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  57. package/dist/esm/controllers/project.controller.mjs +7 -1
  58. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  59. package/dist/esm/controllers/projectAccessKey.controller.mjs +10 -4
  60. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  61. package/dist/esm/controllers/stripe.controller.mjs +15 -3
  62. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  63. package/dist/esm/controllers/tag.controller.mjs +4 -6
  64. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  65. package/dist/esm/controllers/user.controller.mjs +19 -14
  66. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  67. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs +5 -7
  68. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
  69. package/dist/esm/schemas/dictionary.schema.mjs +1 -1
  70. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  71. package/dist/esm/schemas/discussion.schema.mjs +1 -1
  72. package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
  73. package/dist/esm/schemas/oAuth2.schema.mjs +1 -1
  74. package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
  75. package/dist/esm/schemas/organization.schema.mjs +1 -1
  76. package/dist/esm/schemas/organization.schema.mjs.map +1 -1
  77. package/dist/esm/schemas/plans.schema.mjs +1 -1
  78. package/dist/esm/schemas/plans.schema.mjs.map +1 -1
  79. package/dist/esm/schemas/project.schema.mjs +1 -1
  80. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  81. package/dist/esm/schemas/session.schema.mjs +1 -1
  82. package/dist/esm/schemas/session.schema.mjs.map +1 -1
  83. package/dist/esm/schemas/tag.schema.mjs +1 -1
  84. package/dist/esm/schemas/tag.schema.mjs.map +1 -1
  85. package/dist/esm/schemas/user.schema.mjs +1 -1
  86. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  87. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs +3 -0
  88. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  89. package/dist/esm/utils/auth/getAuth.mjs +8 -5
  90. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  91. package/dist/esm/utils/errors/errorCodes.mjs +13 -0
  92. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  93. package/dist/esm/utils/mapper/project.mjs +1 -2
  94. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  95. package/dist/esm/utils/mapper/session.mjs +13 -0
  96. package/dist/esm/utils/mapper/session.mjs.map +1 -0
  97. package/dist/esm/utils/permissions.mjs +51 -34
  98. package/dist/esm/utils/permissions.mjs.map +1 -1
  99. package/dist/esm/utils/validation/validateArray.mjs +5 -1
  100. package/dist/esm/utils/validation/validateArray.mjs.map +1 -1
  101. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  102. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  103. package/dist/types/controllers/organization.controller.d.ts +3 -5
  104. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  105. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  106. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  107. package/dist/types/controllers/stripe.controller.d.ts +4 -1
  108. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  109. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  110. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  111. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
  112. package/dist/types/types/organization.types.d.ts +1 -3
  113. package/dist/types/types/organization.types.d.ts.map +1 -1
  114. package/dist/types/types/project.types.d.ts +1 -3
  115. package/dist/types/types/project.types.d.ts.map +1 -1
  116. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
  117. package/dist/types/utils/auth/getAuth.d.ts +2 -2
  118. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  119. package/dist/types/utils/errors/errorCodes.d.ts +13 -0
  120. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  121. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  122. package/dist/types/utils/mapper/session.d.ts +4 -0
  123. package/dist/types/utils/mapper/session.d.ts.map +1 -0
  124. package/dist/types/utils/permissions.d.ts +32 -28
  125. package/dist/types/utils/permissions.d.ts.map +1 -1
  126. package/dist/types/utils/validation/validateArray.d.ts.map +1 -1
  127. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/organization.controller.ts"],"sourcesContent":["import type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n} from '@/types/organization.types';\nimport type { User } from '@/types/user.types';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { SessionModel } from '@models/session.model';\nimport { sendEmail } from '@services/email.service';\nimport * as organizationService from '@services/organization.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport { ErrorHandler, type AppError } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getOrganizationFiltersAndPagination,\n type OrganizationFilters,\n type OrganizationFiltersParams,\n} from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n mapOrganizationToAPI,\n mapOrganizationsToAPI,\n} from '@utils/mapper/organization';\nimport { hasPermission } from '@utils/permissions';\nimport { getPLanDetails } from '@utils/plan';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\nimport { Types } from 'mongoose';\nimport { Stripe } from 'stripe';\n\nexport type GetOrganizationsParams =\n FiltersAndPagination<OrganizationFiltersParams>;\nexport type GetOrganizationsResult = PaginatedResponse<OrganizationAPI>;\n\n/**\n * Retrieves a list of organizations based on filters and pagination.\n */\nexport const getOrganizations = async (\n req: Request<GetOrganizationsParams>,\n res: ResponseWithSession<GetOrganizationsResult>,\n _next: NextFunction\n) => {\n const { user, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getOrganizationFiltersAndPagination(req);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n const restrictedFilter: OrganizationFilters = {\n ...filters,\n membersIds: { $in: [...(filters?.membersIds ?? []), String(user.id)] },\n };\n\n try {\n const organizations = await organizationService.findOrganizations(\n restrictedFilter,\n skip,\n pageSize\n );\n\n if (\n !hasPermission(\n roles,\n 'organization:read'\n )({\n ...res.locals,\n targetOrganizations: organizations,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await organizationService.countOrganizations(filters);\n\n const responseData = formatPaginatedResponse<OrganizationAPI>({\n data: mapOrganizationsToAPI(organizations),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.status(200).json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetOrganizationParam = { organizationId: string };\nexport type GetOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Retrieves an organization by its ID.\n */\nexport const getOrganization = async (\n req: Request<GetOrganizationParam, any, any>,\n res: ResponseWithSession<GetOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { roles } = res.locals;\n const { organizationId } = req.params as Partial<GetOrganizationParam>;\n\n if (!organizationId) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_ID_NOT_FOUND');\n return;\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles,\n 'organization:read'\n )({\n ...res.locals,\n targetOrganizations: [organization],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const responseData = formatResponse<OrganizationAPI>({\n data: mapOrganizationToAPI(organization),\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 AddOrganizationBody = OrganizationCreationData;\nexport type AddOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Adds a new organization to the database.\n */\nexport const addOrganization = async (\n req: Request<any, any, AddOrganizationBody>,\n res: ResponseWithSession<AddOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, roles } = res.locals;\n const organization = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_DATA_NOT_FOUND');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const newOrganization = await organizationService.createOrganization(\n organization,\n user.id\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization created successfully',\n fr: 'Organisation créée avec succès',\n es: 'Organización creada con éxito',\n }),\n description: t({\n en: 'Your organization has been created successfully',\n fr: 'Votre organisation a été créée avec succès',\n es: 'Su organización ha sido creada con éxito',\n }),\n data: mapOrganizationToAPI(newOrganization),\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 UpdateOrganizationBody = Partial<Organization>;\nexport type UpdateOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Updates an existing organization in the database.\n */\nexport const updateOrganization = async (\n req: Request<undefined, undefined, UpdateOrganizationBody>,\n res: ResponseWithSession<UpdateOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, roles } = res.locals;\n const organizationFields = req.body;\n\n if (!organizationFields) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_DATA_NOT_FOUND');\n return;\n }\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'organization:write'\n )({\n ...res.locals,\n targetOrganizations: [organization],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedOrganization =\n await organizationService.updateOrganizationById(\n organization.id,\n organizationFields\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\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 };\n\nexport type OrganizationMemberByIdOption = {\n userId: string | Types.ObjectId;\n isAdmin?: boolean;\n};\n\nexport type AddOrganizationMemberBody = {\n userEmail: string;\n};\nexport type AddOrganizationMemberResult = ResponseData<OrganizationAPI>;\n\n/**\n * Add member to the organization in the database.\n */\nexport const addOrganizationMember = async (\n req: Request<any, any, AddOrganizationMemberBody>,\n res: ResponseWithSession<AddOrganizationMemberResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, user, roles } = res.locals;\n const { userEmail } = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!organization.plan) {\n ErrorHandler.handleGenericErrorResponse(res, 'PLAN_NOT_FOUND', {\n organizationId: organization.id,\n });\n return;\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 planType = getPLanDetails(organization.plan);\n\n if (\n planType.numberOfOrganizationUsers &&\n organization.membersIds.length >= planType.numberOfOrganizationUsers\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PLAN_USER_LIMIT_REACHED', {\n organizationId: organization.id,\n });\n return;\n }\n\n try {\n let newMember = await userService.getUserByEmail(userEmail);\n\n if (!newMember) {\n // Create user if not found\n const newUser = await userService.createUser({ email: userEmail });\n if (!newUser) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_CREATION_FAILED', {\n email: userEmail,\n });\n return;\n }\n\n newMember = newUser;\n }\n\n await sendEmail({\n type: 'invite',\n to: userEmail,\n username: newMember.email.slice(0, newMember.email.indexOf('@')),\n invitedByUsername: user.name,\n invitedByEmail: user.email,\n organizationName: organization.name,\n inviteLink: `${process.env.FRONTEND_URL}/login?email=${newMember.email}`,\n inviteFromIp: req.ip ?? '',\n inviteFromLocation: req.hostname,\n });\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n ...organization,\n membersIds: [...organization.membersIds, newMember.id],\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\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 UpdateOrganizationMembersBody = Partial<{\n membersIds: OrganizationMemberByIdOption[];\n}>;\nexport type UpdateOrganizationMembersResult = ResponseData<OrganizationAPI>;\n\n/**\n * Update members to the organization in the database.\n */\nexport const updateOrganizationMembers = async (\n req: Request<any, any, UpdateOrganizationMembersBody>,\n res: ResponseWithSession<UpdateOrganizationMembersResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, roles } = res.locals;\n const { membersIds } = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (membersIds?.length === 0) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n return;\n }\n\n if (membersIds?.map((el) => el.isAdmin)?.length === 0) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n return;\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 try {\n let existingUsers: UserAndAdmin[] = [];\n\n if (membersIds) {\n const userIdList = membersIds?.map((member) => member.userId);\n const users = await userService.getUsersByIds(userIdList);\n\n if (users) {\n const userMap: UserAndAdmin[] = users.map((user) => {\n const isAdmin =\n membersIds.find(\n (member) => String(member.userId) === String(user.id)\n )?.isAdmin ?? false;\n\n return {\n user,\n isAdmin,\n };\n });\n\n existingUsers = 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 =\n await organizationService.updateOrganizationById(organization.id, {\n ...organization,\n membersIds: formattedMembers,\n adminsIds: formattedAdmin,\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\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 DeleteOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Deletes an organization from the database by its ID.\n */\nexport const deleteOrganization = async (\n _req: Request,\n res: ResponseWithSession,\n _next: NextFunction\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const { organization, roles } = res.locals;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n const projects = await projectService.findProjects({\n organizationId: organization.id,\n });\n\n if (projects.length > 0) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECTS_EXIST', {\n organizationId: organization.id,\n });\n return;\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 try {\n // Cancel the subscription on Stripe if it exists\n if (organization.plan?.subscriptionId) {\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n }\n\n const deletedOrganization =\n await organizationService.deleteOrganizationById(organization.id);\n\n if (!deletedOrganization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND', {\n organizationId: organization.id,\n });\n return;\n }\n\n logger.info(`Organization deleted: ${String(deletedOrganization.id)}`);\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization deleted successfully',\n fr: 'Organisation supprimée avec succès',\n es: 'Organización eliminada con éxito',\n }),\n description: t({\n en: 'Your organization has been deleted successfully',\n fr: 'Votre organisation a été supprimée avec succès',\n es: 'Su organización ha sido eliminada con éxito',\n }),\n data: mapOrganizationToAPI(deletedOrganization),\n });\n\n // No need to update session here, as it's a delete operation\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type SelectOrganizationParam = {\n organizationId: string | Types.ObjectId;\n};\nexport type SelectOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Select an organization.\n */\nexport const selectOrganization = async (\n req: Request<SelectOrganizationParam>,\n res: ResponseWithSession<SelectOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organizationId } = req.params as Partial<SelectOrganizationParam>;\n const { session } = res.locals;\n\n if (!organizationId) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_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 organization =\n await organizationService.getOrganizationById(organizationId);\n\n // Update session to set activeOrganizationId\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: String(organization.id),\n activeProjectId: null,\n },\n }\n );\n\n // No need to update session here, as it's a select operation\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization retrieved successfully',\n fr: 'Organisation récupérée avec succès',\n es: 'Organización recuperada con éxito',\n }),\n description: t({\n en: 'Your organization has been retrieved successfully',\n fr: 'Votre organisation a été récupérée avec succès',\n es: 'Su organización ha sido recuperada con éxito',\n }),\n data: mapOrganizationToAPI(organization),\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 UnselectOrganizationResult = ResponseData<null>;\n\n/**\n * Unselect an organization.\n */\nexport const unselectOrganization = async (\n _req: Request,\n res: ResponseWithSession<UnselectOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { session } = res.locals;\n try {\n // Update session to clear activeOrganizationId and activeProjectId\n\n if (typeof session === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED');\n return;\n }\n\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: null,\n activeProjectId: null,\n },\n }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Organization unselected successfully',\n fr: 'Organisation désélectionnée avec succès',\n es: 'Organización deseleccionada con éxito',\n }),\n description: t({\n en: 'Your organization has been unselected successfully',\n fr: 'Votre organisation a été désélectionnée avec succès',\n es: 'Su organización ha sido deseleccionada con éxito',\n }),\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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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/organization.controller.ts"],"sourcesContent":["import type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n} from '@/types/organization.types';\nimport type { User, UserAPI } from '@/types/user.types';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { SessionModel } from '@models/session.model';\nimport { sendEmail } from '@services/email.service';\nimport * as organizationService from '@services/organization.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport { ErrorHandler, type AppError } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getOrganizationFiltersAndPagination,\n type OrganizationFilters,\n type OrganizationFiltersParams,\n} from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n mapOrganizationToAPI,\n mapOrganizationsToAPI,\n} from '@utils/mapper/organization';\nimport { hasPermission } from '@utils/permissions';\nimport { getPLanDetails } from '@utils/plan';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\nimport { Types } from 'mongoose';\nimport { Stripe } from 'stripe';\n\nexport type GetOrganizationsParams =\n FiltersAndPagination<OrganizationFiltersParams>;\nexport type GetOrganizationsResult = PaginatedResponse<OrganizationAPI>;\n\n/**\n * Retrieves a list of organizations based on filters and pagination.\n */\nexport const getOrganizations = async (\n req: Request<GetOrganizationsParams>,\n res: ResponseWithSession<GetOrganizationsResult>,\n _next: NextFunction\n) => {\n const { user, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getOrganizationFiltersAndPagination(req);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n const restrictedFilter: OrganizationFilters = {\n ...filters,\n membersIds: { $in: [...(filters?.membersIds ?? []), String(user.id)] },\n };\n\n try {\n const organizations = await organizationService.findOrganizations(\n restrictedFilter,\n skip,\n pageSize\n );\n\n if (\n !hasPermission(\n roles,\n 'organization:read'\n )({\n ...res.locals,\n targetOrganizations: organizations,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await organizationService.countOrganizations(filters);\n\n const responseData = formatPaginatedResponse<OrganizationAPI>({\n data: mapOrganizationsToAPI(organizations),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.status(200).json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetOrganizationParam = { organizationId: string };\nexport type GetOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Retrieves an organization by its ID.\n */\nexport const getOrganization = async (\n req: Request<GetOrganizationParam, any, any>,\n res: ResponseWithSession<GetOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { roles } = res.locals;\n const { organizationId } = req.params as Partial<GetOrganizationParam>;\n\n if (!organizationId) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_ID_NOT_FOUND');\n return;\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles,\n 'organization:read'\n )({\n ...res.locals,\n targetOrganizations: [organization],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const responseData = formatResponse<OrganizationAPI>({\n data: mapOrganizationToAPI(organization),\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 AddOrganizationBody = OrganizationCreationData;\nexport type AddOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Adds a new organization to the database.\n */\nexport const addOrganization = async (\n req: Request<any, any, AddOrganizationBody>,\n res: ResponseWithSession<AddOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, roles } = res.locals;\n const organization = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_DATA_NOT_FOUND');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const newOrganization = await organizationService.createOrganization(\n organization,\n user.id\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization created successfully',\n fr: 'Organisation créée avec succès',\n es: 'Organización creada con éxito',\n }),\n description: t({\n en: 'Your organization has been created successfully',\n fr: 'Votre organisation a été créée avec succès',\n es: 'Su organización ha sido creada con éxito',\n }),\n data: mapOrganizationToAPI(newOrganization),\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 UpdateOrganizationBody = Partial<Organization>;\nexport type UpdateOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Updates an existing organization in the database.\n */\nexport const updateOrganization = async (\n req: Request<undefined, undefined, UpdateOrganizationBody>,\n res: ResponseWithSession<UpdateOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, roles } = res.locals;\n const organizationFields = req.body;\n\n if (!organizationFields) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_DATA_NOT_FOUND');\n return;\n }\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'organization:write'\n )({\n ...res.locals,\n targetOrganizations: [organization],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedOrganization =\n await organizationService.updateOrganizationById(\n organization.id,\n organizationFields\n );\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\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 AddOrganizationMemberBody = {\n userEmail: string;\n};\nexport type AddOrganizationMemberResult = ResponseData<OrganizationAPI>;\n\n/**\n * Add member to the organization in the database.\n */\nexport const addOrganizationMember = async (\n req: Request<any, any, AddOrganizationMemberBody>,\n res: ResponseWithSession<AddOrganizationMemberResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, user, roles } = res.locals;\n const { userEmail } = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\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 planType = getPLanDetails(organization.plan);\n\n if (\n planType.numberOfOrganizationUsers &&\n organization.membersIds.length >= planType.numberOfOrganizationUsers\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PLAN_USER_LIMIT_REACHED', {\n organizationId: organization.id,\n });\n return;\n }\n\n try {\n let newMember = await userService.getUserByEmail(userEmail);\n\n if (!newMember) {\n // Create user if not found\n const newUser = await userService.createUser({ email: userEmail });\n if (!newUser) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_CREATION_FAILED', {\n email: userEmail,\n });\n return;\n }\n\n newMember = newUser;\n }\n\n await sendEmail({\n type: 'invite',\n to: userEmail,\n username: newMember.email.slice(0, newMember.email.indexOf('@')),\n invitedByUsername: user.name,\n invitedByEmail: user.email,\n organizationName: organization.name,\n inviteLink: `${process.env.CLIENT_URL}/auth/login?email=${newMember.email}`,\n inviteFromIp: req.ip ?? '',\n inviteFromLocation: req.hostname,\n });\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n ...organization,\n membersIds: [...organization.membersIds, newMember.id],\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\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 UpdateOrganizationMembersBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersResult = ResponseData<OrganizationAPI>;\n\n/**\n * Update members to the organization in the database.\n */\nexport const updateOrganizationMembers = async (\n req: Request<any, any, UpdateOrganizationMembersBody>,\n res: ResponseWithSession<UpdateOrganizationMembersResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, roles } = res.locals;\n const { membersIds, adminsIds } = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\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 if (!membersIds) {\n ErrorHandler.handleGenericErrorResponse(res, 'INVALID_REQUEST_BODY');\n return;\n }\n\n if (membersIds?.length === 0) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n return;\n }\n\n if (adminsIds?.filter((id) => membersIds?.includes(id)).length === 0) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n return;\n }\n\n try {\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n const existingAdmins = await userService.getUsersByIds(adminsIds!);\n\n if (!existingAdmins) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\n ...organization,\n membersIds: existingUsers.map((user) => user.id),\n adminsIds: existingAdmins.map((user) => user.id),\n });\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization updated successfully',\n fr: 'Organisation mise à jour avec succès',\n es: 'Organización actualizada con éxito',\n }),\n description: t({\n en: 'Your organization has been updated successfully',\n fr: 'Votre organisation a été mise à jour avec succès',\n es: 'Su organización ha sido actualizada con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\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 DeleteOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Deletes an organization from the database by its ID.\n */\nexport const deleteOrganization = async (\n _req: Request,\n res: ResponseWithSession,\n _next: NextFunction\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const { organization, roles } = res.locals;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED');\n return;\n }\n\n const projects = await projectService.findProjects({\n organizationId: organization.id,\n });\n\n if (projects.length > 0) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECTS_EXIST', {\n organizationId: organization.id,\n });\n return;\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 try {\n // Cancel the subscription on Stripe if it exists\n if (organization.plan?.subscriptionId) {\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n }\n\n const deletedOrganization =\n await organizationService.deleteOrganizationById(organization.id);\n\n if (!deletedOrganization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND', {\n organizationId: organization.id,\n });\n return;\n }\n\n logger.info(`Organization deleted: ${String(deletedOrganization.id)}`);\n\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization deleted successfully',\n fr: 'Organisation supprimée avec succès',\n es: 'Organización eliminada con éxito',\n }),\n description: t({\n en: 'Your organization has been deleted successfully',\n fr: 'Votre organisation a été supprimée avec succès',\n es: 'Su organización ha sido eliminada con éxito',\n }),\n data: mapOrganizationToAPI(deletedOrganization),\n });\n\n // No need to update session here, as it's a delete operation\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type SelectOrganizationParam = {\n organizationId: string | Types.ObjectId;\n};\nexport type SelectOrganizationResult = ResponseData<OrganizationAPI>;\n\n/**\n * Select an organization.\n */\nexport const selectOrganization = async (\n req: Request<SelectOrganizationParam>,\n res: ResponseWithSession<SelectOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organizationId } = req.params as Partial<SelectOrganizationParam>;\n const { session } = res.locals;\n\n if (!organizationId) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_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 organization =\n await organizationService.getOrganizationById(organizationId);\n\n // Update session to set activeOrganizationId\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: String(organization.id),\n activeProjectId: null,\n },\n }\n );\n\n // No need to update session here, as it's a select operation\n const responseData = formatResponse<OrganizationAPI>({\n message: t({\n en: 'Organization retrieved successfully',\n fr: 'Organisation récupérée avec succès',\n es: 'Organización recuperada con éxito',\n }),\n description: t({\n en: 'Your organization has been retrieved successfully',\n fr: 'Votre organisation a été récupérée avec succès',\n es: 'Su organización ha sido recuperada con éxito',\n }),\n data: mapOrganizationToAPI(organization),\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 UnselectOrganizationResult = ResponseData<null>;\n\n/**\n * Unselect an organization.\n */\nexport const unselectOrganization = async (\n _req: Request,\n res: ResponseWithSession<UnselectOrganizationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { session } = res.locals;\n try {\n // Update session to clear activeOrganizationId and activeProjectId\n\n if (typeof session === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED');\n return;\n }\n\n await SessionModel.updateOne(\n { _id: session.id },\n {\n $set: {\n activeOrganizationId: null,\n activeProjectId: null,\n },\n }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Organization unselected successfully',\n fr: 'Organisation désélectionnée avec succès',\n es: 'Organización deseleccionada con éxito',\n }),\n description: t({\n en: 'Your organization has been unselected successfully',\n fr: 'Votre organisation a été désélectionnée avec succès',\n es: 'Su organización ha sido deseleccionada con éxito',\n }),\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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","names":[]}
@@ -76,7 +76,13 @@ const addProject = async (req, res, _next) => {
76
76
  if (!projectData) {
77
77
  ErrorHandler.handleGenericErrorResponse(res, "PROJECT_DATA_NOT_FOUND");
78
78
  }
79
- if (!hasPermission(roles, "project:admin")(res.locals)) {
79
+ if (!hasPermission(
80
+ roles,
81
+ "organization:admin"
82
+ )({
83
+ ...res.locals,
84
+ targetOrganizations: [organization]
85
+ })) {
80
86
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
81
87
  return;
82
88
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import type {\n Project,\n ProjectAPI,\n ProjectConfiguration,\n ProjectCreationData,\n ProjectData,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\nimport { 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 ProjectFilters,\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 { Types } from 'mongoose';\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, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(req);\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 const restrictedFilter: ProjectFilters = {\n ...filters,\n membersIds: { $in: [...(filters.membersIds ?? []), String(user.id)] },\n organizationId: String(organization.id),\n };\n\n try {\n const projects = await projectService.findProjects(\n restrictedFilter,\n skip,\n pageSize\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 (!hasPermission(roles, 'project:admin')(res.locals)) {\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<Project>;\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, roles } = 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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","names":["user"]}
1
+ {"version":3,"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import type {\n Project,\n ProjectAPI,\n ProjectConfiguration,\n ProjectCreationData,\n ProjectData,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\nimport { 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 ProjectFilters,\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 { Types } from 'mongoose';\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, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(req);\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 const restrictedFilter: ProjectFilters = {\n ...filters,\n membersIds: { $in: [...(filters.membersIds ?? []), String(user.id)] },\n organizationId: String(organization.id),\n };\n\n try {\n const projects = await projectService.findProjects(\n restrictedFilter,\n skip,\n pageSize\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<Project>;\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, roles } = 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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","names":["user"]}
@@ -1,11 +1,12 @@
1
1
  import { sendEmail } from "./../services/email.service.mjs";
2
2
  import * as projectAccessKeyService from "./../services/projectAccessKey.service.mjs";
3
3
  import { ErrorHandler } from "./../utils/errors/index.mjs";
4
- import { hasPermission } from "./../utils/permissions.mjs";
4
+ import { hasPermission, intersectPermissions } from "./../utils/permissions.mjs";
5
5
  import { formatResponse } from "./../utils/responseData.mjs";
6
6
  import { t } from "express-intlayer";
7
7
  const addNewAccessKey = async (req, res, _next) => {
8
- const { user, project, roles } = res.locals;
8
+ const { user, project, roles, permissions } = res.locals;
9
+ const { grants, name, expiresAt } = req.body;
9
10
  if (!project) {
10
11
  ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
11
12
  return;
@@ -24,9 +25,14 @@ const addNewAccessKey = async (req, res, _next) => {
24
25
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
25
26
  return;
26
27
  }
28
+ const filteredPermisions = intersectPermissions(permissions, grants);
27
29
  try {
28
30
  const newAccessKey = await projectAccessKeyService.addNewAccessKey(
29
- req.body,
31
+ {
32
+ name,
33
+ expiresAt,
34
+ grants: filteredPermisions
35
+ },
30
36
  project.id,
31
37
  user
32
38
  );
@@ -48,7 +54,7 @@ const addNewAccessKey = async (req, res, _next) => {
48
54
  type: "oAuthTokenCreated",
49
55
  to: user.email,
50
56
  username: user.name,
51
- applicationName: newAccessKey.clientId,
57
+ applicationName: newAccessKey.name ?? newAccessKey.clientId,
52
58
  scopes: newAccessKey.grants,
53
59
  tokenDetailsUrl: `${process.env.CLIENT_URL}/oauth2/token`,
54
60
  securityLogUrl: `${process.env.CLIENT_URL}/security-log`,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/projectAccessKey.controller.ts"],"sourcesContent":["import type { AccessKeyData, OAuth2Access } from '@/types/project.types';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { sendEmail } from '@services/email.service';\nimport * as projectAccessKeyService from '@services/projectAccessKey.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { hasPermission } from '@utils/permissions';\nimport { type ResponseData, formatResponse } from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\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 req: Request<AddNewAccessKeyBody>,\n res: ResponseWithSession<AddNewAccessKeyResponse>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [project.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newAccessKey = await projectAccessKeyService.addNewAccessKey(\n req.body,\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 res.json(responseData);\n\n sendEmail({\n type: 'oAuthTokenCreated',\n to: user.email,\n username: user.name,\n applicationName: newAccessKey.clientId,\n scopes: newAccessKey.grants,\n tokenDetailsUrl: `${process.env.CLIENT_URL}/oauth2/token`,\n securityLogUrl: `${process.env.CLIENT_URL}/security-log`,\n supportUrl: `${process.env.CLIENT_URL}/support`,\n });\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request,\n res: ResponseWithSession<AddNewAccessKeyResponse>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { clientId } = req.body;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!clientId) {\n ErrorHandler.handleGenericErrorResponse(res, 'CLIENT_ID_NOT_FOUND');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [project.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const deletedAccessKey = await projectAccessKeyService.deleteAccessKey(\n clientId,\n project,\n user.id\n );\n\n if (!deletedAccessKey) {\n ErrorHandler.handleGenericErrorResponse(res, 'ACCESS_KEY_NOT_FOUND', {\n clientId,\n });\n return;\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 res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request<RefreshAccessKeyBody>,\n res: ResponseWithSession<RefreshAccessKeyResponse>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { clientId } = req.body;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n }\n\n if (!clientId) {\n ErrorHandler.handleGenericErrorResponse(res, 'CLIENT_ID_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [project!.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\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 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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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/projectAccessKey.controller.ts"],"sourcesContent":["import type { AccessKeyData, OAuth2Access } from '@/types/project.types';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { 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 { type ResponseData, formatResponse } from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\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 req: Request<AddNewAccessKeyBody>,\n res: ResponseWithSession<AddNewAccessKeyResponse>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles, permissions } = res.locals;\n const { grants, name, expiresAt } = req.body;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [project.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\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 res.json(responseData);\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.CLIENT_URL}/oauth2/token`,\n securityLogUrl: `${process.env.CLIENT_URL}/security-log`,\n supportUrl: `${process.env.CLIENT_URL}/support`,\n });\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request,\n res: ResponseWithSession<AddNewAccessKeyResponse>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { clientId } = req.body;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!clientId) {\n ErrorHandler.handleGenericErrorResponse(res, 'CLIENT_ID_NOT_FOUND');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [project.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const deletedAccessKey = await projectAccessKeyService.deleteAccessKey(\n clientId,\n project,\n user.id\n );\n\n if (!deletedAccessKey) {\n ErrorHandler.handleGenericErrorResponse(res, 'ACCESS_KEY_NOT_FOUND', {\n clientId,\n });\n return;\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 res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\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 req: Request<RefreshAccessKeyBody>,\n res: ResponseWithSession<RefreshAccessKeyResponse>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { clientId } = req.body;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n }\n\n if (!clientId) {\n ErrorHandler.handleGenericErrorResponse(res, 'CLIENT_ID_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles,\n 'project:write'\n )({\n ...res.locals,\n targetProjectIds: [project!.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\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 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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","names":[]}
@@ -55,8 +55,7 @@ const getSubscription = async (req, res) => {
55
55
  // Associate the subscription with the customer
56
56
  items: [{ price: priceId }],
57
57
  // Set the price ID for the subscription
58
- expand: ["latest_invoice.payment_intent"],
59
- // Expand to get payment intent details
58
+ expand: ["latest_invoice.confirmation_secret"],
60
59
  payment_settings: {
61
60
  payment_method_types: ["card"]
62
61
  // Specify payment method types
@@ -77,8 +76,21 @@ const getSubscription = async (req, res) => {
77
76
  );
78
77
  return;
79
78
  }
79
+ const clientSecret = subscription.latest_invoice?.confirmation_secret?.client_secret;
80
+ if (!clientSecret) {
81
+ ErrorHandler.handleGenericErrorResponse(
82
+ res,
83
+ "SUBSCRIPTION_CREATION_FAILED",
84
+ {
85
+ user,
86
+ organization,
87
+ priceId
88
+ }
89
+ );
90
+ return;
91
+ }
80
92
  const responseData = formatResponse({
81
- data: subscription
93
+ data: { subscription, clientSecret }
82
94
  });
83
95
  res.json(responseData);
84
96
  return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import type { Organization } from '@/types/organization.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 { type ResponseData, formatResponse } from '@utils/responseData';\nimport type { Request } from 'express';\nimport { t } from 'express-intlayer';\nimport type { Locales } from 'intlayer';\nimport { Stripe } from 'stripe';\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 Stripe.Response<Stripe.Subscription>\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: Locales }).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.payment_intent'], // Expand to get payment intent details\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: 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 // Prepare the response data with subscription details\n const responseData = formatResponse<Stripe.Response<Stripe.Subscription>>({\n data: subscription,\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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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import type { Organization } from '@/types/organization.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 { type ResponseData, formatResponse } from '@utils/responseData';\nimport type { Request } from 'express';\nimport { t } from 'express-intlayer';\nimport type { Locales } from 'intlayer';\nimport { Stripe } from 'stripe';\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: Locales }).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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","names":[]}
@@ -33,9 +33,7 @@ const getTags = async (req, res, _next) => {
33
33
  "tag:read"
34
34
  )({
35
35
  ...res.locals,
36
- targetTagProjectIds: [
37
- ...new Set(tags.map((tag) => String(tag.projectId)))
38
- ]
36
+ targetTags: tags
39
37
  })) {
40
38
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
41
39
  return;
@@ -85,7 +83,7 @@ const addTag = async (req, res, _next) => {
85
83
  "tag:admin"
86
84
  )({
87
85
  ...res.locals,
88
- targetTagProjectIds: [String(tag.projectId)]
86
+ targetTags: [tag]
89
87
  })) {
90
88
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
91
89
  return;
@@ -138,7 +136,7 @@ const updateTag = async (req, res, _next) => {
138
136
  "tag:write"
139
137
  )({
140
138
  ...res.locals,
141
- targetTagProjectIds: [String(tagToDelete.projectId)]
139
+ targetTags: [tagToDelete]
142
140
  })) {
143
141
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
144
142
  return;
@@ -191,7 +189,7 @@ const deleteTag = async (req, res, _next) => {
191
189
  "tag:admin"
192
190
  )({
193
191
  ...res.locals,
194
- targetTagProjectIds: [String(tagToDelete.projectId)]
192
+ targetTags: [tagToDelete]
195
193
  })) {
196
194
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
197
195
  return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/tag.controller.ts"],"sourcesContent":["import type {\n Tag,\n TagAPI,\n TagCreationData,\n TagData,\n TagSchema,\n} from '@/types/tag.types';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as tagService from '@services/tag.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getTagFiltersAndPagination,\n type TagFilters,\n type TagFiltersParams,\n} from '@utils/filtersAndPagination/getTagFiltersAndPagination';\nimport { mapTagsToAPI, mapTagToAPI } from '@utils/mapper/tag';\nimport { hasPermission } from '@utils/permissions';\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';\n\nexport type GetTagsParams = FiltersAndPagination<TagFiltersParams>;\nexport type GetTagsResult = PaginatedResponse<TagAPI>;\n\n/**\n * Retrieves a list of tags based on filters and pagination.\n */\nexport const getTags = async (\n req: Request<GetTagsParams>,\n res: ResponseWithSession<GetTagsResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getTagFiltersAndPagination(req);\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 const restrictedFilter: TagFilters = {\n ...filters,\n organizationId: String(organization.id),\n };\n\n try {\n const tags = await tagService.findTags(restrictedFilter, skip, pageSize);\n\n if (\n !hasPermission(\n roles,\n 'tag:read'\n )({\n ...res.locals,\n targetTagProjectIds: [\n ...new Set(tags.map((tag) => String(tag.projectId))),\n ],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await tagService.countTags(filters);\n\n const formattedTags = mapTagsToAPI(tags);\n\n const responseData = formatPaginatedResponse<TagAPI>({\n data: formattedTags,\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 AddTagBody = TagCreationData;\nexport type AddTagResult = ResponseData<TagAPI>;\n\n/**\n * Adds a new tag to the database.\n */\nexport const addTag = async (\n req: Request<any, any, AddTagBody>,\n res: ResponseWithSession<AddTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, project, user, roles } = res.locals;\n const tagData = 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 (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!tagData) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_DATA_NOT_FOUND');\n }\n\n const tag: TagData = {\n creatorId: user.id,\n organizationId: organization.id,\n projectId: project.id,\n ...tagData,\n };\n\n if (\n !hasPermission(\n roles,\n 'tag:admin'\n )({\n ...res.locals,\n targetTagProjectIds: [String(tag.projectId)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newTag = await tagService.createTag(tag);\n\n const formattedTag = mapTagToAPI(newTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag created successfully',\n fr: 'Tag créé avec succès',\n es: 'Tag creado con éxito',\n }),\n description: t({\n en: 'Your tag has been created successfully',\n fr: 'Votre tag a été créé avec succès',\n es: 'Su tag ha sido creado con éxito',\n }),\n data: formattedTag,\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 UpdateTagParams = { tagId: string | Tag['id'] };\nexport type UpdateTagBody = Partial<TagData>;\nexport type UpdateTagResult = ResponseData<TagAPI>;\n\n/**\n * Updates an existing tag in the database.\n */\nexport const updateTag = async (\n req: Request<UpdateTagParams, any, UpdateTagBody>,\n res: ResponseWithSession<UpdateTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { tagId } = req.params;\n const { organization, user, 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 try {\n const tag = {\n _id: tagId,\n name: req.body.name,\n key: req.body.key,\n description: req.body.description,\n instructions: req.body.instructions,\n } as Partial<TagSchema> & { _id: Tag['id'] };\n\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles,\n 'tag:write'\n )({\n ...res.locals,\n targetTagProjectIds: [String(tagToDelete.projectId)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_NOT_IN_ORGANIZATION');\n return;\n }\n\n const updatedTag = await tagService.updateTagById(tag._id, tag);\n\n const formattedTag = mapTagToAPI(updatedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag updated successfully',\n fr: 'Tag mis à jour avec succès',\n es: 'Tag actualizado con éxito',\n }),\n description: t({\n en: 'Your tag has been updated successfully',\n fr: 'Votre tag a été mis à jour avec succès',\n es: 'Su tag ha sido actualizado con éxito',\n }),\n data: formattedTag,\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 DeleteTagParams = { tagId: string | Tag['id'] };\nexport type DeleteTagResult = ResponseData<TagAPI>;\n\n/**\n * Deletes a tag 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 deleteTag = async (\n req: Request<DeleteTagParams>,\n res: ResponseWithSession<DeleteTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, roles } = res.locals;\n const { tagId } = req.params;\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 (!tagId) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_ID_NOT_FOUND');\n return;\n }\n\n try {\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles,\n 'tag:admin'\n )({\n ...res.locals,\n targetTagProjectIds: [String(tagToDelete.projectId)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_NOT_IN_ORGANIZATION');\n return;\n }\n\n const deletedTag = await tagService.deleteTagById(tagId);\n\n if (!deletedTag) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_NOT_FOUND', {\n tagId,\n });\n\n return;\n }\n\n logger.info(`Tag deleted: ${String(deletedTag.id)}`);\n\n const formattedTag = mapTagToAPI(deletedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag deleted successfully',\n fr: 'Tag supprimé avec succès',\n es: 'Tag eliminado con éxito',\n }),\n description: t({\n en: 'Your tag has been deleted successfully',\n fr: 'Votre tag a été supprimé avec succès',\n es: 'Su tag ha sido eliminado con éxito',\n }),\n data: formattedTag,\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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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/tag.controller.ts"],"sourcesContent":["import type {\n Tag,\n TagAPI,\n TagCreationData,\n TagData,\n TagSchema,\n} from '@/types/tag.types';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as tagService from '@services/tag.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getTagFiltersAndPagination,\n type TagFilters,\n type TagFiltersParams,\n} from '@utils/filtersAndPagination/getTagFiltersAndPagination';\nimport { mapTagsToAPI, mapTagToAPI } from '@utils/mapper/tag';\nimport { hasPermission } from '@utils/permissions';\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';\n\nexport type GetTagsParams = FiltersAndPagination<TagFiltersParams>;\nexport type GetTagsResult = PaginatedResponse<TagAPI>;\n\n/**\n * Retrieves a list of tags based on filters and pagination.\n */\nexport const getTags = async (\n req: Request<GetTagsParams>,\n res: ResponseWithSession<GetTagsResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getTagFiltersAndPagination(req);\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 const restrictedFilter: TagFilters = {\n ...filters,\n organizationId: String(organization.id),\n };\n\n try {\n const tags = await tagService.findTags(restrictedFilter, skip, pageSize);\n\n if (\n !hasPermission(\n roles,\n 'tag:read'\n )({\n ...res.locals,\n targetTags: tags,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await tagService.countTags(filters);\n\n const formattedTags = mapTagsToAPI(tags);\n\n const responseData = formatPaginatedResponse<TagAPI>({\n data: formattedTags,\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 AddTagBody = TagCreationData;\nexport type AddTagResult = ResponseData<TagAPI>;\n\n/**\n * Adds a new tag to the database.\n */\nexport const addTag = async (\n req: Request<any, any, AddTagBody>,\n res: ResponseWithSession<AddTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization, project, user, roles } = res.locals;\n const tagData = 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 (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!tagData) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_DATA_NOT_FOUND');\n }\n\n const tag: TagData = {\n creatorId: user.id,\n organizationId: organization.id,\n projectId: project.id,\n ...tagData,\n };\n\n if (\n !hasPermission(\n roles,\n 'tag:admin'\n )({\n ...res.locals,\n targetTags: [tag as Tag],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newTag = await tagService.createTag(tag);\n\n const formattedTag = mapTagToAPI(newTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag created successfully',\n fr: 'Tag créé avec succès',\n es: 'Tag creado con éxito',\n }),\n description: t({\n en: 'Your tag has been created successfully',\n fr: 'Votre tag a été créé avec succès',\n es: 'Su tag ha sido creado con éxito',\n }),\n data: formattedTag,\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 UpdateTagParams = { tagId: string | Tag['id'] };\nexport type UpdateTagBody = Partial<TagData>;\nexport type UpdateTagResult = ResponseData<TagAPI>;\n\n/**\n * Updates an existing tag in the database.\n */\nexport const updateTag = async (\n req: Request<UpdateTagParams, any, UpdateTagBody>,\n res: ResponseWithSession<UpdateTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { tagId } = req.params;\n const { organization, user, 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 try {\n const tag = {\n _id: tagId,\n name: req.body.name,\n key: req.body.key,\n description: req.body.description,\n instructions: req.body.instructions,\n } as Partial<TagSchema> & { _id: Tag['id'] };\n\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles,\n 'tag:write'\n )({\n ...res.locals,\n targetTags: [tagToDelete],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_NOT_IN_ORGANIZATION');\n return;\n }\n\n const updatedTag = await tagService.updateTagById(tag._id, tag);\n\n const formattedTag = mapTagToAPI(updatedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag updated successfully',\n fr: 'Tag mis à jour avec succès',\n es: 'Tag actualizado con éxito',\n }),\n description: t({\n en: 'Your tag has been updated successfully',\n fr: 'Votre tag a été mis à jour avec succès',\n es: 'Su tag ha sido actualizado con éxito',\n }),\n data: formattedTag,\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 DeleteTagParams = { tagId: string | Tag['id'] };\nexport type DeleteTagResult = ResponseData<TagAPI>;\n\n/**\n * Deletes a tag 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 deleteTag = async (\n req: Request<DeleteTagParams>,\n res: ResponseWithSession<DeleteTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, roles } = res.locals;\n const { tagId } = req.params;\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 (!tagId) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_ID_NOT_FOUND');\n return;\n }\n\n try {\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles,\n 'tag:admin'\n )({\n ...res.locals,\n targetTags: [tagToDelete],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_NOT_IN_ORGANIZATION');\n return;\n }\n\n const deletedTag = await tagService.deleteTagById(tagId);\n\n if (!deletedTag) {\n ErrorHandler.handleGenericErrorResponse(res, 'TAG_NOT_FOUND', {\n tagId,\n });\n\n return;\n }\n\n logger.info(`Tag deleted: ${String(deletedTag.id)}`);\n\n const formattedTag = mapTagToAPI(deletedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag deleted successfully',\n fr: 'Tag supprimé avec succès',\n es: 'Tag eliminado con éxito',\n }),\n description: t({\n en: 'Your tag has been deleted successfully',\n fr: 'Votre tag a été supprimé avec succès',\n es: 'Su tag ha sido eliminado con éxito',\n }),\n data: formattedTag,\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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","names":[]}