@intlayer/backend 7.5.9 → 7.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/README.md +9 -2
  2. package/dist/assets/utils/AI/askDocQuestion/PROMPT.md +1 -1
  3. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/ci.json +3080 -0
  4. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/list_projects.json +1 -0
  5. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_fastify.json +9 -0
  6. package/dist/esm/controllers/ai.controller.mjs +95 -128
  7. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  8. package/dist/esm/controllers/bitbucket.controller.mjs +77 -0
  9. package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -0
  10. package/dist/esm/controllers/dictionary.controller.mjs +106 -198
  11. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  12. package/dist/esm/controllers/eventListener.controller.mjs +13 -19
  13. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  14. package/dist/esm/controllers/github.controller.mjs +77 -0
  15. package/dist/esm/controllers/github.controller.mjs.map +1 -0
  16. package/dist/esm/controllers/gitlab.controller.mjs +77 -0
  17. package/dist/esm/controllers/gitlab.controller.mjs.map +1 -0
  18. package/dist/esm/controllers/newsletter.controller.mjs +30 -60
  19. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  20. package/dist/esm/controllers/oAuth2.controller.mjs +11 -8
  21. package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
  22. package/dist/esm/controllers/organization.controller.mjs +100 -225
  23. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  24. package/dist/esm/controllers/project.controller.mjs +194 -204
  25. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  26. package/dist/esm/controllers/projectAccessKey.controller.mjs +38 -71
  27. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  28. package/dist/esm/controllers/search.controller.mjs +3 -3
  29. package/dist/esm/controllers/search.controller.mjs.map +1 -1
  30. package/dist/esm/controllers/stripe.controller.mjs +34 -67
  31. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  32. package/dist/esm/controllers/tag.controller.mjs +51 -113
  33. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  34. package/dist/esm/controllers/user.controller.mjs +64 -113
  35. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  36. package/dist/esm/export.mjs +4 -1
  37. package/dist/esm/index.mjs +105 -41
  38. package/dist/esm/index.mjs.map +1 -1
  39. package/dist/esm/middlewares/oAuth2.middleware.mjs +19 -14
  40. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  41. package/dist/esm/middlewares/sessionAuth.middleware.mjs +6 -7
  42. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  43. package/dist/esm/routes/ai.routes.mjs +19 -15
  44. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  45. package/dist/esm/routes/bitbucket.routes.mjs +43 -0
  46. package/dist/esm/routes/bitbucket.routes.mjs.map +1 -0
  47. package/dist/esm/routes/dictionary.routes.mjs +10 -10
  48. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  49. package/dist/esm/routes/eventListener.routes.mjs +3 -3
  50. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  51. package/dist/esm/routes/github.routes.mjs +43 -0
  52. package/dist/esm/routes/github.routes.mjs.map +1 -0
  53. package/dist/esm/routes/gitlab.routes.mjs +43 -0
  54. package/dist/esm/routes/gitlab.routes.mjs.map +1 -0
  55. package/dist/esm/routes/newsletter.routes.mjs +5 -5
  56. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  57. package/dist/esm/routes/organization.routes.mjs +11 -11
  58. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  59. package/dist/esm/routes/project.routes.mjs +38 -14
  60. package/dist/esm/routes/project.routes.mjs.map +1 -1
  61. package/dist/esm/routes/search.routes.mjs +3 -3
  62. package/dist/esm/routes/search.routes.mjs.map +1 -1
  63. package/dist/esm/routes/stripe.routes.mjs +5 -5
  64. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  65. package/dist/esm/routes/tags.routes.mjs +6 -6
  66. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  67. package/dist/esm/routes/user.routes.mjs +9 -9
  68. package/dist/esm/routes/user.routes.mjs.map +1 -1
  69. package/dist/esm/schemas/project.schema.mjs +70 -1
  70. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  71. package/dist/esm/services/bitbucket.service.mjs +173 -0
  72. package/dist/esm/services/bitbucket.service.mjs.map +1 -0
  73. package/dist/esm/services/ci.service.mjs +134 -0
  74. package/dist/esm/services/ci.service.mjs.map +1 -0
  75. package/dist/esm/services/email.service.mjs +1 -1
  76. package/dist/esm/services/email.service.mjs.map +1 -1
  77. package/dist/esm/services/github.service.mjs +218 -0
  78. package/dist/esm/services/github.service.mjs.map +1 -0
  79. package/dist/esm/services/gitlab.service.mjs +217 -0
  80. package/dist/esm/services/gitlab.service.mjs.map +1 -0
  81. package/dist/esm/services/oAuth2.service.mjs +1 -1
  82. package/dist/esm/services/subscription.service.mjs +1 -1
  83. package/dist/esm/services/subscription.service.mjs.map +1 -1
  84. package/dist/esm/services/webhook.service.mjs +164 -0
  85. package/dist/esm/services/webhook.service.mjs.map +1 -0
  86. package/dist/esm/utils/auth/getAuth.mjs +28 -16
  87. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  88. package/dist/esm/utils/cors.mjs +15 -5
  89. package/dist/esm/utils/cors.mjs.map +1 -1
  90. package/dist/esm/utils/errors/ErrorHandler.mjs +32 -4
  91. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  92. package/dist/esm/utils/errors/ErrorsClass.mjs +1 -1
  93. package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
  94. package/dist/esm/utils/errors/errorCodes.mjs +234 -0
  95. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  96. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs +3 -2
  97. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  98. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs +1 -1
  99. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  100. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs +1 -1
  101. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
  102. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs +3 -2
  103. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  104. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs +3 -2
  105. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  106. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs +3 -2
  107. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  108. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs +3 -2
  109. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  110. package/dist/esm/utils/mapper/project.mjs +28 -1
  111. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  112. package/dist/esm/utils/mongoDB/connectDB.mjs +1 -1
  113. package/dist/esm/utils/rateLimiter.mjs +40 -30
  114. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  115. package/dist/esm/webhooks/stripe.webhook.mjs +2 -2
  116. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  117. package/dist/types/controllers/ai.controller.d.ts +29 -12
  118. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  119. package/dist/types/controllers/bitbucket.controller.d.ts +62 -0
  120. package/dist/types/controllers/bitbucket.controller.d.ts.map +1 -0
  121. package/dist/types/controllers/dictionary.controller.d.ts +23 -13
  122. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  123. package/dist/types/controllers/eventListener.controller.d.ts +4 -2
  124. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  125. package/dist/types/controllers/github.controller.d.ts +63 -0
  126. package/dist/types/controllers/github.controller.d.ts.map +1 -0
  127. package/dist/types/controllers/gitlab.controller.d.ts +67 -0
  128. package/dist/types/controllers/gitlab.controller.d.ts.map +1 -0
  129. package/dist/types/controllers/newsletter.controller.d.ts +8 -7
  130. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  131. package/dist/types/controllers/oAuth2.controller.d.ts +4 -2
  132. package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
  133. package/dist/types/controllers/organization.controller.d.ts +28 -12
  134. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  135. package/dist/types/controllers/project.controller.d.ts +60 -17
  136. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  137. package/dist/types/controllers/projectAccessKey.controller.d.ts +10 -5
  138. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  139. package/dist/types/controllers/search.controller.d.ts +4 -2
  140. package/dist/types/controllers/search.controller.d.ts.map +1 -1
  141. package/dist/types/controllers/stripe.controller.d.ts +11 -12
  142. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  143. package/dist/types/controllers/tag.controller.d.ts +14 -9
  144. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  145. package/dist/types/controllers/user.controller.d.ts +22 -9
  146. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  147. package/dist/types/emails/InviteUserEmail.d.ts +4 -4
  148. package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
  149. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
  150. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
  151. package/dist/types/emails/PasswordChangeConfirmation.d.ts +4 -4
  152. package/dist/types/emails/ResetUserPassword.d.ts +4 -4
  153. package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
  154. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
  155. package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
  156. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
  157. package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
  158. package/dist/types/emails/Welcome.d.ts +4 -4
  159. package/dist/types/export.d.ts +11 -5
  160. package/dist/types/middlewares/oAuth2.middleware.d.ts +9 -4
  161. package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
  162. package/dist/types/middlewares/sessionAuth.middleware.d.ts +13 -3
  163. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  164. package/dist/types/models/discussion.model.d.ts +3 -3
  165. package/dist/types/models/oAuth2.model.d.ts +3 -3
  166. package/dist/types/routes/ai.routes.d.ts +2 -2
  167. package/dist/types/routes/ai.routes.d.ts.map +1 -1
  168. package/dist/types/routes/bitbucket.routes.d.ts +35 -0
  169. package/dist/types/routes/bitbucket.routes.d.ts.map +1 -0
  170. package/dist/types/routes/dictionary.routes.d.ts +2 -2
  171. package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
  172. package/dist/types/routes/eventListener.routes.d.ts +2 -2
  173. package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
  174. package/dist/types/routes/github.routes.d.ts +35 -0
  175. package/dist/types/routes/github.routes.d.ts.map +1 -0
  176. package/dist/types/routes/gitlab.routes.d.ts +35 -0
  177. package/dist/types/routes/gitlab.routes.d.ts.map +1 -0
  178. package/dist/types/routes/newsletter.routes.d.ts +2 -2
  179. package/dist/types/routes/newsletter.routes.d.ts.map +1 -1
  180. package/dist/types/routes/organization.routes.d.ts +2 -2
  181. package/dist/types/routes/organization.routes.d.ts.map +1 -1
  182. package/dist/types/routes/project.routes.d.ts +22 -2
  183. package/dist/types/routes/project.routes.d.ts.map +1 -1
  184. package/dist/types/routes/search.routes.d.ts +2 -2
  185. package/dist/types/routes/search.routes.d.ts.map +1 -1
  186. package/dist/types/routes/stripe.routes.d.ts +2 -2
  187. package/dist/types/routes/stripe.routes.d.ts.map +1 -1
  188. package/dist/types/routes/tags.routes.d.ts +2 -2
  189. package/dist/types/routes/tags.routes.d.ts.map +1 -1
  190. package/dist/types/routes/user.routes.d.ts +2 -2
  191. package/dist/types/routes/user.routes.d.ts.map +1 -1
  192. package/dist/types/schemas/dictionary.schema.d.ts +6 -6
  193. package/dist/types/schemas/discussion.schema.d.ts +6 -6
  194. package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
  195. package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
  196. package/dist/types/schemas/plans.schema.d.ts +6 -6
  197. package/dist/types/schemas/project.schema.d.ts +6 -6
  198. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  199. package/dist/types/schemas/session.schema.d.ts +6 -6
  200. package/dist/types/schemas/tag.schema.d.ts +6 -6
  201. package/dist/types/schemas/user.schema.d.ts +6 -6
  202. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  203. package/dist/types/services/bitbucket.service.d.ts +71 -0
  204. package/dist/types/services/bitbucket.service.d.ts.map +1 -0
  205. package/dist/types/services/ci.service.d.ts +27 -0
  206. package/dist/types/services/ci.service.d.ts.map +1 -0
  207. package/dist/types/services/github.service.d.ts +40 -0
  208. package/dist/types/services/github.service.d.ts.map +1 -0
  209. package/dist/types/services/gitlab.service.d.ts +58 -0
  210. package/dist/types/services/gitlab.service.d.ts.map +1 -0
  211. package/dist/types/services/webhook.service.d.ts +19 -0
  212. package/dist/types/services/webhook.service.d.ts.map +1 -0
  213. package/dist/types/types/project.types.d.ts +46 -5
  214. package/dist/types/types/project.types.d.ts.map +1 -1
  215. package/dist/types/types/session.types.d.ts +1 -1
  216. package/dist/types/types/user.types.d.ts +1 -1
  217. package/dist/types/utils/AI/auditTag/index.d.ts +1 -1
  218. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  219. package/dist/types/utils/cors.d.ts +2 -2
  220. package/dist/types/utils/errors/ErrorHandler.d.ts +31 -3
  221. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  222. package/dist/types/utils/errors/ErrorsClass.d.ts +1 -1
  223. package/dist/types/utils/errors/errorCodes.d.ts +234 -0
  224. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  225. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +8 -4
  226. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
  227. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +6 -3
  228. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
  229. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts +6 -2
  230. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
  231. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +8 -4
  232. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
  233. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +6 -2
  234. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
  235. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +8 -4
  236. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
  237. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts +6 -2
  238. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
  239. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  240. package/dist/types/utils/permissions.d.ts +1 -1
  241. package/dist/types/utils/rateLimiter.d.ts +4 -2
  242. package/dist/types/utils/rateLimiter.d.ts.map +1 -1
  243. package/package.json +24 -28
  244. package/dist/esm/middlewares/request.middleware.mjs +0 -17
  245. package/dist/esm/middlewares/request.middleware.mjs.map +0 -1
  246. package/dist/types/middlewares/request.middleware.d.ts +0 -7
  247. package/dist/types/middlewares/request.middleware.d.ts.map +0 -1
