@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.
Files changed (216) hide show
  1. package/README.md +9 -2
  2. package/dist/assets/utils/AI/askDocQuestion/PROMPT.md +1 -1
  3. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/init.json +2054 -0
  4. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_fastify.json +9 -0
  5. package/dist/esm/controllers/ai.controller.mjs +95 -128
  6. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/dictionary.controller.mjs +86 -198
  8. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/eventListener.controller.mjs +13 -19
  10. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/github.controller.mjs +77 -0
  12. package/dist/esm/controllers/github.controller.mjs.map +1 -0
  13. package/dist/esm/controllers/newsletter.controller.mjs +30 -60
  14. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  15. package/dist/esm/controllers/oAuth2.controller.mjs +11 -8
  16. package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
  17. package/dist/esm/controllers/organization.controller.mjs +100 -225
  18. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  19. package/dist/esm/controllers/project.controller.mjs +87 -204
  20. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  21. package/dist/esm/controllers/projectAccessKey.controller.mjs +38 -71
  22. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  23. package/dist/esm/controllers/search.controller.mjs +3 -3
  24. package/dist/esm/controllers/search.controller.mjs.map +1 -1
  25. package/dist/esm/controllers/stripe.controller.mjs +34 -67
  26. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  27. package/dist/esm/controllers/tag.controller.mjs +51 -113
  28. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  29. package/dist/esm/controllers/user.controller.mjs +64 -113
  30. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  31. package/dist/esm/export.mjs +2 -1
  32. package/dist/esm/index.mjs +101 -41
  33. package/dist/esm/index.mjs.map +1 -1
  34. package/dist/esm/middlewares/oAuth2.middleware.mjs +19 -14
  35. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  36. package/dist/esm/middlewares/sessionAuth.middleware.mjs +6 -7
  37. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  38. package/dist/esm/routes/ai.routes.mjs +19 -15
  39. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  40. package/dist/esm/routes/dictionary.routes.mjs +10 -10
  41. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  42. package/dist/esm/routes/eventListener.routes.mjs +3 -3
  43. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  44. package/dist/esm/routes/github.routes.mjs +43 -0
  45. package/dist/esm/routes/github.routes.mjs.map +1 -0
  46. package/dist/esm/routes/newsletter.routes.mjs +5 -5
  47. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  48. package/dist/esm/routes/organization.routes.mjs +11 -11
  49. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  50. package/dist/esm/routes/project.routes.mjs +13 -13
  51. package/dist/esm/routes/project.routes.mjs.map +1 -1
  52. package/dist/esm/routes/search.routes.mjs +3 -3
  53. package/dist/esm/routes/search.routes.mjs.map +1 -1
  54. package/dist/esm/routes/stripe.routes.mjs +5 -5
  55. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  56. package/dist/esm/routes/tags.routes.mjs +6 -6
  57. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  58. package/dist/esm/routes/user.routes.mjs +9 -9
  59. package/dist/esm/routes/user.routes.mjs.map +1 -1
  60. package/dist/esm/schemas/project.schema.mjs +35 -1
  61. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  62. package/dist/esm/services/email.service.mjs +1 -1
  63. package/dist/esm/services/email.service.mjs.map +1 -1
  64. package/dist/esm/services/github.service.mjs +130 -0
  65. package/dist/esm/services/github.service.mjs.map +1 -0
  66. package/dist/esm/services/oAuth2.service.mjs +1 -1
  67. package/dist/esm/services/subscription.service.mjs +1 -1
  68. package/dist/esm/services/subscription.service.mjs.map +1 -1
  69. package/dist/esm/utils/auth/getAuth.mjs +14 -8
  70. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  71. package/dist/esm/utils/cors.mjs +15 -5
  72. package/dist/esm/utils/cors.mjs.map +1 -1
  73. package/dist/esm/utils/errors/ErrorHandler.mjs +32 -4
  74. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  75. package/dist/esm/utils/errors/ErrorsClass.mjs +1 -1
  76. package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
  77. package/dist/esm/utils/errors/errorCodes.mjs +78 -0
  78. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  79. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs +3 -2
  80. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  81. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs +1 -1
  82. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  83. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs +1 -1
  84. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
  85. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs +3 -2
  86. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  87. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs +3 -2
  88. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  89. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs +3 -2
  90. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  91. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs +3 -2
  92. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  93. package/dist/esm/utils/mapper/project.mjs +28 -1
  94. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  95. package/dist/esm/utils/mongoDB/connectDB.mjs +1 -1
  96. package/dist/esm/utils/rateLimiter.mjs +40 -30
  97. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  98. package/dist/esm/webhooks/stripe.webhook.mjs +2 -2
  99. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  100. package/dist/types/controllers/ai.controller.d.ts +29 -12
  101. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  102. package/dist/types/controllers/dictionary.controller.d.ts +23 -13
  103. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  104. package/dist/types/controllers/eventListener.controller.d.ts +4 -2
  105. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  106. package/dist/types/controllers/github.controller.d.ts +63 -0
  107. package/dist/types/controllers/github.controller.d.ts.map +1 -0
  108. package/dist/types/controllers/newsletter.controller.d.ts +8 -7
  109. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  110. package/dist/types/controllers/oAuth2.controller.d.ts +4 -2
  111. package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
  112. package/dist/types/controllers/organization.controller.d.ts +28 -12
  113. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  114. package/dist/types/controllers/project.controller.d.ts +21 -16
  115. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  116. package/dist/types/controllers/projectAccessKey.controller.d.ts +10 -5
  117. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  118. package/dist/types/controllers/search.controller.d.ts +4 -2
  119. package/dist/types/controllers/search.controller.d.ts.map +1 -1
  120. package/dist/types/controllers/stripe.controller.d.ts +11 -12
  121. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  122. package/dist/types/controllers/tag.controller.d.ts +14 -9
  123. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  124. package/dist/types/controllers/user.controller.d.ts +22 -9
  125. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  126. package/dist/types/emails/InviteUserEmail.d.ts +4 -4
  127. package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
  128. package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
  129. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
  130. package/dist/types/emails/ResetUserPassword.d.ts +4 -4
  131. package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
  132. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
  133. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
  134. package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
  135. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
  136. package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
  137. package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
  138. package/dist/types/emails/Welcome.d.ts +4 -4
  139. package/dist/types/emails/Welcome.d.ts.map +1 -1
  140. package/dist/types/export.d.ts +6 -4
  141. package/dist/types/middlewares/oAuth2.middleware.d.ts +9 -4
  142. package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
  143. package/dist/types/middlewares/sessionAuth.middleware.d.ts +13 -3
  144. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  145. package/dist/types/models/dictionary.model.d.ts +4 -4
  146. package/dist/types/models/discussion.model.d.ts +2 -2
  147. package/dist/types/models/oAuth2.model.d.ts +3 -3
  148. package/dist/types/routes/ai.routes.d.ts +2 -2
  149. package/dist/types/routes/ai.routes.d.ts.map +1 -1
  150. package/dist/types/routes/dictionary.routes.d.ts +2 -2
  151. package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
  152. package/dist/types/routes/eventListener.routes.d.ts +2 -2
  153. package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
  154. package/dist/types/routes/github.routes.d.ts +35 -0
  155. package/dist/types/routes/github.routes.d.ts.map +1 -0
  156. package/dist/types/routes/newsletter.routes.d.ts +2 -2
  157. package/dist/types/routes/newsletter.routes.d.ts.map +1 -1
  158. package/dist/types/routes/organization.routes.d.ts +2 -2
  159. package/dist/types/routes/organization.routes.d.ts.map +1 -1
  160. package/dist/types/routes/project.routes.d.ts +2 -2
  161. package/dist/types/routes/project.routes.d.ts.map +1 -1
  162. package/dist/types/routes/search.routes.d.ts +2 -2
  163. package/dist/types/routes/search.routes.d.ts.map +1 -1
  164. package/dist/types/routes/stripe.routes.d.ts +2 -2
  165. package/dist/types/routes/stripe.routes.d.ts.map +1 -1
  166. package/dist/types/routes/tags.routes.d.ts +2 -2
  167. package/dist/types/routes/tags.routes.d.ts.map +1 -1
  168. package/dist/types/routes/user.routes.d.ts +2 -2
  169. package/dist/types/routes/user.routes.d.ts.map +1 -1
  170. package/dist/types/schemas/dictionary.schema.d.ts +6 -6
  171. package/dist/types/schemas/discussion.schema.d.ts +6 -6
  172. package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
  173. package/dist/types/schemas/project.schema.d.ts +6 -6
  174. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  175. package/dist/types/schemas/session.schema.d.ts +6 -6
  176. package/dist/types/schemas/tag.schema.d.ts +6 -6
  177. package/dist/types/schemas/user.schema.d.ts +6 -6
  178. package/dist/types/services/email.service.d.ts +11 -11
  179. package/dist/types/services/github.service.d.ts +21 -0
  180. package/dist/types/services/github.service.d.ts.map +1 -0
  181. package/dist/types/types/project.types.d.ts +18 -5
  182. package/dist/types/types/project.types.d.ts.map +1 -1
  183. package/dist/types/types/session.types.d.ts +1 -1
  184. package/dist/types/types/user.types.d.ts +1 -1
  185. package/dist/types/utils/AI/auditTag/index.d.ts +1 -1
  186. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  187. package/dist/types/utils/cors.d.ts +2 -2
  188. package/dist/types/utils/errors/ErrorHandler.d.ts +31 -3
  189. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  190. package/dist/types/utils/errors/ErrorsClass.d.ts +1 -1
  191. package/dist/types/utils/errors/errorCodes.d.ts +78 -0
  192. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  193. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +8 -4
  194. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
  195. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +6 -3
  196. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
  197. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts +6 -2
  198. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
  199. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +8 -4
  200. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
  201. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +8 -4
  202. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
  203. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +8 -4
  204. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
  205. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts +6 -2
  206. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
  207. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  208. package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
  209. package/dist/types/utils/permissions.d.ts +1 -1
  210. package/dist/types/utils/rateLimiter.d.ts +4 -2
  211. package/dist/types/utils/rateLimiter.d.ts.map +1 -1
  212. package/package.json +23 -27
  213. package/dist/esm/middlewares/request.middleware.mjs +0 -17
  214. package/dist/esm/middlewares/request.middleware.mjs.map +0 -1
  215. package/dist/types/middlewares/request.middleware.d.ts +0 -7
  216. 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":";;;;;;;AAYA,MAAa,uBACX,aACA,UACW;CACX,MAAM,WAAW,QAAQ,IAAI;AAE7B,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,qCAAqC;CAGvD,MAAM,SAAS,IAAI,gBAAgB;EACjC,WAAW;EACX,OAAO;EACP,OAAO;EACP,cAAc;EACf,CAAC;AAEF,KAAI,MACF,QAAO,OAAO,SAAS,MAAM;AAG/B,QAAO,4CAA4C,OAAO,UAAU;;AAGtE,MAAa,uBAAuB,OAAO,SAAkC;CAC3E,MAAM,WAAW,QAAQ,IAAI;CAC7B,MAAM,eAAe,QAAQ,IAAI;AAEjC,KAAI,CAAC,YAAY,CAAC,aAChB,OAAM,IAAI,MAAM,8CAA8C;AAGhE,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,+CACA;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACT;GACD,MAAM,KAAK,UAAU;IACnB,WAAW;IACX,eAAe;IACf;IACD,CAAC;GACH,CACF;AAED,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,iCAAiC,SAAS,aAAa;EAGzE,MAAM,OAAO,MAAM,SAAS,MAAM;AAElC,MAAI,KAAK,MACP,OAAM,IAAI,MAAM,uBAAuB,KAAK,oBAAoB;AAGlE,SAAO,KAAK;UACL,OAAO;AACd,SAAO,MAAM,2CAA2C,MAAM;AAC9D,QAAM;;;AAIV,MAAa,eAAe,OAC1B,gBACgC;AAChC,KAAI;EAGF,MAAM,EAAE,SAAS,MAFD,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC,CAEnB,KAAK,MAAM,yBAAyB;GACjE,MAAM;GACN,UAAU;GACV,YAAY;GACb,CAAC;AAEF,SAAO;UACA,OAAO;AACd,SAAO,MAAM,uCAAuC,MAAM;AAC1D,QAAM;;;;;;;AAQV,MAAa,sBAAsB,OACjC,aACA,OACA,MACA,SAAiB,WACK;AACtB,KAAI;EAKF,MAAM,EAAE,SAAS,MAJD,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC,CAInB,KAAK,IAAI,QAAQ;GAC9C;GACA;GACA,UAAU;GACV,WAAW;GACZ,CAAC;AAEF,MAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,CACzC,QAAO,EAAE;AAcX,SATmB,KAAK,KACrB,QAAQ,SAAS;AAChB,OAAI,KAAK,SAAS,UAAU,CAAC,KAAK,KAAM,QAAO;AAC/C,UAAQ,6BAAmD,MACxD,cAAc,KAAK,MAAM,SAAS,UAAU,CAC9C;IACD,CACD,KAAK,SAAS,KAAK,KAAe;UAG9BA,OAAY;AAEnB,MAAI,MAAM,WAAW,OAAO,MAAM,WAAW,IAAK,QAAO,EAAE;AAE3D,SAAO,MAAM,0CAA0C,MAAM;AAC7D,SAAO,EAAE;;;;;;AAOb,MAAa,4BAA4B,OACvC,aACA,OACA,MACA,MACA,SAAiB,WACU;AAC3B,KAAI;EAGF,MAAM,EAAE,SAAS,MAFD,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC,CAEnB,KAAK,MAAM,WAAW;GACnD;GACA;GACA;GACA,KAAK;GACN,CAAC;AAGF,MAAI,MAAM,QAAQ,KAAK,IAAI,EAAE,aAAa,MACxC,OAAM,IAAI,MAAM,yCAAyC;AAQ3D,SAJuB,OAAO,KAAK,KAAK,SAAS,SAAS,CAAC,SACzD,QACD;UAGMA,OAAY;AACnB,MAAI,MAAM,WAAW,IAAK,QAAO;AAEjC,SAAO,MAAM,4CAA4C,MAAM;AAC/D,QAAM;;;AAKV,MAAa,yBAAyB,OACpC,WAC2B;AAC3B,KAAI;EAEF,MAAM,KADS,aAAa,CACV,IAAI;EAEtB,IAAI,UAAU,MAAM,GAAG,WAAW,UAAU,CAAC,QAAQ;GAC3C;GACR,YAAY;GACb,CAAC;AAEF,MAAI,CAAC,WAAW,SAAS,QAAQ,OAAO,CACtC,WAAU,MAAM,GAAG,WAAW,UAAU,CAAC,QAAQ;GAC/C,QAAQ,IAAI,SAAS,OAAO;GAC5B,YAAY;GACb,CAAC;AAGJ,MAAI,CAAC,QACH,WAAU,MAAM,GAAG,WAAW,WAAW,CAAC,QAAQ;GACxC;GACR,YAAY;GACb,CAAC;AAGJ,MAAI,CAAC,WAAW,SAAS,QAAQ,OAAO,CACtC,WAAU,MAAM,GAAG,WAAW,WAAW,CAAC,QAAQ;GAChD,QAAQ,IAAI,SAAS,OAAO;GAC5B,YAAY;GACb,CAAC;AAGJ,MAAI,CAAC,QACH,QAAO;AAKT,SAFoB,QAAQ,eAAe,QAAQ,gBAE7B;UACf,OAAO;AACd,SAAO,MAAM,0CAA0C,MAAM;AAC7D,SAAO"}
