@intlayer/backend 7.5.8 → 7.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/assets/utils/AI/askDocQuestion/PROMPT.md +1 -1
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/init.json +2054 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_fastify.json +9 -0
- package/dist/esm/controllers/ai.controller.mjs +95 -128
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/dictionary.controller.mjs +86 -198
- package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
- package/dist/esm/controllers/eventListener.controller.mjs +13 -19
- package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
- package/dist/esm/controllers/github.controller.mjs +77 -0
- package/dist/esm/controllers/github.controller.mjs.map +1 -0
- package/dist/esm/controllers/newsletter.controller.mjs +30 -60
- package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
- package/dist/esm/controllers/oAuth2.controller.mjs +11 -8
- package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
- package/dist/esm/controllers/organization.controller.mjs +100 -225
- package/dist/esm/controllers/organization.controller.mjs.map +1 -1
- package/dist/esm/controllers/project.controller.mjs +87 -204
- package/dist/esm/controllers/project.controller.mjs.map +1 -1
- package/dist/esm/controllers/projectAccessKey.controller.mjs +38 -71
- package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
- package/dist/esm/controllers/search.controller.mjs +3 -3
- package/dist/esm/controllers/search.controller.mjs.map +1 -1
- package/dist/esm/controllers/stripe.controller.mjs +34 -67
- package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
- package/dist/esm/controllers/tag.controller.mjs +51 -113
- package/dist/esm/controllers/tag.controller.mjs.map +1 -1
- package/dist/esm/controllers/user.controller.mjs +64 -113
- package/dist/esm/controllers/user.controller.mjs.map +1 -1
- package/dist/esm/export.mjs +2 -1
- package/dist/esm/index.mjs +101 -41
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/middlewares/oAuth2.middleware.mjs +19 -14
- package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
- package/dist/esm/middlewares/sessionAuth.middleware.mjs +6 -7
- package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
- package/dist/esm/routes/ai.routes.mjs +19 -15
- package/dist/esm/routes/ai.routes.mjs.map +1 -1
- package/dist/esm/routes/dictionary.routes.mjs +10 -10
- package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
- package/dist/esm/routes/eventListener.routes.mjs +3 -3
- package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
- package/dist/esm/routes/github.routes.mjs +43 -0
- package/dist/esm/routes/github.routes.mjs.map +1 -0
- package/dist/esm/routes/newsletter.routes.mjs +5 -5
- package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
- package/dist/esm/routes/organization.routes.mjs +11 -11
- package/dist/esm/routes/organization.routes.mjs.map +1 -1
- package/dist/esm/routes/project.routes.mjs +13 -13
- package/dist/esm/routes/project.routes.mjs.map +1 -1
- package/dist/esm/routes/search.routes.mjs +3 -3
- package/dist/esm/routes/search.routes.mjs.map +1 -1
- package/dist/esm/routes/stripe.routes.mjs +5 -5
- package/dist/esm/routes/stripe.routes.mjs.map +1 -1
- package/dist/esm/routes/tags.routes.mjs +6 -6
- package/dist/esm/routes/tags.routes.mjs.map +1 -1
- package/dist/esm/routes/user.routes.mjs +9 -9
- package/dist/esm/routes/user.routes.mjs.map +1 -1
- package/dist/esm/schemas/project.schema.mjs +35 -1
- package/dist/esm/schemas/project.schema.mjs.map +1 -1
- package/dist/esm/services/email.service.mjs +1 -1
- package/dist/esm/services/email.service.mjs.map +1 -1
- package/dist/esm/services/github.service.mjs +130 -0
- package/dist/esm/services/github.service.mjs.map +1 -0
- package/dist/esm/services/oAuth2.service.mjs +1 -1
- package/dist/esm/services/subscription.service.mjs +1 -1
- package/dist/esm/services/subscription.service.mjs.map +1 -1
- package/dist/esm/utils/auth/getAuth.mjs +14 -8
- package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
- package/dist/esm/utils/cors.mjs +15 -5
- package/dist/esm/utils/cors.mjs.map +1 -1
- package/dist/esm/utils/errors/ErrorHandler.mjs +32 -4
- package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
- package/dist/esm/utils/errors/ErrorsClass.mjs +1 -1
- package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
- package/dist/esm/utils/errors/errorCodes.mjs +78 -0
- package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs +1 -1
- package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs +1 -1
- package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs +3 -2
- package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/mapper/project.mjs +28 -1
- package/dist/esm/utils/mapper/project.mjs.map +1 -1
- package/dist/esm/utils/mongoDB/connectDB.mjs +1 -1
- package/dist/esm/utils/rateLimiter.mjs +40 -30
- package/dist/esm/utils/rateLimiter.mjs.map +1 -1
- package/dist/esm/webhooks/stripe.webhook.mjs +2 -2
- package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
- package/dist/types/controllers/ai.controller.d.ts +29 -12
- package/dist/types/controllers/ai.controller.d.ts.map +1 -1
- package/dist/types/controllers/dictionary.controller.d.ts +23 -13
- package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
- package/dist/types/controllers/eventListener.controller.d.ts +4 -2
- package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
- package/dist/types/controllers/github.controller.d.ts +63 -0
- package/dist/types/controllers/github.controller.d.ts.map +1 -0
- package/dist/types/controllers/newsletter.controller.d.ts +8 -7
- package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
- package/dist/types/controllers/oAuth2.controller.d.ts +4 -2
- package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
- package/dist/types/controllers/organization.controller.d.ts +28 -12
- package/dist/types/controllers/organization.controller.d.ts.map +1 -1
- package/dist/types/controllers/project.controller.d.ts +21 -16
- package/dist/types/controllers/project.controller.d.ts.map +1 -1
- package/dist/types/controllers/projectAccessKey.controller.d.ts +10 -5
- package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
- package/dist/types/controllers/search.controller.d.ts +4 -2
- package/dist/types/controllers/search.controller.d.ts.map +1 -1
- package/dist/types/controllers/stripe.controller.d.ts +11 -12
- package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
- package/dist/types/controllers/tag.controller.d.ts +14 -9
- package/dist/types/controllers/tag.controller.d.ts.map +1 -1
- package/dist/types/controllers/user.controller.d.ts +22 -9
- package/dist/types/controllers/user.controller.d.ts.map +1 -1
- package/dist/types/emails/InviteUserEmail.d.ts +4 -4
- package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
- package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
- package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
- package/dist/types/emails/ResetUserPassword.d.ts +4 -4
- package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
- package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
- package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
- package/dist/types/emails/Welcome.d.ts +4 -4
- package/dist/types/emails/Welcome.d.ts.map +1 -1
- package/dist/types/export.d.ts +6 -4
- package/dist/types/middlewares/oAuth2.middleware.d.ts +9 -4
- package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
- package/dist/types/middlewares/sessionAuth.middleware.d.ts +13 -3
- package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
- package/dist/types/models/dictionary.model.d.ts +4 -4
- package/dist/types/models/discussion.model.d.ts +2 -2
- package/dist/types/models/oAuth2.model.d.ts +3 -3
- package/dist/types/routes/ai.routes.d.ts +2 -2
- package/dist/types/routes/ai.routes.d.ts.map +1 -1
- package/dist/types/routes/dictionary.routes.d.ts +2 -2
- package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
- package/dist/types/routes/eventListener.routes.d.ts +2 -2
- package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
- package/dist/types/routes/github.routes.d.ts +35 -0
- package/dist/types/routes/github.routes.d.ts.map +1 -0
- package/dist/types/routes/newsletter.routes.d.ts +2 -2
- package/dist/types/routes/newsletter.routes.d.ts.map +1 -1
- package/dist/types/routes/organization.routes.d.ts +2 -2
- package/dist/types/routes/organization.routes.d.ts.map +1 -1
- package/dist/types/routes/project.routes.d.ts +2 -2
- package/dist/types/routes/project.routes.d.ts.map +1 -1
- package/dist/types/routes/search.routes.d.ts +2 -2
- package/dist/types/routes/search.routes.d.ts.map +1 -1
- package/dist/types/routes/stripe.routes.d.ts +2 -2
- package/dist/types/routes/stripe.routes.d.ts.map +1 -1
- package/dist/types/routes/tags.routes.d.ts +2 -2
- package/dist/types/routes/tags.routes.d.ts.map +1 -1
- package/dist/types/routes/user.routes.d.ts +2 -2
- package/dist/types/routes/user.routes.d.ts.map +1 -1
- package/dist/types/schemas/dictionary.schema.d.ts +6 -6
- package/dist/types/schemas/discussion.schema.d.ts +6 -6
- package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
- package/dist/types/schemas/project.schema.d.ts +6 -6
- package/dist/types/schemas/project.schema.d.ts.map +1 -1
- package/dist/types/schemas/session.schema.d.ts +6 -6
- package/dist/types/schemas/tag.schema.d.ts +6 -6
- package/dist/types/schemas/user.schema.d.ts +6 -6
- package/dist/types/services/email.service.d.ts +11 -11
- package/dist/types/services/github.service.d.ts +21 -0
- package/dist/types/services/github.service.d.ts.map +1 -0
- package/dist/types/types/project.types.d.ts +18 -5
- package/dist/types/types/project.types.d.ts.map +1 -1
- package/dist/types/types/session.types.d.ts +1 -1
- package/dist/types/types/user.types.d.ts +1 -1
- package/dist/types/utils/AI/auditTag/index.d.ts +1 -1
- package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
- package/dist/types/utils/cors.d.ts +2 -2
- package/dist/types/utils/errors/ErrorHandler.d.ts +31 -3
- package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
- package/dist/types/utils/errors/ErrorsClass.d.ts +1 -1
- package/dist/types/utils/errors/errorCodes.d.ts +78 -0
- package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +6 -3
- package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts +6 -2
- package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +8 -4
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts +6 -2
- package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
- package/dist/types/utils/mapper/project.d.ts.map +1 -1
- package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
- package/dist/types/utils/permissions.d.ts +1 -1
- package/dist/types/utils/rateLimiter.d.ts +4 -2
- package/dist/types/utils/rateLimiter.d.ts.map +1 -1
- package/package.json +23 -27
- package/dist/esm/middlewares/request.middleware.mjs +0 -17
- package/dist/esm/middlewares/request.middleware.mjs.map +0 -1
- package/dist/types/middlewares/request.middleware.d.ts +0 -7
- package/dist/types/middlewares/request.middleware.d.ts.map +0 -1
|
@@ -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 "
|
|
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 (
|
|
21
|
-
const { user, roles } =
|
|
22
|
-
const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getOrganizationFiltersAndPagination(
|
|
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
|
-
...
|
|
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
|
-
|
|
45
|
-
return;
|
|
38
|
+
return reply.code(200).send(responseData);
|
|
46
39
|
} catch (error) {
|
|
47
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
55
|
-
const { roles } =
|
|
56
|
-
const { organizationId } =
|
|
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
|
-
...
|
|
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
|
-
|
|
72
|
-
return;
|
|
57
|
+
return reply.send(responseData);
|
|
73
58
|
} catch (error) {
|
|
74
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
82
|
-
const { user } =
|
|
83
|
-
const organization =
|
|
84
|
-
if (!organization)
|
|
85
|
-
|
|
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
|
-
|
|
108
|
-
return;
|
|
85
|
+
return reply.send(responseData);
|
|
109
86
|
} catch (error) {
|
|
110
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
118
|
-
const { organization, roles } =
|
|
119
|
-
const organizationFields =
|
|
120
|
-
if (!organizationFields)
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
151
|
-
return;
|
|
117
|
+
return reply.send(responseData);
|
|
152
118
|
} catch (error) {
|
|
153
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
161
|
-
const { organization, user, roles } =
|
|
162
|
-
const { userEmail } =
|
|
163
|
-
if (!organization)
|
|
164
|
-
|
|
165
|
-
|
|
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.
|
|
219
|
-
inviteFromIp:
|
|
220
|
-
inviteFromLocation:
|
|
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(
|
|
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 (
|
|
232
|
-
const { organization, roles } =
|
|
233
|
-
const { membersIds, adminsIds } =
|
|
234
|
-
if (!organization)
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
286
|
-
return;
|
|
212
|
+
return reply.send(responseData);
|
|
287
213
|
} catch (error) {
|
|
288
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
296
|
-
const { user } =
|
|
297
|
-
const { organizationId } =
|
|
298
|
-
const { membersIds, adminsIds } =
|
|
299
|
-
if (!user)
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
350
|
-
return;
|
|
253
|
+
return reply.send(responseData);
|
|
351
254
|
} catch (error) {
|
|
352
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
261
|
+
const deleteOrganization = async (_request, reply) => {
|
|
360
262
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
361
|
-
const { organization, roles } =
|
|
362
|
-
if (!organization)
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
399
|
-
return;
|
|
288
|
+
return reply.send(responseData);
|
|
400
289
|
} catch (error) {
|
|
401
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
409
|
-
const { organizationId } =
|
|
410
|
-
const { session } =
|
|
411
|
-
if (!organizationId)
|
|
412
|
-
|
|
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
|
-
|
|
439
|
-
return;
|
|
320
|
+
return reply.send(responseData);
|
|
440
321
|
} catch (error) {
|
|
441
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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 (
|
|
449
|
-
const { session } =
|
|
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
|
-
|
|
473
|
-
return;
|
|
349
|
+
return reply.send(responseData);
|
|
474
350
|
} catch (error) {
|
|
475
|
-
ErrorHandler.handleAppErrorResponse(
|
|
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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"}
|