@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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { logger } from "../logger/index.mjs";
|
|
2
|
+
import { getDBClient } from "../utils/mongoDB/connectDB.mjs";
|
|
3
|
+
import { configurationFilesCandidates } from "@intlayer/config";
|
|
4
|
+
import { Octokit } from "@octokit/rest";
|
|
5
|
+
import { ObjectId } from "mongodb";
|
|
6
|
+
|
|
7
|
+
//#region src/services/github.service.ts
|
|
8
|
+
const getAuthorizationUrl = (redirectUri, login) => {
|
|
9
|
+
const clientId = process.env.GITHUB_CLIENT_ID;
|
|
10
|
+
if (!clientId) throw new Error("GitHub Client ID is not configured");
|
|
11
|
+
const params = new URLSearchParams({
|
|
12
|
+
client_id: clientId,
|
|
13
|
+
scope: "repo",
|
|
14
|
+
state: "github_oauth",
|
|
15
|
+
redirect_uri: redirectUri
|
|
16
|
+
});
|
|
17
|
+
if (login) params.append("login", login);
|
|
18
|
+
return `https://github.com/login/oauth/authorize?${params.toString()}`;
|
|
19
|
+
};
|
|
20
|
+
const exchangeCodeForToken = async (code) => {
|
|
21
|
+
const clientId = process.env.GITHUB_CLIENT_ID;
|
|
22
|
+
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
|
|
23
|
+
if (!clientId || !clientSecret) throw new Error("GitHub OAuth credentials are not configured");
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch("https://github.com/login/oauth/access_token", {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
Accept: "application/json"
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
client_id: clientId,
|
|
33
|
+
client_secret: clientSecret,
|
|
34
|
+
code
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) throw new Error(`GitHub token exchange failed: ${response.statusText}`);
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
if (data.error) throw new Error(`GitHub token error: ${data.error_description}`);
|
|
40
|
+
return data.access_token;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error("Error exchanging GitHub code for token:", error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const getUserRepos = async (accessToken) => {
|
|
47
|
+
try {
|
|
48
|
+
const { data } = await new Octokit({ auth: accessToken }).rest.repos.listForAuthenticatedUser({
|
|
49
|
+
sort: "updated",
|
|
50
|
+
per_page: 100,
|
|
51
|
+
visibility: "all"
|
|
52
|
+
});
|
|
53
|
+
return data;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.error("Error fetching GitHub repositories:", error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Check if valid intlayer configuration files exist in a repository (Recursively).
|
|
61
|
+
* Returns an array of file paths found (e.g. ['intlayer.config.ts', 'apps/web/intlayer.config.js']).
|
|
62
|
+
*/
|
|
63
|
+
const checkIntlayerConfig = async (accessToken, owner, repo, branch = "main") => {
|
|
64
|
+
try {
|
|
65
|
+
const { data } = await new Octokit({ auth: accessToken }).rest.git.getTree({
|
|
66
|
+
owner,
|
|
67
|
+
repo,
|
|
68
|
+
tree_sha: branch,
|
|
69
|
+
recursive: "true"
|
|
70
|
+
});
|
|
71
|
+
if (!data.tree || !Array.isArray(data.tree)) return [];
|
|
72
|
+
return data.tree.filter((item) => {
|
|
73
|
+
if (item.type !== "blob" || !item.path) return false;
|
|
74
|
+
return configurationFilesCandidates.some((candidate) => item.path?.endsWith(candidate));
|
|
75
|
+
}).map((item) => item.path);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error.status === 404 || error.status === 409) return [];
|
|
78
|
+
logger.error("Error checking intlayer configuration:", error);
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Get repository file contents and decode it
|
|
84
|
+
*/
|
|
85
|
+
const getRepositoryFileContents = async (accessToken, owner, repo, path, branch = "main") => {
|
|
86
|
+
try {
|
|
87
|
+
const { data } = await new Octokit({ auth: accessToken }).rest.repos.getContent({
|
|
88
|
+
owner,
|
|
89
|
+
repo,
|
|
90
|
+
path,
|
|
91
|
+
ref: branch
|
|
92
|
+
});
|
|
93
|
+
if (Array.isArray(data) || !("content" in data)) throw new Error("Path points to a directory, not a file");
|
|
94
|
+
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.status === 404) return null;
|
|
97
|
+
logger.error("Error fetching repository file contents:", error);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const getGitHubTokenFromUser = async (userId) => {
|
|
102
|
+
try {
|
|
103
|
+
const db = getDBClient().db();
|
|
104
|
+
let account = await db.collection("account").findOne({
|
|
105
|
+
userId,
|
|
106
|
+
providerId: "github"
|
|
107
|
+
});
|
|
108
|
+
if (!account && ObjectId.isValid(userId)) account = await db.collection("account").findOne({
|
|
109
|
+
userId: new ObjectId(userId),
|
|
110
|
+
providerId: "github"
|
|
111
|
+
});
|
|
112
|
+
if (!account) account = await db.collection("accounts").findOne({
|
|
113
|
+
userId,
|
|
114
|
+
providerId: "github"
|
|
115
|
+
});
|
|
116
|
+
if (!account && ObjectId.isValid(userId)) account = await db.collection("accounts").findOne({
|
|
117
|
+
userId: new ObjectId(userId),
|
|
118
|
+
providerId: "github"
|
|
119
|
+
});
|
|
120
|
+
if (!account) return null;
|
|
121
|
+
return account.accessToken || account.access_token || null;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.error("Error retrieving GitHub token from DB:", error);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
export { checkIntlayerConfig, exchangeCodeForToken, getAuthorizationUrl, getGitHubTokenFromUser, getRepositoryFileContents, getUserRepos };
|
|
130
|
+
//# sourceMappingURL=github.service.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.service.mjs","names":["error: any"],"sources":["../../../src/services/github.service.ts"],"sourcesContent":["import { configurationFilesCandidates } from '@intlayer/config';\nimport { logger } from '@logger';\nimport type { RestEndpointMethodTypes } from '@octokit/rest';\nimport { Octokit } from '@octokit/rest';\nimport { getDBClient } from '@utils/mongoDB/connectDB';\nimport { ObjectId } from 'mongodb';\n\nexport type GitHubRepository =\n RestEndpointMethodTypes['repos']['listForAuthenticatedUser']['response']['data'][0];\nexport type GitHubFileContent =\n RestEndpointMethodTypes['repos']['getContent']['response']['data'];\n\nexport const getAuthorizationUrl = (\n redirectUri: string,\n login?: string\n): string => {\n const clientId = process.env.GITHUB_CLIENT_ID;\n\n if (!clientId) {\n throw new Error('GitHub Client ID is not configured');\n }\n\n const params = new URLSearchParams({\n client_id: clientId,\n scope: 'repo',\n state: 'github_oauth',\n redirect_uri: redirectUri,\n });\n\n if (login) {\n params.append('login', login);\n }\n\n return `https://github.com/login/oauth/authorize?${params.toString()}`;\n};\n\nexport const exchangeCodeForToken = async (code: string): Promise<string> => {\n const clientId = process.env.GITHUB_CLIENT_ID;\n const clientSecret = process.env.GITHUB_CLIENT_SECRET;\n\n if (!clientId || !clientSecret) {\n throw new Error('GitHub OAuth credentials are not configured');\n }\n\n try {\n const response = await fetch(\n 'https://github.com/login/oauth/access_token',\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n }),\n }\n );\n\n if (!response.ok) {\n throw new Error(`GitHub token exchange failed: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (data.error) {\n throw new Error(`GitHub token error: ${data.error_description}`);\n }\n\n return data.access_token;\n } catch (error) {\n logger.error('Error exchanging GitHub code for token:', error);\n throw error;\n }\n};\n\nexport const getUserRepos = async (\n accessToken: string\n): Promise<GitHubRepository[]> => {\n try {\n const octokit = new Octokit({ auth: accessToken });\n\n const { data } = await octokit.rest.repos.listForAuthenticatedUser({\n sort: 'updated',\n per_page: 100,\n visibility: 'all',\n });\n\n return data;\n } catch (error) {\n logger.error('Error fetching GitHub repositories:', error);\n throw error;\n }\n};\n\n/**\n * Check if valid intlayer configuration files exist in a repository (Recursively).\n * Returns an array of file paths found (e.g. ['intlayer.config.ts', 'apps/web/intlayer.config.js']).\n */\nexport const checkIntlayerConfig = async (\n accessToken: string,\n owner: string,\n repo: string,\n branch: string = 'main'\n): Promise<string[]> => {\n try {\n const octokit = new Octokit({ auth: accessToken });\n\n // Use Git Tree API to get all files recursively\n // This allows finding configs in monorepos/subfolders\n const { data } = await octokit.rest.git.getTree({\n owner,\n repo,\n tree_sha: branch,\n recursive: 'true',\n });\n\n if (!data.tree || !Array.isArray(data.tree)) {\n return [];\n }\n\n // Filter files that match the configuration candidates\n // We check if the path ends with one of the candidate filenames\n const foundFiles = data.tree\n .filter((item) => {\n if (item.type !== 'blob' || !item.path) return false;\n return (configurationFilesCandidates as readonly string[]).some(\n (candidate) => item.path?.endsWith(candidate)\n );\n })\n .map((item) => item.path as string); // Return the full path (e.g., 'packages/app/intlayer.config.ts')\n\n return foundFiles;\n } catch (error: any) {\n // If branch doesn't exist or repo is empty\n if (error.status === 404 || error.status === 409) return [];\n\n logger.error('Error checking intlayer configuration:', error);\n return [];\n }\n};\n\n/**\n * Get repository file contents and decode it\n */\nexport const getRepositoryFileContents = async (\n accessToken: string,\n owner: string,\n repo: string,\n path: string,\n branch: string = 'main'\n): Promise<string | null> => {\n try {\n const octokit = new Octokit({ auth: accessToken });\n\n const { data } = await octokit.rest.repos.getContent({\n owner,\n repo,\n path,\n ref: branch,\n });\n\n // Octokit types are union types (file | dir | submodule), we need to check if it's a file\n if (Array.isArray(data) || !('content' in data)) {\n throw new Error('Path points to a directory, not a file');\n }\n\n // GitHub returns content in base64, we must decode it to read the actual code\n const decodedContent = Buffer.from(data.content, 'base64').toString(\n 'utf-8'\n );\n\n return decodedContent;\n } catch (error: any) {\n if (error.status === 404) return null;\n\n logger.error('Error fetching repository file contents:', error);\n throw error;\n }\n};\n\n// ... [Keep getGitHubTokenFromUser unchanged] ...\nexport const getGitHubTokenFromUser = async (\n userId: string\n): Promise<string | null> => {\n try {\n const client = getDBClient();\n const db = client.db();\n\n let account = await db.collection('account').findOne({\n userId: userId,\n providerId: 'github',\n });\n\n if (!account && ObjectId.isValid(userId)) {\n account = await db.collection('account').findOne({\n userId: new ObjectId(userId),\n providerId: 'github',\n });\n }\n\n if (!account) {\n account = await db.collection('accounts').findOne({\n userId: userId,\n providerId: 'github',\n });\n }\n\n if (!account && ObjectId.isValid(userId)) {\n account = await db.collection('accounts').findOne({\n userId: new ObjectId(userId),\n providerId: 'github',\n });\n }\n\n if (!account) {\n return null;\n }\n\n const accessToken = account.accessToken || account.access_token;\n\n return accessToken || null;\n } catch (error) {\n logger.error('Error retrieving GitHub token from DB:', error);\n return null;\n }\n};\n"],"mappings":"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"}
|
|
@@ -2,11 +2,11 @@ import { ensureMongoDocumentToObject } from "../utils/ensureMongoDocumentToObjec
|
|
|
2
2
|
import { GenericError } from "../utils/errors/ErrorsClass.mjs";
|
|
3
3
|
import { getOrganizationById } from "./organization.service.mjs";
|
|
4
4
|
import { ProjectModel } from "../models/project.model.mjs";
|
|
5
|
+
import { OAuth2AccessTokenModel } from "../models/oAuth2.model.mjs";
|
|
5
6
|
import { getUserById } from "./user.service.mjs";
|
|
6
7
|
import { mapUserToAPI } from "../utils/mapper/user.mjs";
|
|
7
8
|
import { mapOrganizationToAPI } from "../utils/mapper/organization.mjs";
|
|
8
9
|
import { mapProjectToAPI } from "../utils/mapper/project.mjs";
|
|
9
|
-
import { OAuth2AccessTokenModel } from "../models/oAuth2.model.mjs";
|
|
10
10
|
import { getTokenExpireAt } from "../utils/oAuth2.mjs";
|
|
11
11
|
import { randomBytes } from "node:crypto";
|
|
12
12
|
|
|
@@ -79,7 +79,7 @@ const changeSubscriptionStatus = async (subscriptionId, status, userId, organiza
|
|
|
79
79
|
email: user.email,
|
|
80
80
|
planName: organization.plan.type,
|
|
81
81
|
date: (/* @__PURE__ */ new Date()).toLocaleDateString(),
|
|
82
|
-
link: `${process.env.
|
|
82
|
+
link: `${process.env.APP_URL}/dashboard`
|
|
83
83
|
};
|
|
84
84
|
switch (status) {
|
|
85
85
|
case "active":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.service.mjs","names":["Stripe","discountType: 'amount' | 'percentage' | null","results: PricingResult"],"sources":["../../../src/services/subscription.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { GenericError } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport Stripe from 'stripe';\nimport type { Organization } from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\nimport { sendEmail } from './email.service';\nimport { getOrganizationById, updatePlan } from './organization.service';\nimport { getUserById } from './user.service';\n\nexport const addOrUpdateSubscription = async (\n subscriptionId: string,\n priceId: string,\n customerId: string,\n userId: string,\n organization: Organization,\n status: Plan['status']\n): Promise<Plan | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n });\n }\n\n if (String(user.customerId) !== customerId) {\n (user.customerId as unknown as string) = customerId;\n await user.save();\n }\n\n const planInfo = retrievePlanInformation(priceId);\n\n const subscriptions = await stripe.subscriptions.list({\n customer: customerId,\n status: 'active',\n limit: 1,\n });\n\n if (subscriptions.data.length >= 1) {\n // Active subscription exists; update it to the new plan\n const otherSubscriptionArray = subscriptions.data.filter(\n (subscription) => subscription.id !== subscriptionId\n );\n\n for (const subscription of otherSubscriptionArray) {\n await stripe.subscriptions.cancel(subscription.id);\n }\n }\n\n const updatedOrganization = await updatePlan(organization, {\n creatorId: user.id,\n priceId,\n customerId,\n subscriptionId,\n type: planInfo.type,\n period: planInfo.period,\n status,\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n logger.info(\n `Plan updated for organization ${organization.id} - ${planInfo.type} - ${planInfo.period}`\n );\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const cancelSubscription = async (\n subscriptionId: string | Organization['id'],\n organizationId: Organization['id'] | string\n): Promise<Plan | null> => {\n const organization = await getOrganizationById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', {\n subscriptionId,\n });\n }\n\n if (!subscriptionId) {\n throw new GenericError('NO_SUBSCRIPTION_ID_PROVIDED');\n }\n\n if (!organization.plan) {\n throw new GenericError('ORGANIZATION_PLAN_NOT_FOUND', {\n subscriptionId,\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await updatePlan(organization, {\n status: 'canceled',\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n logger.info(\n `Cancelled plan for organization ${updatedOrganization.id} - ${updatedOrganization.plan?.type} - ${updatedOrganization.plan?.period}`\n );\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const changeSubscriptionStatus = async (\n subscriptionId: string,\n status: Plan['status'],\n userId: string,\n organizationId: string\n): Promise<Plan | null> => {\n const organization = await getOrganizationById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n if (!organization.plan) {\n throw new GenericError('ORGANIZATION_PLAN_NOT_FOUND', {\n userId,\n subscriptionId,\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await updatePlan(organization, {\n status,\n subscriptionId,\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n logger.info(\n `Updated plan status for organization ${organization.id} - Status: ${status}`\n );\n\n const emailData = {\n to: user.email,\n username: user.name,\n email: user.email,\n planName: organization.plan.type,\n date: new Date().toLocaleDateString(),\n link: `${process.env.CLIENT_URL}/dashboard`,\n };\n\n switch (status) {\n case 'active':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentSuccess',\n organizationName: organization.name,\n subscriptionStartDate: emailData.date,\n manageSubscriptionLink: emailData.link,\n });\n break;\n case 'canceled':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentCancellation',\n organizationName: organization.name,\n cancellationDate: emailData.date,\n reactivateLink: emailData.link,\n });\n break;\n case 'incomplete':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentError',\n organizationName: organization.name,\n errorDate: emailData.date,\n retryPaymentLink: emailData.link,\n });\n break;\n default:\n logger.warn(`Unhandled subscription status: ${status}`);\n }\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const getCouponId = async (\n promoCode: string\n): Promise<string | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Retrieve the coupon details by name\n const coupons = await stripe.coupons.list();\n const matchingCoupon = coupons.data.find(\n (coupon) => coupon.name === promoCode\n );\n\n return matchingCoupon ? matchingCoupon.id : null;\n } catch (error) {\n console.error('Error retrieving coupon:', error);\n return null;\n }\n};\n\nexport type PricingResult = Record<\n string,\n {\n originalTotal: number;\n discountApplied: number;\n discountType: 'amount' | 'percentage' | null;\n finalTotal: number;\n currency: string;\n }\n>;\n\nexport const getPricing = async (\n priceIds: string[],\n promoCode?: string\n): Promise<PricingResult> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // 1. Fetch all price objects\n const pricePromises = priceIds.map((priceId) =>\n stripe.prices.retrieve(priceId)\n );\n const prices = await Promise.all(pricePromises);\n\n // Calculate the total amount before discount (to help with proportional distribution if needed)\n const totalAmount = prices.reduce(\n (sum, price) => sum + (price.unit_amount ?? 0),\n 0\n );\n\n // 2. Retrieve the discount (if promo code is provided)\n let discountAmount = 0;\n let discountType: 'amount' | 'percentage' | null = null;\n\n if (promoCode) {\n const coupons = await stripe.coupons.list();\n const matchingCoupons = coupons.data.find(\n (coupon) => coupon.name === promoCode\n );\n if (matchingCoupons) {\n if (matchingCoupons.amount_off) {\n discountAmount = matchingCoupons.amount_off;\n discountType = 'amount';\n } else if (matchingCoupons.percent_off) {\n // For a percentage discount, we won't store discountAmount as a raw number\n // because each price line is discounted individually by the same percentage.\n discountAmount = matchingCoupons.percent_off;\n discountType = 'percentage';\n }\n }\n }\n\n // 3. Build the result for each priceId\n const results: PricingResult = {};\n\n for (const price of prices) {\n if (!price.id || !price.unit_amount) {\n continue; // Skip any invalid price\n }\n\n const originalTotal = price.unit_amount;\n let appliedDiscount = 0;\n let finalTotal = originalTotal;\n\n // Apply discount based on the discount type\n if (discountType === 'percentage' && discountAmount > 0) {\n // percentage-based discount\n appliedDiscount = (originalTotal * discountAmount) / 100;\n finalTotal = originalTotal - appliedDiscount;\n } else if (\n discountType === 'amount' &&\n totalAmount > 0 &&\n discountAmount > 0\n ) {\n // fixed amount discount - distribute proportionally\n const proportion = originalTotal / totalAmount;\n appliedDiscount = discountAmount * proportion;\n finalTotal = originalTotal - appliedDiscount;\n }\n\n // Prevent final total from going negative due to rounding\n finalTotal = Math.max(finalTotal, 0);\n\n results[price.id] = {\n originalTotal: originalTotal,\n discountApplied: appliedDiscount,\n discountType,\n finalTotal: finalTotal,\n currency: price.currency,\n };\n }\n\n return results;\n } catch (error) {\n console.error('Error calculating pricing per priceId:', error);\n throw new Error('Failed to calculate pricing breakdown.');\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription.service.mjs","names":["Stripe","discountType: 'amount' | 'percentage' | null","results: PricingResult"],"sources":["../../../src/services/subscription.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { GenericError } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport Stripe from 'stripe';\nimport type { Organization } from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\nimport { sendEmail } from './email.service';\nimport { getOrganizationById, updatePlan } from './organization.service';\nimport { getUserById } from './user.service';\n\nexport const addOrUpdateSubscription = async (\n subscriptionId: string,\n priceId: string,\n customerId: string,\n userId: string,\n organization: Organization,\n status: Plan['status']\n): Promise<Plan | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n });\n }\n\n if (String(user.customerId) !== customerId) {\n (user.customerId as unknown as string) = customerId;\n await user.save();\n }\n\n const planInfo = retrievePlanInformation(priceId);\n\n const subscriptions = await stripe.subscriptions.list({\n customer: customerId,\n status: 'active',\n limit: 1,\n });\n\n if (subscriptions.data.length >= 1) {\n // Active subscription exists; update it to the new plan\n const otherSubscriptionArray = subscriptions.data.filter(\n (subscription) => subscription.id !== subscriptionId\n );\n\n for (const subscription of otherSubscriptionArray) {\n await stripe.subscriptions.cancel(subscription.id);\n }\n }\n\n const updatedOrganization = await updatePlan(organization, {\n creatorId: user.id,\n priceId,\n customerId,\n subscriptionId,\n type: planInfo.type,\n period: planInfo.period,\n status,\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n logger.info(\n `Plan updated for organization ${organization.id} - ${planInfo.type} - ${planInfo.period}`\n );\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const cancelSubscription = async (\n subscriptionId: string | Organization['id'],\n organizationId: Organization['id'] | string\n): Promise<Plan | null> => {\n const organization = await getOrganizationById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', {\n subscriptionId,\n });\n }\n\n if (!subscriptionId) {\n throw new GenericError('NO_SUBSCRIPTION_ID_PROVIDED');\n }\n\n if (!organization.plan) {\n throw new GenericError('ORGANIZATION_PLAN_NOT_FOUND', {\n subscriptionId,\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await updatePlan(organization, {\n status: 'canceled',\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n logger.info(\n `Cancelled plan for organization ${updatedOrganization.id} - ${updatedOrganization.plan?.type} - ${updatedOrganization.plan?.period}`\n );\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const changeSubscriptionStatus = async (\n subscriptionId: string,\n status: Plan['status'],\n userId: string,\n organizationId: string\n): Promise<Plan | null> => {\n const organization = await getOrganizationById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n if (!organization.plan) {\n throw new GenericError('ORGANIZATION_PLAN_NOT_FOUND', {\n userId,\n subscriptionId,\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await updatePlan(organization, {\n status,\n subscriptionId,\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n logger.info(\n `Updated plan status for organization ${organization.id} - Status: ${status}`\n );\n\n const emailData = {\n to: user.email,\n username: user.name,\n email: user.email,\n planName: organization.plan.type,\n date: new Date().toLocaleDateString(),\n link: `${process.env.APP_URL}/dashboard`,\n };\n\n switch (status) {\n case 'active':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentSuccess',\n organizationName: organization.name,\n subscriptionStartDate: emailData.date,\n manageSubscriptionLink: emailData.link,\n });\n break;\n case 'canceled':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentCancellation',\n organizationName: organization.name,\n cancellationDate: emailData.date,\n reactivateLink: emailData.link,\n });\n break;\n case 'incomplete':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentError',\n organizationName: organization.name,\n errorDate: emailData.date,\n retryPaymentLink: emailData.link,\n });\n break;\n default:\n logger.warn(`Unhandled subscription status: ${status}`);\n }\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const getCouponId = async (\n promoCode: string\n): Promise<string | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Retrieve the coupon details by name\n const coupons = await stripe.coupons.list();\n const matchingCoupon = coupons.data.find(\n (coupon) => coupon.name === promoCode\n );\n\n return matchingCoupon ? matchingCoupon.id : null;\n } catch (error) {\n console.error('Error retrieving coupon:', error);\n return null;\n }\n};\n\nexport type PricingResult = Record<\n string,\n {\n originalTotal: number;\n discountApplied: number;\n discountType: 'amount' | 'percentage' | null;\n finalTotal: number;\n currency: string;\n }\n>;\n\nexport const getPricing = async (\n priceIds: string[],\n promoCode?: string\n): Promise<PricingResult> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // 1. Fetch all price objects\n const pricePromises = priceIds.map((priceId) =>\n stripe.prices.retrieve(priceId)\n );\n const prices = await Promise.all(pricePromises);\n\n // Calculate the total amount before discount (to help with proportional distribution if needed)\n const totalAmount = prices.reduce(\n (sum, price) => sum + (price.unit_amount ?? 0),\n 0\n );\n\n // 2. Retrieve the discount (if promo code is provided)\n let discountAmount = 0;\n let discountType: 'amount' | 'percentage' | null = null;\n\n if (promoCode) {\n const coupons = await stripe.coupons.list();\n const matchingCoupons = coupons.data.find(\n (coupon) => coupon.name === promoCode\n );\n if (matchingCoupons) {\n if (matchingCoupons.amount_off) {\n discountAmount = matchingCoupons.amount_off;\n discountType = 'amount';\n } else if (matchingCoupons.percent_off) {\n // For a percentage discount, we won't store discountAmount as a raw number\n // because each price line is discounted individually by the same percentage.\n discountAmount = matchingCoupons.percent_off;\n discountType = 'percentage';\n }\n }\n }\n\n // 3. Build the result for each priceId\n const results: PricingResult = {};\n\n for (const price of prices) {\n if (!price.id || !price.unit_amount) {\n continue; // Skip any invalid price\n }\n\n const originalTotal = price.unit_amount;\n let appliedDiscount = 0;\n let finalTotal = originalTotal;\n\n // Apply discount based on the discount type\n if (discountType === 'percentage' && discountAmount > 0) {\n // percentage-based discount\n appliedDiscount = (originalTotal * discountAmount) / 100;\n finalTotal = originalTotal - appliedDiscount;\n } else if (\n discountType === 'amount' &&\n totalAmount > 0 &&\n discountAmount > 0\n ) {\n // fixed amount discount - distribute proportionally\n const proportion = originalTotal / totalAmount;\n appliedDiscount = discountAmount * proportion;\n finalTotal = originalTotal - appliedDiscount;\n }\n\n // Prevent final total from going negative due to rounding\n finalTotal = Math.max(finalTotal, 0);\n\n results[price.id] = {\n originalTotal: originalTotal,\n discountApplied: appliedDiscount,\n discountType,\n finalTotal: finalTotal,\n currency: price.currency,\n };\n }\n\n return results;\n } catch (error) {\n console.error('Error calculating pricing per priceId:', error);\n throw new Error('Failed to calculate pricing breakdown.');\n }\n};\n"],"mappings":"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"}
|
|
@@ -34,7 +34,7 @@ const formatSession = (session) => {
|
|
|
34
34
|
};
|
|
35
35
|
const getAuth = (dbClient) => {
|
|
36
36
|
if (!dbClient) throw new Error("MongoDB connection not established");
|
|
37
|
-
|
|
37
|
+
return betterAuth({
|
|
38
38
|
appName: "Intlayer",
|
|
39
39
|
database: mongodbAdapter(dbClient.db()),
|
|
40
40
|
user: { modelName: "users" },
|
|
@@ -44,7 +44,7 @@ const getAuth = (dbClient) => {
|
|
|
44
44
|
type: "welcome",
|
|
45
45
|
to: user.email,
|
|
46
46
|
username: user.name ?? user.email.split("@")[0],
|
|
47
|
-
loginLink: `${process.env.
|
|
47
|
+
loginLink: `${process.env.APP_URL}/auth/login`,
|
|
48
48
|
locale: user.lang
|
|
49
49
|
});
|
|
50
50
|
logger.info("Welcome e‑mail delivered", { email: user.email });
|
|
@@ -65,7 +65,7 @@ const getAuth = (dbClient) => {
|
|
|
65
65
|
type: "welcome",
|
|
66
66
|
to: user.email,
|
|
67
67
|
username: user.name ?? user.email.split("@")[0],
|
|
68
|
-
loginLink: `${process.env.
|
|
68
|
+
loginLink: `${process.env.APP_URL}/auth/login`,
|
|
69
69
|
locale: user.lang
|
|
70
70
|
});
|
|
71
71
|
logger.info("Welcome e‑mail delivered", { email: user.email });
|
|
@@ -95,7 +95,6 @@ const getAuth = (dbClient) => {
|
|
|
95
95
|
plugins: [
|
|
96
96
|
customSession(async ({ session }) => {
|
|
97
97
|
const typedSession = session;
|
|
98
|
-
await auth.api.callbackSSOSAML;
|
|
99
98
|
let userAPI = null;
|
|
100
99
|
let organizationAPI = null;
|
|
101
100
|
let projectAPI = null;
|
|
@@ -159,7 +158,7 @@ const getAuth = (dbClient) => {
|
|
|
159
158
|
type: "resetPassword",
|
|
160
159
|
to: user.email,
|
|
161
160
|
username: user.name ?? user.email.split("@")[0],
|
|
162
|
-
resetLink: `${process.env.
|
|
161
|
+
resetLink: `${process.env.APP_URL}/auth/password/reset?token=${token}`
|
|
163
162
|
});
|
|
164
163
|
},
|
|
165
164
|
resetPasswordTokenExpiresIn: 3600
|
|
@@ -188,7 +187,7 @@ const getAuth = (dbClient) => {
|
|
|
188
187
|
crossSubDomainCookies: {
|
|
189
188
|
enabled: true,
|
|
190
189
|
additionalCookies: ["session_token"],
|
|
191
|
-
domain: process.env.
|
|
190
|
+
domain: process.env.APP_URL
|
|
192
191
|
},
|
|
193
192
|
cookiePrefix: "intlayer",
|
|
194
193
|
cookies: { session_token: {
|
|
@@ -198,7 +197,7 @@ const getAuth = (dbClient) => {
|
|
|
198
197
|
secure: true
|
|
199
198
|
}
|
|
200
199
|
} },
|
|
201
|
-
trustedOrigins: [process.env.
|
|
200
|
+
trustedOrigins: [process.env.WEBSITE_URL, process.env.APP_URL],
|
|
202
201
|
socialProviders: {
|
|
203
202
|
google: {
|
|
204
203
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
@@ -208,6 +207,14 @@ const getAuth = (dbClient) => {
|
|
|
208
207
|
clientId: process.env.GITHUB_CLIENT_ID,
|
|
209
208
|
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
|
210
209
|
},
|
|
210
|
+
atlassian: {
|
|
211
|
+
clientId: process.env.ATLASSIAN_CLIENT_ID,
|
|
212
|
+
clientSecret: process.env.ATLASSIAN_CLIENT_SECRET
|
|
213
|
+
},
|
|
214
|
+
gitlab: {
|
|
215
|
+
clientId: process.env.GITLAB_CLIENT_ID,
|
|
216
|
+
clientSecret: process.env.GITLAB_CLIENT_SECRET
|
|
217
|
+
},
|
|
211
218
|
linkedin: {
|
|
212
219
|
clientId: process.env.LINKEDIN_CLIENT_ID,
|
|
213
220
|
clientSecret: process.env.LINKEDIN_CLIENT_SECRET
|
|
@@ -219,7 +226,6 @@ const getAuth = (dbClient) => {
|
|
|
219
226
|
},
|
|
220
227
|
logger: { log: (level, message, ...args) => logger[level](message, ...args) }
|
|
221
228
|
});
|
|
222
|
-
return auth;
|
|
223
229
|
};
|
|
224
230
|
|
|
225
231
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getAuth.mjs","names":["userAPI: UserAPI | null","organizationAPI: OrganizationAPI | null","projectAPI: ProjectAPI | null"],"sources":["../../../../src/utils/auth/getAuth.ts"],"sourcesContent":["import { passkey } from '@better-auth/passkey';\nimport { sso } from '@better-auth/sso';\nimport { sendVerificationUpdate } from '@controllers/user.controller';\nimport { logger } from '@logger';\nimport { OrganizationModel } from '@models/organization.model';\nimport { sendEmail } from '@services/email.service';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapSessionToAPI } from '@utils/mapper/session';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport {\n computeEffectivePermission,\n getSessionRoles,\n intersectPermissions,\n} from '@utils/permissions';\nimport { betterAuth, type OmitId } from 'better-auth';\nimport { mongodbAdapter } from 'better-auth/adapters/mongodb';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { customSession, lastLoginMethod, twoFactor } from 'better-auth/plugins';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { MongoClient } from 'mongodb';\nimport type { OrganizationAPI } from '@/types/organization.types';\nimport type { ProjectAPI } from '@/types/project.types';\nimport type {\n Session,\n SessionContext,\n SessionDataApi,\n} from '@/types/session.types';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type Auth = ReturnType<typeof betterAuth>;\n\nexport const formatSession = (session: SessionContext): OmitId<Session> => {\n const roles = getSessionRoles(session);\n let permissions = computeEffectivePermission(roles);\n\n // Intersect in the case a Access Token try to override the permissions\n if (session.permissions) {\n permissions = intersectPermissions(permissions, session.permissions);\n }\n\n const resultSession = {\n session: session.session,\n user: session.user,\n organization: session.organization,\n project: session.project,\n authType: 'session',\n permissions,\n roles,\n } as OmitId<Session>;\n\n return resultSession;\n};\n\nexport const getAuth = (dbClient: MongoClient): Auth => {\n if (!dbClient) {\n throw new Error('MongoDB connection not established');\n }\n\n const auth = betterAuth({\n appName: 'Intlayer',\n\n database: mongodbAdapter(dbClient.db()),\n\n /**\n * User model\n */\n user: {\n modelName: 'users',\n },\n\n databaseHooks: {\n user: {\n create: {\n // Runs once, immediately after the INSERT\n after: async (user) => {\n if (!user?.emailVerified) return;\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.CLIENT_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n },\n },\n },\n },\n\n hooks: {\n after: createAuthMiddleware(async (ctx) => {\n const { path, context } = ctx;\n\n const newUser = context.newSession?.user;\n const existingUser = context.session?.user;\n const user = newUser ?? existingUser;\n\n if (!user) return;\n\n if (path.includes('/verify-email')) {\n sendVerificationUpdate(user as unknown as User);\n logger.info('SSE verification update sent', {\n email: user.email,\n userId: user.id,\n });\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.CLIENT_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n }\n }),\n },\n\n advanced: {\n // 1️⃣ Change or drop the global prefix\n // cookiePrefix: \"intlayer\", // => intlayer.session_token\n cookiePrefix: 'intlayer', // => session_token (no prefix)\n\n // 2️⃣ Override just the session‑token cookie\n cookies: {\n session_token: {\n // name: 'intlayer_session_token', // final name depends on the prefix above\n // attributes: { sameSite: \"lax\", maxAge: 60 * 60 * 24 } // optional\n },\n },\n\n // 3️⃣ (optional) turn off the automatic __Secure‑ prefix in non‑prod\n // useSecureCookies: false,\n },\n\n secret: process.env.BETTER_AUTH_SECRET as string,\n session: {\n modelName: 'sessions',\n id: 'id',\n\n additionalFields: {\n activeOrganizationId: { type: 'string', nullable: true, input: false },\n activeProjectId: { type: 'string', nullable: true, input: false },\n },\n },\n\n plugins: [\n customSession(async ({ session }) => {\n const typedSession = session as unknown as SessionDataApi;\n\n await auth.api.callbackSSOSAML;\n\n let userAPI: UserAPI | null = null;\n let organizationAPI: OrganizationAPI | null = null;\n let projectAPI: ProjectAPI | null = null;\n\n if (typedSession.userId) {\n const userData = await getUserById(typedSession.userId);\n\n if (userData) {\n userAPI = mapUserToAPI(userData);\n }\n }\n\n if (typedSession.activeOrganizationId) {\n const orgData = await getOrganizationById(\n typedSession.activeOrganizationId\n );\n\n if (orgData) {\n organizationAPI = mapOrganizationToAPI(orgData);\n }\n }\n if (typedSession.activeProjectId) {\n const projectData = await getProjectById(\n typedSession.activeProjectId\n );\n\n if (projectData) {\n projectAPI = mapProjectToAPI(projectData);\n }\n }\n\n const sessionWithNoPermission: SessionContext = {\n session: typedSession,\n user: userAPI!,\n organization: organizationAPI ?? null,\n project: projectAPI ?? null,\n authType: 'session',\n };\n\n const formattedSession = formatSession(sessionWithNoPermission);\n\n return mapSessionToAPI(formattedSession);\n }),\n lastLoginMethod({\n storeInDatabase: true, // adds user.lastLoginMethod in DB and session\n schema: {\n user: {\n lastLoginMethod: 'lastLoginMethod', // Custom field name\n },\n },\n customResolveMethod: (context) => {\n // When user clicks the magic link\n if (context.path === '/magic-link/verify') {\n return 'magic-link';\n }\n\n // Fallback to default behavior for everything else\n return null;\n },\n }),\n passkey({\n rpID: process.env.DOMAIN,\n rpName: 'Intlayer',\n }),\n twoFactor(),\n magicLink({\n sendMagicLink: async ({ email, url }) => {\n logger.info('sending magic link', { email, url });\n await sendEmail({\n type: 'magicLink',\n to: email,\n username: email.split('@')[0],\n magicLink: url,\n });\n },\n }),\n sso({\n organizationProvisioning: {},\n }),\n ],\n\n emailAndPassword: {\n enabled: true,\n disableSignUp: false,\n requireEmailVerification: true,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n autoSignIn: true,\n sendResetPassword: async ({ user, token }) => {\n logger.info('sending reset password email', { email: user.email });\n await sendEmail({\n type: 'resetPassword',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n resetLink: `${process.env.CLIENT_URL}/auth/password/reset?token=${token}`,\n });\n },\n resetPasswordTokenExpiresIn: 3600,\n },\n accountLinking: {\n enabled: true, // allow linking in general\n trustedProviders: ['google', 'github', 'linkedin'], // optional: auto‑link when Google verifies the e‑mail\n },\n emailVerification: {\n autoSignInAfterVerification: true,\n sendOnSignIn: true,\n sendVerificationEmail: async ({ user, url }) => {\n logger.info('sending verification email', { email: user.email });\n await sendEmail({\n type: 'validate',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n validationLink: url,\n });\n },\n },\n\n crossSubDomainCookies: {\n enabled: true,\n additionalCookies: ['session_token'],\n domain: process.env.CLIENT_URL as string,\n },\n cookiePrefix: 'intlayer',\n cookies: {\n session_token: {\n name: 'session_token',\n attributes: {\n httpOnly: true,\n secure: true,\n },\n },\n },\n\n trustedOrigins: [process.env.CLIENT_URL as string],\n\n socialProviders: {\n google: {\n clientId: process.env.GOOGLE_CLIENT_ID as string,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,\n },\n github: {\n clientId: process.env.GITHUB_CLIENT_ID as string,\n clientSecret: process.env.GITHUB_CLIENT_SECRET as string,\n },\n linkedin: {\n clientId: process.env.LINKEDIN_CLIENT_ID as string,\n clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string,\n },\n microsoft: {\n clientId: process.env.MICROSOFT_CLIENT_ID as string,\n clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string,\n },\n // socialProviders: {\n // apple: {\n // clientId: process.env.APPLE_CLIENT_ID as string,\n // clientSecret: process.env.APPLE_CLIENT_SECRET as string,\n // // Optional\n // appBundleIdentifier: process.env\n // .APPLE_APP_BUNDLE_IDENTIFIER as string,\n // },\n // },\n // // Add appleid.apple.com to trustedOrigins for Sign In with Apple flows\n // trustedOrigins: ['https://appleid.apple.com'],\n },\n\n logger: {\n log: (level, message, ...args) => logger[level](message, ...args),\n },\n });\n\n return auth;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"getAuth.mjs","names":["userAPI: UserAPI | null","organizationAPI: OrganizationAPI | null","projectAPI: ProjectAPI | null"],"sources":["../../../../src/utils/auth/getAuth.ts"],"sourcesContent":["import { passkey } from '@better-auth/passkey';\nimport { sso } from '@better-auth/sso';\nimport { sendVerificationUpdate } from '@controllers/user.controller';\nimport { logger } from '@logger';\nimport { sendEmail } from '@services/email.service';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapSessionToAPI } from '@utils/mapper/session';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport {\n computeEffectivePermission,\n getSessionRoles,\n intersectPermissions,\n} from '@utils/permissions';\nimport { betterAuth, type OmitId } from 'better-auth';\nimport { mongodbAdapter } from 'better-auth/adapters/mongodb';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { customSession, lastLoginMethod, twoFactor } from 'better-auth/plugins';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { MongoClient } from 'mongodb';\nimport type { OrganizationAPI } from '@/types/organization.types';\nimport type { ProjectAPI } from '@/types/project.types';\nimport type {\n Session,\n SessionContext,\n SessionDataApi,\n} from '@/types/session.types';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type Auth = ReturnType<typeof betterAuth>;\n\nexport const formatSession = (session: SessionContext): OmitId<Session> => {\n const roles = getSessionRoles(session);\n let permissions = computeEffectivePermission(roles);\n\n // Intersect in the case a Access Token try to override the permissions\n if (session.permissions) {\n permissions = intersectPermissions(permissions, session.permissions);\n }\n\n const resultSession = {\n session: session.session,\n user: session.user,\n organization: session.organization,\n project: session.project,\n authType: 'session',\n permissions,\n roles,\n } as OmitId<Session>;\n\n return resultSession;\n};\n\nexport const getAuth = (dbClient: MongoClient): Auth => {\n if (!dbClient) {\n throw new Error('MongoDB connection not established');\n }\n\n const auth = betterAuth({\n appName: 'Intlayer',\n\n database: mongodbAdapter(dbClient.db()),\n\n /**\n * User model\n */\n user: {\n modelName: 'users',\n },\n\n databaseHooks: {\n user: {\n create: {\n // Runs once, immediately after the INSERT\n after: async (user) => {\n if (!user?.emailVerified) return;\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.APP_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n },\n },\n },\n },\n\n hooks: {\n after: createAuthMiddleware(async (ctx) => {\n const { path, context } = ctx;\n\n const newUser = context.newSession?.user;\n const existingUser = context.session?.user;\n const user = newUser ?? existingUser;\n\n if (!user) return;\n\n if (path.includes('/verify-email')) {\n sendVerificationUpdate(user as unknown as User);\n logger.info('SSE verification update sent', {\n email: user.email,\n userId: user.id,\n });\n\n await sendEmail({\n type: 'welcome',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n loginLink: `${process.env.APP_URL}/auth/login`,\n locale: (user as any).lang,\n });\n logger.info('Welcome e‑mail delivered', {\n email: user.email,\n });\n }\n }),\n },\n\n advanced: {\n // 1️⃣ Change or drop the global prefix\n // cookiePrefix: \"intlayer\", // => intlayer.session_token\n cookiePrefix: 'intlayer', // => session_token (no prefix)\n\n // 2️⃣ Override just the session‑token cookie\n cookies: {\n session_token: {\n // name: 'intlayer_session_token', // final name depends on the prefix above\n // attributes: { sameSite: \"lax\", maxAge: 60 * 60 * 24 } // optional\n },\n },\n\n // 3️⃣ (optional) turn off the automatic __Secure‑ prefix in non‑prod\n // useSecureCookies: false,\n },\n\n secret: process.env.BETTER_AUTH_SECRET as string,\n session: {\n modelName: 'sessions',\n id: 'id',\n\n additionalFields: {\n activeOrganizationId: { type: 'string', nullable: true, input: false },\n activeProjectId: { type: 'string', nullable: true, input: false },\n },\n },\n\n plugins: [\n customSession(async ({ session }) => {\n const typedSession = session as unknown as SessionDataApi;\n\n let userAPI: UserAPI | null = null;\n let organizationAPI: OrganizationAPI | null = null;\n let projectAPI: ProjectAPI | null = null;\n\n if (typedSession.userId) {\n const userData = await getUserById(typedSession.userId);\n\n if (userData) {\n userAPI = mapUserToAPI(userData);\n }\n }\n\n if (typedSession.activeOrganizationId) {\n const orgData = await getOrganizationById(\n typedSession.activeOrganizationId\n );\n\n if (orgData) {\n organizationAPI = mapOrganizationToAPI(orgData);\n }\n }\n if (typedSession.activeProjectId) {\n const projectData = await getProjectById(\n typedSession.activeProjectId\n );\n\n if (projectData) {\n projectAPI = mapProjectToAPI(projectData);\n }\n }\n\n const sessionWithNoPermission: SessionContext = {\n session: typedSession,\n user: userAPI!,\n organization: organizationAPI ?? null,\n project: projectAPI ?? null,\n authType: 'session',\n };\n\n const formattedSession = formatSession(sessionWithNoPermission);\n\n return mapSessionToAPI(formattedSession);\n }),\n lastLoginMethod({\n storeInDatabase: true, // adds user.lastLoginMethod in DB and session\n schema: {\n user: {\n lastLoginMethod: 'lastLoginMethod', // Custom field name\n },\n },\n customResolveMethod: (context) => {\n // When user clicks the magic link\n if (context.path === '/magic-link/verify') {\n return 'magic-link';\n }\n\n // Fallback to default behavior for everything else\n return null;\n },\n }),\n passkey({\n rpID: process.env.DOMAIN,\n rpName: 'Intlayer',\n }),\n twoFactor(),\n magicLink({\n sendMagicLink: async ({ email, url }) => {\n logger.info('sending magic link', { email, url });\n await sendEmail({\n type: 'magicLink',\n to: email,\n username: email.split('@')[0],\n magicLink: url,\n });\n },\n }),\n sso({\n organizationProvisioning: {},\n }),\n ],\n\n emailAndPassword: {\n enabled: true,\n disableSignUp: false,\n requireEmailVerification: true,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n autoSignIn: true,\n sendResetPassword: async ({ user, token }) => {\n logger.info('sending reset password email', { email: user.email });\n await sendEmail({\n type: 'resetPassword',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n resetLink: `${process.env.APP_URL}/auth/password/reset?token=${token}`,\n });\n },\n resetPasswordTokenExpiresIn: 3600,\n },\n accountLinking: {\n enabled: true, // allow linking in general\n trustedProviders: ['google', 'github', 'linkedin'], // optional: auto‑link when Google verifies the e‑mail\n },\n emailVerification: {\n autoSignInAfterVerification: true,\n sendOnSignIn: true,\n sendVerificationEmail: async ({ user, url }) => {\n logger.info('sending verification email', { email: user.email });\n await sendEmail({\n type: 'validate',\n to: user.email,\n username: user.name ?? user.email.split('@')[0],\n validationLink: url,\n });\n },\n },\n\n crossSubDomainCookies: {\n enabled: true,\n additionalCookies: ['session_token'],\n domain: process.env.APP_URL as string,\n },\n cookiePrefix: 'intlayer',\n cookies: {\n session_token: {\n name: 'session_token',\n attributes: {\n httpOnly: true,\n secure: true,\n },\n },\n },\n\n trustedOrigins: [\n process.env.WEBSITE_URL as string,\n process.env.APP_URL as string,\n ],\n\n socialProviders: {\n google: {\n clientId: process.env.GOOGLE_CLIENT_ID as string,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,\n },\n github: {\n clientId: process.env.GITHUB_CLIENT_ID as string,\n clientSecret: process.env.GITHUB_CLIENT_SECRET as string,\n },\n atlassian: {\n clientId: process.env.ATLASSIAN_CLIENT_ID as string,\n clientSecret: process.env.ATLASSIAN_CLIENT_SECRET as string,\n },\n gitlab: {\n clientId: process.env.GITLAB_CLIENT_ID as string,\n clientSecret: process.env.GITLAB_CLIENT_SECRET as string,\n },\n linkedin: {\n clientId: process.env.LINKEDIN_CLIENT_ID as string,\n clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string,\n },\n microsoft: {\n clientId: process.env.MICROSOFT_CLIENT_ID as string,\n clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string,\n },\n // socialProviders: {\n // apple: {\n // clientId: process.env.APPLE_CLIENT_ID as string,\n // clientSecret: process.env.APPLE_CLIENT_SECRET as string,\n // // Optional\n // appBundleIdentifier: process.env\n // .APPLE_APP_BUNDLE_IDENTIFIER as string,\n // },\n // },\n // // Add appleid.apple.com to trustedOrigins for Sign In with Apple flows\n // trustedOrigins: ['https://appleid.apple.com'],\n },\n\n logger: {\n log: (level, message, ...args) => logger[level](message, ...args),\n },\n });\n\n return auth;\n};\n"],"mappings":"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"}
|
package/dist/esm/utils/cors.mjs
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { logger } from "../logger/index.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/utils/cors.ts
|
|
4
|
-
const whitelist = [process.env.
|
|
4
|
+
const whitelist = [process.env.APP_URL];
|
|
5
5
|
const corsOptions = {
|
|
6
6
|
origin: (origin, callback) => {
|
|
7
|
-
if (!origin)
|
|
7
|
+
if (!origin) {
|
|
8
|
+
callback(null, true);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
8
11
|
if (whitelist.includes(origin)) {
|
|
9
12
|
logger.info("whitelisted origin", origin);
|
|
10
|
-
|
|
13
|
+
callback(null, true);
|
|
14
|
+
return;
|
|
11
15
|
}
|
|
12
16
|
logger.info("non whitelisted origin", origin);
|
|
13
17
|
callback(null, origin);
|
|
@@ -23,8 +27,14 @@ const corsOptions = {
|
|
|
23
27
|
"browsing-topics"
|
|
24
28
|
],
|
|
25
29
|
exposedHeaders: [""],
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
methods: [
|
|
31
|
+
"GET",
|
|
32
|
+
"HEAD",
|
|
33
|
+
"PUT",
|
|
34
|
+
"PATCH",
|
|
35
|
+
"POST",
|
|
36
|
+
"DELETE"
|
|
37
|
+
],
|
|
28
38
|
credentials: true
|
|
29
39
|
};
|
|
30
40
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.mjs","names":["corsOptions:
|
|
1
|
+
{"version":3,"file":"cors.mjs","names":["corsOptions: FastifyCorsOptions"],"sources":["../../../src/utils/cors.ts"],"sourcesContent":["import type { FastifyCorsOptions } from '@fastify/cors';\nimport { logger } from '@logger';\n\nconst whitelist = [process.env.APP_URL!];\n\nexport const corsOptions: FastifyCorsOptions = {\n origin: (origin, callback) => {\n // Allow requests with no origin (like mobile apps or curl requests)\n if (!origin) {\n callback(null, true);\n return;\n }\n\n if (whitelist.includes(origin)) {\n logger.info('whitelisted origin', origin);\n callback(null, true);\n return;\n }\n\n logger.info('non whitelisted origin', origin);\n // Reflect the request's origin (echo back the origin header)\n callback(null, origin);\n },\n allowedHeaders: [\n 'authorization',\n 'Content-Type',\n 'credentials',\n 'cache-control',\n 'Access-Control-Allow-Origin',\n 'private-state-token-redemption',\n 'private-state-token-issuance',\n 'browsing-topics',\n ],\n exposedHeaders: [''],\n methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],\n credentials: true,\n};\n"],"mappings":";;;AAGA,MAAM,YAAY,CAAC,QAAQ,IAAI,QAAS;AAExC,MAAaA,cAAkC;CAC7C,SAAS,QAAQ,aAAa;AAE5B,MAAI,CAAC,QAAQ;AACX,YAAS,MAAM,KAAK;AACpB;;AAGF,MAAI,UAAU,SAAS,OAAO,EAAE;AAC9B,UAAO,KAAK,sBAAsB,OAAO;AACzC,YAAS,MAAM,KAAK;AACpB;;AAGF,SAAO,KAAK,0BAA0B,OAAO;AAE7C,WAAS,MAAM,OAAO;;CAExB,gBAAgB;EACd;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CACD,gBAAgB,CAAC,GAAG;CACpB,SAAS;EAAC;EAAO;EAAQ;EAAO;EAAS;EAAQ;EAAS;CAC1D,aAAa;CACd"}
|
|
@@ -3,7 +3,7 @@ import { HttpStatusCodes } from "../httpStatusCodes.mjs";
|
|
|
3
3
|
import { formatPaginatedResponse, formatResponse } from "../responseData.mjs";
|
|
4
4
|
import { errorData } from "./errorCodes.mjs";
|
|
5
5
|
import { Locales } from "@intlayer/types";
|
|
6
|
-
import { t } from "
|
|
6
|
+
import { t } from "fastify-intlayer";
|
|
7
7
|
|
|
8
8
|
//#region src/utils/errors/ErrorHandler.ts
|
|
9
9
|
var ErrorHandler = class ErrorHandler {
|
|
@@ -27,7 +27,10 @@ var ErrorHandler = class ErrorHandler {
|
|
|
27
27
|
* @param isPaginatedResponse - (Optional) Flag to determine if the response should be paginated.
|
|
28
28
|
*/
|
|
29
29
|
static handleAppErrorResponse(res, error, messageDetails, isPaginatedResponse = false) {
|
|
30
|
-
if (!error.isAppError)
|
|
30
|
+
if (!error.isAppError) {
|
|
31
|
+
ErrorHandler.handleCustomErrorResponse(res, error.errorKey ?? "UNKNOWN_ERROR", "Error", error.message ?? JSON.stringify(error), void 0, error.httpStatusCode ?? HttpStatusCodes.INTERNAL_SERVER_ERROR_500, isPaginatedResponse);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
31
34
|
const isMultilingual = error.isMultilingual ?? false;
|
|
32
35
|
ErrorHandler.handleCustomErrorResponse(res, error.errorKey, isMultilingual ? error.multilingualTitle : error.title, isMultilingual ? error.multilingualMessage : error.message, error.messageDetails ?? messageDetails, error.httpStatusCode, isPaginatedResponse);
|
|
33
36
|
}
|
|
@@ -54,7 +57,8 @@ var ErrorHandler = class ErrorHandler {
|
|
|
54
57
|
},
|
|
55
58
|
status
|
|
56
59
|
});
|
|
57
|
-
res.status(status).json(responseData$1);
|
|
60
|
+
if ("status" in res && "json" in res) res.status(status).json(responseData$1);
|
|
61
|
+
else res.code(status).send(responseData$1);
|
|
58
62
|
return;
|
|
59
63
|
}
|
|
60
64
|
const responseData = formatResponse({
|
|
@@ -66,7 +70,31 @@ var ErrorHandler = class ErrorHandler {
|
|
|
66
70
|
},
|
|
67
71
|
status
|
|
68
72
|
});
|
|
69
|
-
res.status(status).json(responseData);
|
|
73
|
+
if ("status" in res && "json" in res) res.status(status).json(responseData);
|
|
74
|
+
else res.code(status).send(responseData);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Formats a generic error response without sending it (useful for Fastify plugins).
|
|
78
|
+
* @param errorKey - A key representing the specific error.
|
|
79
|
+
* @param errorDetails - Optional error details to include.
|
|
80
|
+
* @param statusCode - Optional HTTP status code.
|
|
81
|
+
* @returns Formatted error response object.
|
|
82
|
+
*/
|
|
83
|
+
static formatGenericErrorResponse(errorKey, errorDetails, statusCode) {
|
|
84
|
+
const error = errorData[errorKey];
|
|
85
|
+
const status = statusCode ?? error.statusCode;
|
|
86
|
+
const errorTitle = t(error.title, Locales.ENGLISH);
|
|
87
|
+
const errorMessage = t(error.message, Locales.ENGLISH);
|
|
88
|
+
logger.error(errorMessage, errorDetails);
|
|
89
|
+
return formatResponse({
|
|
90
|
+
error: {
|
|
91
|
+
code: errorKey,
|
|
92
|
+
title: errorTitle,
|
|
93
|
+
message: errorMessage,
|
|
94
|
+
...errorDetails
|
|
95
|
+
},
|
|
96
|
+
status
|
|
97
|
+
});
|
|
70
98
|
}
|
|
71
99
|
};
|
|
72
100
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorHandler.mjs","names":["responseData"],"sources":["../../../../src/utils/errors/ErrorHandler.ts"],"sourcesContent":["// Import required modules and types from their respective locations.\n\nimport { Locales, type StrictModeLocaleMap } from '@intlayer/types';\nimport { logger } from '@logger';\nimport { formatPaginatedResponse, formatResponse } from '@utils/responseData';\nimport type { Response } from 'express';\nimport { t } from '
|
|
1
|
+
{"version":3,"file":"ErrorHandler.mjs","names":["responseData"],"sources":["../../../../src/utils/errors/ErrorHandler.ts"],"sourcesContent":["// Import required modules and types from their respective locations.\n\nimport { Locales, type StrictModeLocaleMap } from '@intlayer/types';\nimport { logger } from '@logger';\nimport { formatPaginatedResponse, formatResponse } from '@utils/responseData';\nimport type { Response } from 'express';\nimport type { FastifyReply } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { UserAPI } from '@/types/user.types';\nimport { HttpStatusCodes } from '@/utils/httpStatusCodes';\nimport type { AppError } from './ErrorsClass';\nimport { type ErrorCodes, errorData } from './errorCodes';\n\ntype ResponseLike = Response | FastifyReply;\n\n// Define a class named 'ErrorHandler' to encapsulate error handling logic.\nexport class ErrorHandler {\n /**\n * Handles generic error responses by formatting and sending a JSON response.\n * @param res - The response object provided by Express.js.\n * @param errorKey - A key representing the specific error.\n * @param statusCode - (Optional) A specific HTTP status code to use for the response.\n * @param isPaginatedResponse - Flag to determine if the response should be paginated.\n */\n static handleGenericErrorResponse(\n res: ResponseLike,\n errorKey: ErrorCodes,\n errorDetails?: object,\n statusCode?: HttpStatusCodes,\n isPaginatedResponse: boolean = false\n ) {\n const error = errorData[errorKey];\n const status = statusCode ?? error.statusCode; // Use the provided status code or default to the one in errorData.\n\n // Delegate to a more customizable error response handler.\n ErrorHandler.handleCustomErrorResponse(\n res,\n errorKey,\n error.title,\n error.message,\n errorDetails,\n status,\n isPaginatedResponse\n );\n }\n\n /**\n * Handles application-specific error responses by formatting and sending a JSON response.\n * @param res - The response object provided by Express.js.\n * @param error - The error object.\n * @param messageDetails - (Optional) Additional message details to include in the response.\n * @param isPaginatedResponse - (Optional) Flag to determine if the response should be paginated.\n */\n static handleAppErrorResponse(\n res: ResponseLike,\n error: AppError,\n messageDetails?: object,\n isPaginatedResponse: boolean = false\n ) {\n if (!error.isAppError) {\n ErrorHandler.handleCustomErrorResponse(\n res,\n error.errorKey ?? 'UNKNOWN_ERROR',\n 'Error',\n error.message ?? JSON.stringify(error),\n undefined,\n error.httpStatusCode ?? HttpStatusCodes.INTERNAL_SERVER_ERROR_500,\n isPaginatedResponse\n );\n return;\n }\n\n const isMultilingual = error.isMultilingual ?? false;\n // Delegate to a more customizable error response handler.\n ErrorHandler.handleCustomErrorResponse(\n res,\n error.errorKey,\n isMultilingual ? error.multilingualTitle : error.title,\n isMultilingual ? error.multilingualMessage : error.message,\n error.messageDetails ?? messageDetails,\n error.httpStatusCode,\n isPaginatedResponse\n );\n }\n\n /**\n * Handles more customizable error responses with detailed error messages and codes.\n * @param res - The response object.\n * @param errorKey - Error code key used to fetch the corresponding message and default status.\n * @param message - The localized error message object.\n * @param messageDetails - (Optional) Additional message details to include in the response.\n * @param statusCode - (Optional) HTTP status code, defaults to 500 if not specified.\n * @param isPaginatedResponse - Determines if the error should be part of a paginated response.\n */\n static handleCustomErrorResponse<T>(\n res: ResponseLike,\n errorKey: ErrorCodes | string,\n title: StrictModeLocaleMap<string> | string,\n message: StrictModeLocaleMap<string> | string,\n messageDetails?: object,\n statusCode?: HttpStatusCodes,\n isPaginatedResponse: boolean = false\n ) {\n const errorTitle = t(title as StrictModeLocaleMap<string>, Locales.ENGLISH);\n const errorMessage = t(\n message as StrictModeLocaleMap<string>,\n Locales.ENGLISH\n );\n logger.error(errorMessage, messageDetails); // Log the English version of the error message.\n const status = statusCode ?? HttpStatusCodes.INTERNAL_SERVER_ERROR_500; // Default to 500 if no status code is provided.\n\n if (isPaginatedResponse) {\n // Format the response as a paginated error response if requested.\n const responseData = formatPaginatedResponse<T>({\n error: {\n code: errorKey,\n title: errorTitle,\n message: errorMessage,\n },\n status,\n });\n // Support both Express and Fastify\n if ('status' in res && 'json' in res) {\n (res as Response).status(status).json(responseData);\n } else {\n (res as FastifyReply).code(status).send(responseData);\n }\n return;\n }\n\n // Format the response as a standard non-paginated error response.\n const responseData = formatResponse<UserAPI>({\n error: {\n code: errorKey,\n title: errorTitle,\n message: errorMessage,\n ...messageDetails,\n },\n status,\n });\n\n // Support both Express and Fastify\n if ('status' in res && 'json' in res) {\n (res as Response).status(status).json(responseData);\n } else {\n (res as FastifyReply).code(status).send(responseData);\n }\n }\n\n /**\n * Formats a generic error response without sending it (useful for Fastify plugins).\n * @param errorKey - A key representing the specific error.\n * @param errorDetails - Optional error details to include.\n * @param statusCode - Optional HTTP status code.\n * @returns Formatted error response object.\n */\n static formatGenericErrorResponse(\n errorKey: ErrorCodes,\n errorDetails?: object,\n statusCode?: HttpStatusCodes\n ) {\n const error = errorData[errorKey];\n const status = statusCode ?? error.statusCode;\n const errorTitle = t(error.title, Locales.ENGLISH);\n const errorMessage = t(error.message, Locales.ENGLISH);\n logger.error(errorMessage, errorDetails);\n\n return formatResponse<UserAPI>({\n error: {\n code: errorKey,\n title: errorTitle,\n message: errorMessage,\n ...errorDetails,\n },\n status,\n });\n }\n}\n"],"mappings":";;;;;;;;AAgBA,IAAa,eAAb,MAAa,aAAa;;;;;;;;CAQxB,OAAO,2BACL,KACA,UACA,cACA,YACA,sBAA+B,OAC/B;EACA,MAAM,QAAQ,UAAU;EACxB,MAAM,SAAS,cAAc,MAAM;AAGnC,eAAa,0BACX,KACA,UACA,MAAM,OACN,MAAM,SACN,cACA,QACA,oBACD;;;;;;;;;CAUH,OAAO,uBACL,KACA,OACA,gBACA,sBAA+B,OAC/B;AACA,MAAI,CAAC,MAAM,YAAY;AACrB,gBAAa,0BACX,KACA,MAAM,YAAY,iBAClB,SACA,MAAM,WAAW,KAAK,UAAU,MAAM,EACtC,QACA,MAAM,kBAAkB,gBAAgB,2BACxC,oBACD;AACD;;EAGF,MAAM,iBAAiB,MAAM,kBAAkB;AAE/C,eAAa,0BACX,KACA,MAAM,UACN,iBAAiB,MAAM,oBAAoB,MAAM,OACjD,iBAAiB,MAAM,sBAAsB,MAAM,SACnD,MAAM,kBAAkB,gBACxB,MAAM,gBACN,oBACD;;;;;;;;;;;CAYH,OAAO,0BACL,KACA,UACA,OACA,SACA,gBACA,YACA,sBAA+B,OAC/B;EACA,MAAM,aAAa,EAAE,OAAsC,QAAQ,QAAQ;EAC3E,MAAM,eAAe,EACnB,SACA,QAAQ,QACT;AACD,SAAO,MAAM,cAAc,eAAe;EAC1C,MAAM,SAAS,cAAc,gBAAgB;AAE7C,MAAI,qBAAqB;GAEvB,MAAMA,iBAAe,wBAA2B;IAC9C,OAAO;KACL,MAAM;KACN,OAAO;KACP,SAAS;KACV;IACD;IACD,CAAC;AAEF,OAAI,YAAY,OAAO,UAAU,IAC/B,CAAC,IAAiB,OAAO,OAAO,CAAC,KAAKA,eAAa;OAEnD,CAAC,IAAqB,KAAK,OAAO,CAAC,KAAKA,eAAa;AAEvD;;EAIF,MAAM,eAAe,eAAwB;GAC3C,OAAO;IACL,MAAM;IACN,OAAO;IACP,SAAS;IACT,GAAG;IACJ;GACD;GACD,CAAC;AAGF,MAAI,YAAY,OAAO,UAAU,IAC/B,CAAC,IAAiB,OAAO,OAAO,CAAC,KAAK,aAAa;MAEnD,CAAC,IAAqB,KAAK,OAAO,CAAC,KAAK,aAAa;;;;;;;;;CAWzD,OAAO,2BACL,UACA,cACA,YACA;EACA,MAAM,QAAQ,UAAU;EACxB,MAAM,SAAS,cAAc,MAAM;EACnC,MAAM,aAAa,EAAE,MAAM,OAAO,QAAQ,QAAQ;EAClD,MAAM,eAAe,EAAE,MAAM,SAAS,QAAQ,QAAQ;AACtD,SAAO,MAAM,cAAc,aAAa;AAExC,SAAO,eAAwB;GAC7B,OAAO;IACL,MAAM;IACN,OAAO;IACP,SAAS;IACT,GAAG;IACJ;GACD;GACD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorsClass.mjs","names":[],"sources":["../../../../src/utils/errors/ErrorsClass.ts"],"sourcesContent":["import { HttpStatusCodes } from '@utils/httpStatusCodes';\n// @ts-ignore
|
|
1
|
+
{"version":3,"file":"ErrorsClass.mjs","names":[],"sources":["../../../../src/utils/errors/ErrorsClass.ts"],"sourcesContent":["import { HttpStatusCodes } from '@utils/httpStatusCodes';\n// @ts-ignore fastify-intlayer not build yet\nimport { type StrictModeLocaleMap, t } from 'fastify-intlayer';\nimport { type ErrorCodes, errorData } from './errorCodes';\n\n/**\n * Custom error class that extends the native JavaScript Error class.\n * This class supports multilingual error messages and HTTP status codes.\n */\nexport class AppError extends Error {\n public isAppError: boolean = true; // Flag to identify AppError instances.\n public name: string;\n public isMultilingual: boolean = true;\n public errorKey: string;\n public title: string;\n public multilingualTitle: StrictModeLocaleMap<string>;\n public message: string;\n public multilingualMessage: StrictModeLocaleMap<string>;\n public httpStatusCode: HttpStatusCodes;\n public messageDetails?: object;\n\n /**\n * Constructor for the custom error class.\n * @param multilingualMessage - The error message which can be a simple string or a multilingual object.\n * @param httpStatusCode - Optional HTTP status code, defaults to 500 Internal Server Error.\n */\n constructor(\n multilingualTitle: StrictModeLocaleMap<string>,\n multilingualMessage: StrictModeLocaleMap<string>,\n errorKey: string,\n httpStatusCode: HttpStatusCodes = HttpStatusCodes.INTERNAL_SERVER_ERROR_500,\n messageDetails?: object\n ) {\n const title = t(multilingualTitle); // Translate title based on current locale\n const message = t(multilingualMessage); // Translate message based on current locale.\n\n super(message); // Use translated message for the superclass constructor.\n this.title = title;\n this.multilingualTitle = multilingualTitle;\n this.message = message;\n this.multilingualMessage = multilingualMessage; // Store original message format for potential use.\n this.name = 'AppError';\n this.errorKey = errorKey;\n this.httpStatusCode = httpStatusCode; // Set the HTTP status code.\n this.messageDetails = messageDetails; // Store any additional message details.\n\n // Capture the stack trace to exclude the constructor call.\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\nexport class GenericError extends AppError {\n constructor(errorKey: ErrorCodes, messageDetails?: object) {\n const multilingualTitle = errorData[errorKey].title;\n const multilingualMessage = errorData[errorKey].message;\n const httpStatusCode = errorData[errorKey].statusCode;\n\n super(\n multilingualTitle,\n multilingualMessage,\n errorKey,\n httpStatusCode,\n messageDetails\n );\n }\n}\n"],"mappings":";;;;;;;;;AASA,IAAa,WAAb,cAA8B,MAAM;CAClC,AAAO,aAAsB;CAC7B,AAAO;CACP,AAAO,iBAA0B;CACjC,AAAO;CACP,AAAO;CACP,AAAO;CACP,AAAO;CACP,AAAO;CACP,AAAO;CACP,AAAO;;;;;;CAOP,YACE,mBACA,qBACA,UACA,iBAAkC,gBAAgB,2BAClD,gBACA;EACA,MAAM,QAAQ,EAAE,kBAAkB;EAClC,MAAM,UAAU,EAAE,oBAAoB;AAEtC,QAAM,QAAQ;AACd,OAAK,QAAQ;AACb,OAAK,oBAAoB;AACzB,OAAK,UAAU;AACf,OAAK,sBAAsB;AAC3B,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,OAAK,iBAAiB;AACtB,OAAK,iBAAiB;AAGtB,QAAM,kBAAkB,MAAM,KAAK,YAAY;;;AAInD,IAAa,eAAb,cAAkC,SAAS;CACzC,YAAY,UAAsB,gBAAyB;EACzD,MAAM,oBAAoB,UAAU,UAAU;EAC9C,MAAM,sBAAsB,UAAU,UAAU;EAChD,MAAM,iBAAiB,UAAU,UAAU;AAE3C,QACE,mBACA,qBACA,UACA,gBACA,eACD"}
|