@@ -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.CLIENT_URL}/dashboard`
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
- const auth = betterAuth({
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.CLIENT_URL}/auth/login`,
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.CLIENT_URL}/auth/login`,
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.CLIENT_URL}/auth/password/reset?token=${token}`
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.CLIENT_URL
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.CLIENT_URL],
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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"}
@@ -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.CLIENT_URL];
4
+ const whitelist = [process.env.APP_URL];
5
5
  const corsOptions = {
6
6
  origin: (origin, callback) => {
7
- if (!origin) return callback(null, true);
7
+ if (!origin) {
8
+ callback(null, true);
9
+ return;
10
+ }
8
11
  if (whitelist.includes(origin)) {
9
12
  logger.info("whitelisted origin", origin);
10
- return callback(null, true);
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
- preflightContinue: false,
27
- methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
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: CorsOptions"],"sources":["../../../src/utils/cors.ts"],"sourcesContent":["import { logger } from '@logger';\nimport type { CorsOptions } from 'cors';\n\nconst whitelist = [process.env.CLIENT_URL!];\n\nexport const corsOptions: CorsOptions = {\n origin: (origin, callback) => {\n // Allow requests with no origin (like mobile apps or curl requests)\n if (!origin) return callback(null, true);\n\n if (whitelist.includes(origin)) {\n logger.info('whitelisted origin', origin);\n return callback(null, true);\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 preflightContinue: false,\n methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',\n credentials: true,\n};\n"],"mappings":";;;AAGA,MAAM,YAAY,CAAC,QAAQ,IAAI,WAAY;AAE3C,MAAaA,cAA2B;CACtC,SAAS,QAAQ,aAAa;AAE5B,MAAI,CAAC,OAAQ,QAAO,SAAS,MAAM,KAAK;AAExC,MAAI,UAAU,SAAS,OAAO,EAAE;AAC9B,UAAO,KAAK,sBAAsB,OAAO;AACzC,UAAO,SAAS,MAAM,KAAK;;AAG7B,SAAO,KAAK,0BAA0B,OAAO;AAE7C,WAAS,MAAM,OAAO;;CAExB,gBAAgB;EACd;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CACD,gBAAgB,CAAC,GAAG;CACpB,mBAAmB;CACnB,SAAS;CACT,aAAa;CACd"}
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 "express-intlayer";
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) ErrorHandler.handleCustomErrorResponse(res, error.errorKey ?? "UNKNOWN_ERROR", "Error", error.message ?? JSON.stringify(error), void 0, error.httpStatusCode ?? HttpStatusCodes.INTERNAL_SERVER_ERROR_500, isPaginatedResponse);
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 'express-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\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: Response,\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: Response,\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 }\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: Response,\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 res.status(status).json(responseData);\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 res.status(status).json(responseData);\n }\n}\n"],"mappings":";;;;;;;;AAaA,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,WACT,cAAa,0BACX,KACA,MAAM,YAAY,iBAClB,SACA,MAAM,WAAW,KAAK,UAAU,MAAM,EACtC,QACA,MAAM,kBAAkB,gBAAgB,2BACxC,oBACD;EAGH,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;AACF,OAAI,OAAO,OAAO,CAAC,KAAKA,eAAa;AACrC;;EAIF,MAAM,eAAe,eAAwB;GAC3C,OAAO;IACL,MAAM;IACN,OAAO;IACP,SAAS;IACT,GAAG;IACJ;GACD;GACD,CAAC;AAEF,MAAI,OAAO,OAAO,CAAC,KAAK,aAAa"}
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,6 +1,6 @@
1
1
  import { HttpStatusCodes } from "../httpStatusCodes.mjs";
2
2
  import { errorData } from "./errorCodes.mjs";
3
- import { t } from "express-intlayer";
3
+ import { t } from "fastify-intlayer";
4
4
 
5
5
  //#region src/utils/errors/ErrorsClass.ts
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorsClass.mjs","names":[],"sources":["../../../../src/utils/errors/ErrorsClass.ts"],"sourcesContent":["import { HttpStatusCodes } from '@utils/httpStatusCodes';\n// @ts-ignore express-intlayer not build yet\nimport { type StrictModeLocaleMap, t } from 'express-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"}
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"}