@@ -10,29 +10,23 @@ import { sendEmail } from "../services/email.service.mjs";
10
10
  import { getOrganizationFiltersAndPagination } from "../utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs";
11
11
  import { mapOrganizationToAPI, mapOrganizationsToAPI } from "../utils/mapper/organization.mjs";
12
12
  import { getPlanDetails } from "../utils/plan.mjs";
13
- import { t } from "express-intlayer";
13
+ import { t } from "fastify-intlayer";
14
14
  import { Stripe } from "stripe";
15
15
 
16
16
  //#region src/controllers/organization.controller.ts
17
17
  /**
18
18
  * Retrieves a list of organizations based on filters and pagination.
19
19
  */
20
- const getOrganizations = async (req, res, _next) => {
21
- const { user, roles } = res.locals;
22
- const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getOrganizationFiltersAndPagination(req, res);
23
- if (!user) {
24
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
25
- return;
26
- }
20
+ const getOrganizations = async (request, reply) => {
21
+ const { user, roles } = request.locals || {};
22
+ const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getOrganizationFiltersAndPagination(request);
23
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
27
24
  try {
28
25
  const organizations = await findOrganizations(filters, skip, pageSize, sortOptions);
29
- if (!hasPermission(roles, "organization:read")({
30
- ...res.locals,
26
+ if (!hasPermission(roles || [], "organization:read")({
27
+ ...request.locals,
31
28
  targetOrganizations: organizations
32
- })) {
33
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
34
- return;
35
- }
29
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
36
30
  const totalItems = await countOrganizations(filters);
37
31
  const responseData = formatPaginatedResponse({
38
32
  data: mapOrganizationsToAPI(organizations),
@@ -41,54 +35,38 @@ const getOrganizations = async (req, res, _next) => {
41
35
  totalPages: getNumberOfPages(totalItems),
42
36
  totalItems
43
37
  });
44
- res.status(200).json(responseData);
45
- return;
38
+ return reply.code(200).send(responseData);
46
39
  } catch (error) {
47
- ErrorHandler.handleAppErrorResponse(res, error);
48
- return;
40
+ return ErrorHandler.handleAppErrorResponse(reply, error);
49
41
  }
50
42
  };
51
43
  /**
52
44
  * Retrieves an organization by its ID.
53
45
  */
54
- const getOrganization = async (req, res, _next) => {
55
- const { roles } = res.locals;
56
- const { organizationId } = req.params;
57
- if (!organizationId) {
58
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_ID_NOT_FOUND");
59
- return;
60
- }
46
+ const getOrganization = async (request, reply) => {
47
+ const { roles } = request.locals || {};
48
+ const { organizationId } = request.params;
49
+ if (!organizationId) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_ID_NOT_FOUND");
61
50
  try {
62
51
  const organization = await getOrganizationById(organizationId);
63
- if (!hasPermission(roles, "organization:read")({
64
- ...res.locals,
52
+ if (!hasPermission(roles || [], "organization:read")({
53
+ ...request.locals,
65
54
  targetOrganizations: [organization]
66
- })) {
67
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
68
- return;
69
- }
55
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
70
56
  const responseData = formatResponse({ data: mapOrganizationToAPI(organization) });
71
- res.json(responseData);
72
- return;
57
+ return reply.send(responseData);
73
58
  } catch (error) {
74
- ErrorHandler.handleAppErrorResponse(res, error);
75
- return;
59
+ return ErrorHandler.handleAppErrorResponse(reply, error);
76
60
  }
77
61
  };
78
62
  /**
79
63
  * Adds a new organization to the database.
80
64
  */
81
- const addOrganization = async (req, res, _next) => {
82
- const { user } = res.locals;
83
- const organization = req.body;
84
- if (!organization) {
85
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_DATA_NOT_FOUND");
86
- return;
87
- }
88
- if (!user) {
89
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
90
- return;
91
- }
65
+ const addOrganization = async (request, reply) => {
66
+ const { user } = request.locals || {};
67
+ const organization = request.body;
68
+ if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_DATA_NOT_FOUND");
69
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
92
70
  try {
93
71
  const newOrganization = await createOrganization(organization, user.id);
94
72
  const responseData = formatResponse({
@@ -104,34 +82,23 @@ const addOrganization = async (req, res, _next) => {
104
82
  }),
105
83
  data: mapOrganizationToAPI(newOrganization)
106
84
  });
107
- res.json(responseData);
108
- return;
85
+ return reply.send(responseData);
109
86
  } catch (error) {
110
- ErrorHandler.handleAppErrorResponse(res, error);
111
- return;
87
+ return ErrorHandler.handleAppErrorResponse(reply, error);
112
88
  }
113
89
  };
114
90
  /**
115
91
  * Updates an existing organization in the database.
116
92
  */
117
- const updateOrganization = async (req, res, _next) => {
118
- const { organization, roles } = res.locals;
119
- const organizationFields = req.body;
120
- if (!organizationFields) {
121
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_DATA_NOT_FOUND");
122
- return;
123
- }
124
- if (!organization) {
125
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_DEFINED");
126
- return;
127
- }
128
- if (!hasPermission(roles, "organization:write")({
129
- ...res.locals,
93
+ const updateOrganization = async (request, reply) => {
94
+ const { organization, roles } = request.locals || {};
95
+ const organizationFields = request.body;
96
+ if (!organizationFields) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_DATA_NOT_FOUND");
97
+ if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_DEFINED");
98
+ if (!hasPermission(roles || [], "organization:write")({
99
+ ...request.locals,
130
100
  targetOrganizations: [organization]
131
- })) {
132
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
133
- return;
134
- }
101
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
135
102
  try {
136
103
  const updatedOrganization = await updateOrganizationById(organization.id, organizationFields);
137
104
  const responseData = formatResponse({
@@ -147,47 +114,30 @@ const updateOrganization = async (req, res, _next) => {
147
114
  }),
148
115
  data: mapOrganizationToAPI(updatedOrganization)
149
116
  });
150
- res.json(responseData);
151
- return;
117
+ return reply.send(responseData);
152
118
  } catch (error) {
153
- ErrorHandler.handleAppErrorResponse(res, error);
154
- return;
119
+ return ErrorHandler.handleAppErrorResponse(reply, error);
155
120
  }
156
121
  };
157
122
  /**
158
123
  * Add member to the organization in the database.
159
124
  */
160
- const addOrganizationMember = async (req, res, _next) => {
161
- const { organization, user, roles } = res.locals;
162
- const { userEmail } = req.body;
163
- if (!organization) {
164
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_DEFINED");
165
- return;
166
- }
167
- if (!user) {
168
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
169
- return;
170
- }
171
- if (!hasPermission(roles, "organization:admin")({
172
- ...res.locals,
125
+ const addOrganizationMember = async (request, reply) => {
126
+ const { organization, user, roles } = request.locals || {};
127
+ const { userEmail } = request.body;
128
+ if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_DEFINED");
129
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
130
+ if (!hasPermission(roles || [], "organization:admin")({
131
+ ...request.locals,
173
132
  targetOrganizations: [organization]
174
- })) {
175
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
176
- return;
177
- }
133
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
178
134
  const planType = getPlanDetails(organization.plan);
179
- if (planType.numberOfOrganizationUsers && organization.membersIds.length >= planType.numberOfOrganizationUsers) {
180
- ErrorHandler.handleGenericErrorResponse(res, "PLAN_USER_LIMIT_REACHED", { organizationId: organization.id });
181
- return;
182
- }
135
+ if (planType.numberOfOrganizationUsers && organization.membersIds.length >= planType.numberOfOrganizationUsers) return ErrorHandler.handleGenericErrorResponse(reply, "PLAN_USER_LIMIT_REACHED", { organizationId: organization.id });
183
136
  try {
184
137
  let newMember = await getUserByEmail(userEmail);
185
138
  if (!newMember) {
186
139
  const newUser = await createUser({ email: userEmail });
187
- if (!newUser) {
188
- ErrorHandler.handleGenericErrorResponse(res, "USER_CREATION_FAILED", { email: userEmail });
189
- return;
190
- }
140
+ if (!newUser) return ErrorHandler.handleGenericErrorResponse(reply, "USER_CREATION_FAILED", { email: userEmail });
191
141
  newMember = newUser;
192
142
  }
193
143
  const updatedOrganization = await updateOrganizationById(organization.id, {
@@ -207,7 +157,6 @@ const addOrganizationMember = async (req, res, _next) => {
207
157
  }),
208
158
  data: mapOrganizationToAPI(updatedOrganization)
209
159
  });
210
- res.json(responseData);
211
160
  await sendEmail({
212
161
  type: "invite",
213
162
  to: userEmail,
@@ -215,56 +164,34 @@ const addOrganizationMember = async (req, res, _next) => {
215
164
  invitedByUsername: user.name,
216
165
  invitedByEmail: user.email,
217
166
  organizationName: organization.name,
218
- inviteLink: `${process.env.CLIENT_URL}/auth/login?email=${newMember.email}`,
219
- inviteFromIp: req.ip ?? "",
220
- inviteFromLocation: req.hostname
167
+ inviteLink: `${process.env.APP_URL}/auth/login?email=${newMember.email}`,
168
+ inviteFromIp: request.ip ?? "",
169
+ inviteFromLocation: request.hostname
221
170
  });
222
- return;
171
+ return reply.send(responseData);
223
172
  } catch (error) {
224
- ErrorHandler.handleAppErrorResponse(res, error);
225
- return;
173
+ return ErrorHandler.handleAppErrorResponse(reply, error);
226
174
  }
227
175
  };
228
176
  /**
229
177
  * Update members to the organization in the database.
230
178
  */
231
- const updateOrganizationMembers = async (req, res, _next) => {
232
- const { organization, roles } = res.locals;
233
- const { membersIds, adminsIds } = req.body;
234
- if (!organization) {
235
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_DEFINED");
236
- return;
237
- }
238
- if (!hasPermission(roles, "organization:admin")({
239
- ...res.locals,
179
+ const updateOrganizationMembers = async (request, reply) => {
180
+ const { organization, roles } = request.locals || {};
181
+ const { membersIds, adminsIds } = request.body;
182
+ if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_DEFINED");
183
+ if (!hasPermission(roles || [], "organization:admin")({
184
+ ...request.locals,
240
185
  targetOrganizations: [organization]
241
- })) {
242
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
243
- return;
244
- }
245
- if (!membersIds) {
246
- ErrorHandler.handleGenericErrorResponse(res, "INVALID_REQUEST_BODY");
247
- return;
248
- }
249
- if (membersIds?.length === 0) {
250
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_MUST_HAVE_MEMBER");
251
- return;
252
- }
253
- if (adminsIds?.filter((id) => membersIds?.includes(id)).length === 0) {
254
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_MUST_HAVE_ADMIN");
255
- return;
256
- }
186
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
187
+ if (!membersIds) return ErrorHandler.handleGenericErrorResponse(reply, "INVALID_REQUEST_BODY");
188
+ if (membersIds?.length === 0) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_MUST_HAVE_MEMBER");
189
+ if (adminsIds?.filter((id) => membersIds?.includes(id)).length === 0) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_MUST_HAVE_ADMIN");
257
190
  try {
258
191
  const existingUsers = await getUsersByIds(membersIds);
259
- if (!existingUsers) {
260
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
261
- return;
262
- }
192
+ if (!existingUsers) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_FOUND");
263
193
  const existingAdmins = await getUsersByIds(adminsIds);
264
- if (!existingAdmins) {
265
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
266
- return;
267
- }
194
+ if (!existingAdmins) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_FOUND");
268
195
  const updatedOrganization = await updateOrganizationById(organization.id, {
269
196
  membersIds: existingUsers.map((user) => user.id),
270
197
  adminsIds: existingAdmins.map((user) => user.id)
@@ -282,53 +209,30 @@ const updateOrganizationMembers = async (req, res, _next) => {
282
209
  }),
283
210
  data: mapOrganizationToAPI(updatedOrganization)
284
211
  });
285
- res.json(responseData);
286
- return;
212
+ return reply.send(responseData);
287
213
  } catch (error) {
288
- ErrorHandler.handleAppErrorResponse(res, error);
289
- return;
214
+ return ErrorHandler.handleAppErrorResponse(reply, error);
290
215
  }
291
216
  };
292
217
  /**
293
218
  * Admin-only: Update members of any organization by ID
294
219
  */
295
- const updateOrganizationMembersById = async (req, res, _next) => {
296
- const { user } = res.locals;
297
- const { organizationId } = req.params;
298
- const { membersIds, adminsIds } = req.body;
299
- if (!user) {
300
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
301
- return;
302
- }
303
- if (user.role !== "admin") {
304
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
305
- return;
306
- }
307
- if (!membersIds) {
308
- ErrorHandler.handleGenericErrorResponse(res, "INVALID_REQUEST_BODY");
309
- return;
310
- }
311
- if (membersIds?.length === 0) {
312
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_MUST_HAVE_MEMBER");
313
- return;
314
- }
220
+ const updateOrganizationMembersById = async (request, reply) => {
221
+ const { user } = request.locals || {};
222
+ const { organizationId } = request.params;
223
+ const { membersIds, adminsIds } = request.body;
224
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
225
+ if (user.role !== "admin") return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
226
+ if (!membersIds) return ErrorHandler.handleGenericErrorResponse(reply, "INVALID_REQUEST_BODY");
227
+ if (membersIds?.length === 0) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_MUST_HAVE_MEMBER");
315
228
  try {
316
229
  const targetOrganization = await getOrganizationById(organizationId);
317
- if (!targetOrganization) {
318
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_FOUND");
319
- return;
320
- }
230
+ if (!targetOrganization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_FOUND");
321
231
  const existingUsers = await getUsersByIds(membersIds);
322
- if (!existingUsers) {
323
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
324
- return;
325
- }
232
+ if (!existingUsers) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_FOUND");
326
233
  const finalAdminsIds = adminsIds && adminsIds.length > 0 ? adminsIds : targetOrganization.adminsIds;
327
234
  const existingAdmins = finalAdminsIds ? await getUsersByIds(finalAdminsIds) : [];
328
- if (!existingAdmins || existingAdmins.length === 0) {
329
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_MUST_HAVE_ADMIN");
330
- return;
331
- }
235
+ if (!existingAdmins || existingAdmins.length === 0) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_MUST_HAVE_ADMIN");
332
236
  const updatedOrganization = await updateOrganizationById(targetOrganization.id, {
333
237
  membersIds: existingUsers.map((user$1) => user$1.id),
334
238
  adminsIds: existingAdmins.map((user$1) => user$1.id)
@@ -346,41 +250,27 @@ const updateOrganizationMembersById = async (req, res, _next) => {
346
250
  }),
347
251
  data: mapOrganizationToAPI(updatedOrganization)
348
252
  });
349
- res.json(responseData);
350
- return;
253
+ return reply.send(responseData);
351
254
  } catch (error) {
352
- ErrorHandler.handleAppErrorResponse(res, error);
353
- return;
255
+ return ErrorHandler.handleAppErrorResponse(reply, error);
354
256
  }
355
257
  };
356
258
  /**
357
259
  * Deletes an organization from the database by its ID.
358
260
  */
359
- const deleteOrganization = async (_req, res, _next) => {
261
+ const deleteOrganization = async (_request, reply) => {
360
262
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
361
- const { organization, roles } = res.locals;
362
- if (!organization) {
363
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_DEFINED");
364
- return;
365
- }
366
- if ((await findProjects({ organizationId: organization.id })).length > 0) {
367
- ErrorHandler.handleGenericErrorResponse(res, "PROJECTS_EXIST", { organizationId: organization.id });
368
- return;
369
- }
370
- if (!hasPermission(roles, "organization:admin")({
371
- ...res.locals,
263
+ const { organization, roles } = _request.locals || {};
264
+ if (!organization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_DEFINED");
265
+ if ((await findProjects({ organizationId: organization.id })).length > 0) return ErrorHandler.handleGenericErrorResponse(reply, "PROJECTS_EXIST", { organizationId: organization.id });
266
+ if (!hasPermission(roles || [], "organization:admin")({
267
+ ..._request.locals,
372
268
  targetOrganizations: [organization]
373
- })) {
374
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
375
- return;
376
- }
269
+ })) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
377
270
  try {
378
271
  if (organization.plan?.subscriptionId) await stripe.subscriptions.cancel(organization.plan.subscriptionId);
379
272
  const deletedOrganization = await deleteOrganizationById(organization.id);
380
- if (!deletedOrganization) {
381
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_FOUND", { organizationId: organization.id });
382
- return;
383
- }
273
+ if (!deletedOrganization) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_NOT_FOUND", { organizationId: organization.id });
384
274
  logger.info(`Organization deleted: ${String(deletedOrganization.id)}`);
385
275
  const responseData = formatResponse({
386
276
  message: t({
@@ -395,27 +285,19 @@ const deleteOrganization = async (_req, res, _next) => {
395
285
  }),
396
286
  data: mapOrganizationToAPI(deletedOrganization)
397
287
  });
398
- res.json(responseData);
399
- return;
288
+ return reply.send(responseData);
400
289
  } catch (error) {
401
- ErrorHandler.handleAppErrorResponse(res, error);
402
- return;
290
+ return ErrorHandler.handleAppErrorResponse(reply, error);
403
291
  }
404
292
  };
405
293
  /**
406
294
  * Select an organization.
407
295
  */
408
- const selectOrganization = async (req, res, _next) => {
409
- const { organizationId } = req.params;
410
- const { session } = res.locals;
411
- if (!organizationId) {
412
- ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_ID_NOT_FOUND");
413
- return;
414
- }
415
- if (typeof session === "undefined") {
416
- ErrorHandler.handleGenericErrorResponse(res, "SESSION_NOT_DEFINED");
417
- return;
418
- }
296
+ const selectOrganization = async (request, reply) => {
297
+ const { organizationId } = request.params;
298
+ const { session } = request.locals || {};
299
+ if (!organizationId) return ErrorHandler.handleGenericErrorResponse(reply, "ORGANIZATION_ID_NOT_FOUND");
300
+ if (typeof session === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "SESSION_NOT_DEFINED");
419
301
  try {
420
302
  const organization = await getOrganizationById(organizationId);
421
303
  await SessionModel.updateOne({ _id: session.id }, { $set: {
@@ -435,23 +317,18 @@ const selectOrganization = async (req, res, _next) => {
435
317
  }),
436
318
  data: mapOrganizationToAPI(organization)
437
319
  });
438
- res.json(responseData);
439
- return;
320
+ return reply.send(responseData);
440
321
  } catch (error) {
441
- ErrorHandler.handleAppErrorResponse(res, error);
442
- return;
322
+ return ErrorHandler.handleAppErrorResponse(reply, error);
443
323
  }
444
324
  };
445
325
  /**
446
326
  * Unselect an organization.
447
327
  */
448
- const unselectOrganization = async (_req, res, _next) => {
449
- const { session } = res.locals;
328
+ const unselectOrganization = async (_request, reply) => {
329
+ const { session } = _request.locals || {};
450
330
  try {
451
- if (typeof session === "undefined") {
452
- ErrorHandler.handleGenericErrorResponse(res, "SESSION_NOT_DEFINED");
453
- return;
454
- }
331
+ if (typeof session === "undefined") return ErrorHandler.handleGenericErrorResponse(reply, "SESSION_NOT_DEFINED");
455
332
  await SessionModel.updateOne({ _id: session.id }, { $set: {
456
333
  activeOrganizationId: null,
457
334
  activeProjectId: null
@@ -469,11 +346,9 @@ const unselectOrganization = async (_req, res, _next) => {
469
346
  }),
470
347
  data: null
471
348
  });
472
- res.json(responseData);
473
- return;
349
+ return reply.send(responseData);
474
350
  } catch (error) {
475
- ErrorHandler.handleAppErrorResponse(res, error);
476
- return;
351
+ return ErrorHandler.handleAppErrorResponse(reply, error);
477
352
  }
478
353
  };
479
354
 
@@ -1 +1 @@
1
- {"version":3,"file":"organization.controller.mjs","names":["organizationService.findOrganizations","organizationService.countOrganizations","organizationService.getOrganizationById","organizationService.createOrganization","organizationService.updateOrganizationById","userService.getUserByEmail","userService.createUser","userService.getUsersByIds","user","projectService.findProjects","organizationService.deleteOrganizationById"],"sources":["../../../src/controllers/organization.controller.ts"],"sourcesContent":["import { 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 { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getOrganizationFiltersAndPagination,\n type OrganizationFiltersParams,\n} from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n mapOrganizationsToAPI,\n mapOrganizationToAPI,\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 type { Types } from 'mongoose';\nimport { Stripe } from 'stripe';\nimport type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n} from '@/types/organization.types';\nimport type { User, UserAPI } from '@/types/user.types';\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, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getOrganizationFiltersAndPagination(req, res);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const organizations = await organizationService.findOrganizations(\n filters,\n skip,\n pageSize,\n sortOptions\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 } = 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 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\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 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 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 UpdateOrganizationMembersByIdParams = { organizationId: string };\nexport type UpdateOrganizationMembersByIdBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersByIdResult = ResponseData<OrganizationAPI>;\n\n/**\n * Admin-only: Update members of any organization by ID\n */\nexport const updateOrganizationMembersById = async (\n req: Request<\n UpdateOrganizationMembersByIdParams,\n any,\n UpdateOrganizationMembersByIdBody\n >,\n res: ResponseWithSession<UpdateOrganizationMembersByIdResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user } = res.locals;\n const { organizationId } = req.params;\n const { membersIds, adminsIds } = req.body;\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (user.role !== 'admin') {\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 try {\n const targetOrganization =\n await organizationService.getOrganizationById(organizationId);\n\n if (!targetOrganization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND');\n return;\n }\n\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n const finalAdminsIds =\n adminsIds && adminsIds.length > 0\n ? adminsIds\n : targetOrganization.adminsIds;\n const existingAdmins = finalAdminsIds\n ? await userService.getUsersByIds(finalAdminsIds)\n : [];\n\n if (!existingAdmins || existingAdmins.length === 0) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n return;\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(targetOrganization.id, {\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 members updated successfully',\n fr: \"Membres de l'organisation mis à jour avec succès\",\n es: 'Miembros de la organización actualizados con éxito',\n }),\n description: t({\n en: 'Organization members have been updated successfully',\n fr: \"Les membres de l'organisation ont été mis à jour avec succès\",\n es: 'Los miembros de la organización han sido actualizados 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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"}
1
+ {"version":3,"file":"organization.controller.mjs","names":["organizationService.findOrganizations","organizationService.countOrganizations","organizationService.getOrganizationById","organizationService.createOrganization","organizationService.updateOrganizationById","userService.getUserByEmail","userService.createUser","userService.getUsersByIds","user","projectService.findProjects","organizationService.deleteOrganizationById"],"sources":["../../../src/controllers/organization.controller.ts"],"sourcesContent":["import { logger } from '@logger';\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 { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getOrganizationFiltersAndPagination,\n type OrganizationFiltersParams,\n} from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n mapOrganizationsToAPI,\n mapOrganizationToAPI,\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 { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { Types } from 'mongoose';\nimport { Stripe } from 'stripe';\nimport type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n} from '@/types/organization.types';\nimport type { User, UserAPI } from '@/types/user.types';\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 request: FastifyRequest<{ Querystring: GetOrganizationsParams }>,\n reply: FastifyReply\n) => {\n const { user, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getOrganizationFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const organizations = await organizationService.findOrganizations(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.locals,\n targetOrganizations: organizations,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\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 return reply.code(200).send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 request: FastifyRequest<{ Params: GetOrganizationParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { roles } = request.locals || {};\n const { organizationId } = request.params;\n\n if (!organizationId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_ID_NOT_FOUND'\n );\n }\n\n try {\n const organization =\n await organizationService.getOrganizationById(organizationId);\n\n if (\n !hasPermission(\n roles || [],\n 'organization:read'\n )({\n ...request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const responseData = formatResponse<OrganizationAPI>({\n data: mapOrganizationToAPI(organization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 request: FastifyRequest<{ Body: AddOrganizationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user } = request.locals || {};\n const organization = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_DATA_NOT_FOUND'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\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 return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 request: FastifyRequest<{ Body: UpdateOrganizationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, roles } = request.locals || {};\n const organizationFields = request.body;\n\n if (!organizationFields) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:write'\n )({\n ...request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\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 return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 request: FastifyRequest<{ Body: AddOrganizationMemberBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.locals || {};\n const { userEmail } = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n const planType = getPlanDetails(organization.plan);\n\n if (\n planType.numberOfOrganizationUsers &&\n organization.membersIds.length >= planType.numberOfOrganizationUsers\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_USER_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\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 return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_CREATION_FAILED',\n {\n email: userEmail,\n }\n );\n }\n\n newMember = newUser;\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 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.APP_URL}/auth/login?email=${newMember.email}`,\n inviteFromIp: request.ip ?? '',\n inviteFromLocation: request.hostname,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 request: FastifyRequest<{ Body: UpdateOrganizationMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, roles } = request.locals || {};\n const { membersIds, adminsIds } = request.body;\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (!membersIds) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n }\n\n if (adminsIds?.filter((id) => membersIds?.includes(id)).length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n }\n\n try {\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const existingAdmins = await userService.getUsersByIds(adminsIds!);\n\n if (!existingAdmins) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(organization.id, {\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 return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateOrganizationMembersByIdParams = { organizationId: string };\nexport type UpdateOrganizationMembersByIdBody = Partial<{\n membersIds: (User | UserAPI)['id'][];\n adminsIds: (User | UserAPI)['id'][];\n}>;\nexport type UpdateOrganizationMembersByIdResult = ResponseData<OrganizationAPI>;\n\n/**\n * Admin-only: Update members of any organization by ID\n */\nexport const updateOrganizationMembersById = async (\n request: FastifyRequest<{\n Params: UpdateOrganizationMembersByIdParams;\n Body: UpdateOrganizationMembersByIdBody;\n }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user } = request.locals || {};\n const { organizationId } = request.params;\n const { membersIds, adminsIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (user.role !== 'admin') {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n if (!membersIds) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_MEMBER'\n );\n }\n\n try {\n const targetOrganization =\n await organizationService.getOrganizationById(organizationId);\n\n if (!targetOrganization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n const existingUsers = await userService.getUsersByIds(membersIds);\n\n if (!existingUsers) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const finalAdminsIds =\n adminsIds && adminsIds.length > 0\n ? adminsIds\n : targetOrganization.adminsIds;\n const existingAdmins = finalAdminsIds\n ? await userService.getUsersByIds(finalAdminsIds)\n : [];\n\n if (!existingAdmins || existingAdmins.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_MUST_HAVE_ADMIN'\n );\n }\n\n const updatedOrganization =\n await organizationService.updateOrganizationById(targetOrganization.id, {\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 members updated successfully',\n fr: \"Membres de l'organisation mis à jour avec succès\",\n es: 'Miembros de la organización actualizados con éxito',\n }),\n description: t({\n en: 'Organization members have been updated successfully',\n fr: \"Les membres de l'organisation ont été mis à jour avec succès\",\n es: 'Los miembros de la organización han sido actualizados con éxito',\n }),\n data: mapOrganizationToAPI(updatedOrganization),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const { organization, roles } = _request.locals || {};\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n const projects = await projectService.findProjects({\n organizationId: organization.id,\n });\n\n if (projects.length > 0) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PROJECTS_EXIST', {\n organizationId: organization.id,\n });\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ..._request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n 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 return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND',\n {\n organizationId: organization.id,\n }\n );\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 return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\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 request: FastifyRequest<{ Params: SelectOrganizationParam }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organizationId } = request.params;\n const { session } = request.locals || {};\n\n if (!organizationId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_ID_NOT_FOUND'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n const 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 return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectOrganizationResult = ResponseData<null>;\n\n/**\n * Unselect an organization.\n */\nexport const unselectOrganization = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { session } = _request.locals || {};\n try {\n // Update session to clear activeOrganizationId and activeProjectId\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\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 return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}