@intlayer/backend 5.1.5 → 5.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/controllers/ai.controller.cjs +61 -43
- package/dist/cjs/controllers/ai.controller.cjs.map +1 -1
- package/dist/cjs/controllers/dictionary.controller.cjs +1 -1
- package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
- package/dist/cjs/controllers/organization.controller.cjs +17 -1
- package/dist/cjs/controllers/organization.controller.cjs.map +1 -1
- package/dist/cjs/controllers/project.controller.cjs +1 -0
- package/dist/cjs/controllers/project.controller.cjs.map +1 -1
- package/dist/cjs/controllers/sessionAuth.controller.cjs +6 -12
- package/dist/cjs/controllers/sessionAuth.controller.cjs.map +1 -1
- package/dist/cjs/controllers/stripe.controller.cjs +31 -10
- package/dist/cjs/controllers/stripe.controller.cjs.map +1 -1
- package/dist/cjs/controllers/tag.controller.cjs +0 -7
- package/dist/cjs/controllers/tag.controller.cjs.map +1 -1
- package/dist/cjs/controllers/user.controller.cjs +1 -1
- package/dist/cjs/controllers/user.controller.cjs.map +1 -1
- package/dist/cjs/emails/InviteUserEmail.cjs +3 -3
- package/dist/cjs/emails/InviteUserEmail.cjs.map +1 -1
- package/dist/cjs/emails/PasswordChangeConfirmation.cjs +3 -3
- package/dist/cjs/emails/PasswordChangeConfirmation.cjs.map +1 -1
- package/dist/cjs/emails/ResetUserPassword.cjs +3 -3
- package/dist/cjs/emails/ResetUserPassword.cjs.map +1 -1
- package/dist/cjs/emails/SubscriptionPaymentCancellation.cjs +19 -3
- package/dist/cjs/emails/SubscriptionPaymentCancellation.cjs.map +1 -1
- package/dist/cjs/emails/SubscriptionPaymentError.cjs +19 -3
- package/dist/cjs/emails/SubscriptionPaymentError.cjs.map +1 -1
- package/dist/cjs/emails/SubscriptionPaymentSuccess.cjs +19 -3
- package/dist/cjs/emails/SubscriptionPaymentSuccess.cjs.map +1 -1
- package/dist/cjs/emails/ValidateUserEmail.cjs +3 -3
- package/dist/cjs/emails/ValidateUserEmail.cjs.map +1 -1
- package/dist/cjs/emails/Welcome.cjs +3 -3
- package/dist/cjs/emails/Welcome.cjs.map +1 -1
- package/dist/cjs/export.cjs +10 -10
- package/dist/cjs/export.cjs.map +1 -1
- package/dist/cjs/routes/ai.routes.cjs +21 -15
- package/dist/cjs/routes/ai.routes.cjs.map +1 -1
- package/dist/cjs/routes/dictionary.routes.cjs +15 -12
- package/dist/cjs/routes/dictionary.routes.cjs.map +1 -1
- package/dist/cjs/routes/event-listener.routes.cjs +4 -4
- package/dist/cjs/routes/event-listener.routes.cjs.map +1 -1
- package/dist/cjs/routes/organization.routes.cjs +15 -15
- package/dist/cjs/routes/organization.routes.cjs.map +1 -1
- package/dist/cjs/routes/project.routes.cjs +30 -18
- package/dist/cjs/routes/project.routes.cjs.map +1 -1
- package/dist/cjs/routes/sessionAuth.routes.cjs +38 -35
- package/dist/cjs/routes/sessionAuth.routes.cjs.map +1 -1
- package/dist/cjs/routes/stripe.routes.cjs +23 -11
- package/dist/cjs/routes/stripe.routes.cjs.map +1 -1
- package/dist/cjs/routes/tags.routes.cjs +11 -11
- package/dist/cjs/routes/tags.routes.cjs.map +1 -1
- package/dist/cjs/routes/user.routes.cjs +13 -13
- package/dist/cjs/routes/user.routes.cjs.map +1 -1
- package/dist/cjs/schemas/project.schema.cjs +2 -2
- package/dist/cjs/schemas/project.schema.cjs.map +1 -1
- package/dist/cjs/services/dictionary.service.cjs +0 -6
- package/dist/cjs/services/dictionary.service.cjs.map +1 -1
- package/dist/cjs/services/email.service.cjs +1 -1
- package/dist/cjs/services/email.service.cjs.map +1 -1
- package/dist/cjs/services/subscription.service.cjs +81 -2
- package/dist/cjs/services/subscription.service.cjs.map +1 -1
- package/dist/cjs/utils/AI/{askDocQuestion.cjs → askDocQuestion/askDocQuestion.cjs} +1 -1
- package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs.map +1 -0
- package/dist/{esm/utils/AI → cjs/utils/AI/askDocQuestion}/embeddings.json +4288 -4288
- package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/index.cjs +5 -3
- package/dist/cjs/utils/AI/auditDictionary/index.cjs.map +1 -0
- package/dist/cjs/utils/{auditDictionaryField → AI/auditDictionaryField}/index.cjs +5 -3
- package/dist/cjs/utils/AI/auditDictionaryField/index.cjs.map +1 -0
- package/dist/cjs/utils/{auditDictionaryMetadata → AI/auditDictionaryMetadata}/index.cjs +5 -3
- package/dist/cjs/utils/AI/auditDictionaryMetadata/index.cjs.map +1 -0
- package/dist/cjs/utils/AI/autocomplete/PROMPT.md +13 -0
- package/dist/cjs/utils/AI/autocomplete/index.cjs +73 -0
- package/dist/cjs/utils/AI/autocomplete/index.cjs.map +1 -0
- package/dist/cjs/utils/auditTag/index.cjs +4 -2
- package/dist/cjs/utils/auditTag/index.cjs.map +1 -1
- package/dist/cjs/utils/errors/errorCodes.cjs +26 -0
- package/dist/cjs/utils/errors/errorCodes.cjs.map +1 -1
- package/dist/cjs/utils/mongoDB/connectDB.cjs +10 -0
- package/dist/cjs/utils/mongoDB/connectDB.cjs.map +1 -1
- package/dist/cjs/webhooks/stripe.webhook.cjs +42 -0
- package/dist/cjs/webhooks/stripe.webhook.cjs.map +1 -1
- package/dist/esm/controllers/ai.controller.mjs +59 -42
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/dictionary.controller.mjs +1 -1
- package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
- package/dist/esm/controllers/organization.controller.mjs +18 -2
- package/dist/esm/controllers/organization.controller.mjs.map +1 -1
- package/dist/esm/controllers/project.controller.mjs +1 -0
- package/dist/esm/controllers/project.controller.mjs.map +1 -1
- package/dist/esm/controllers/sessionAuth.controller.mjs +6 -12
- package/dist/esm/controllers/sessionAuth.controller.mjs.map +1 -1
- package/dist/esm/controllers/stripe.controller.mjs +30 -10
- package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
- package/dist/esm/controllers/tag.controller.mjs +0 -7
- package/dist/esm/controllers/tag.controller.mjs.map +1 -1
- package/dist/esm/controllers/user.controller.mjs +2 -2
- package/dist/esm/controllers/user.controller.mjs.map +1 -1
- package/dist/esm/emails/InviteUserEmail.mjs +3 -3
- package/dist/esm/emails/InviteUserEmail.mjs.map +1 -1
- package/dist/esm/emails/PasswordChangeConfirmation.mjs +3 -3
- package/dist/esm/emails/PasswordChangeConfirmation.mjs.map +1 -1
- package/dist/esm/emails/ResetUserPassword.mjs +3 -3
- package/dist/esm/emails/ResetUserPassword.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentCancellation.mjs +19 -3
- package/dist/esm/emails/SubscriptionPaymentCancellation.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentError.mjs +19 -3
- package/dist/esm/emails/SubscriptionPaymentError.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentSuccess.mjs +19 -3
- package/dist/esm/emails/SubscriptionPaymentSuccess.mjs.map +1 -1
- package/dist/esm/emails/ValidateUserEmail.mjs +3 -3
- package/dist/esm/emails/ValidateUserEmail.mjs.map +1 -1
- package/dist/esm/emails/Welcome.mjs +3 -3
- package/dist/esm/emails/Welcome.mjs.map +1 -1
- package/dist/esm/export.mjs +10 -10
- package/dist/esm/export.mjs.map +1 -1
- package/dist/esm/routes/ai.routes.mjs +22 -15
- package/dist/esm/routes/ai.routes.mjs.map +1 -1
- package/dist/esm/routes/dictionary.routes.mjs +14 -11
- package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
- package/dist/esm/routes/event-listener.routes.mjs +4 -4
- package/dist/esm/routes/event-listener.routes.mjs.map +1 -1
- package/dist/esm/routes/organization.routes.mjs +13 -13
- package/dist/esm/routes/organization.routes.mjs.map +1 -1
- package/dist/esm/routes/project.routes.mjs +28 -16
- package/dist/esm/routes/project.routes.mjs.map +1 -1
- package/dist/esm/routes/sessionAuth.routes.mjs +37 -34
- package/dist/esm/routes/sessionAuth.routes.mjs.map +1 -1
- package/dist/esm/routes/stripe.routes.mjs +22 -9
- package/dist/esm/routes/stripe.routes.mjs.map +1 -1
- package/dist/esm/routes/tags.routes.mjs +9 -9
- package/dist/esm/routes/tags.routes.mjs.map +1 -1
- package/dist/esm/routes/user.routes.mjs +11 -11
- package/dist/esm/routes/user.routes.mjs.map +1 -1
- package/dist/esm/schemas/project.schema.mjs +2 -2
- package/dist/esm/schemas/project.schema.mjs.map +1 -1
- package/dist/esm/services/dictionary.service.mjs +0 -6
- package/dist/esm/services/dictionary.service.mjs.map +1 -1
- package/dist/esm/services/email.service.mjs +1 -1
- package/dist/esm/services/email.service.mjs.map +1 -1
- package/dist/esm/services/subscription.service.mjs +78 -1
- package/dist/esm/services/subscription.service.mjs.map +1 -1
- package/dist/esm/utils/AI/{askDocQuestion.mjs → askDocQuestion/askDocQuestion.mjs} +1 -1
- package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -0
- package/dist/{cjs/utils/AI → esm/utils/AI/askDocQuestion}/embeddings.json +4288 -4288
- package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/index.mjs +5 -3
- package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -0
- package/dist/esm/utils/{auditDictionaryField → AI/auditDictionaryField}/index.mjs +5 -3
- package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -0
- package/dist/esm/utils/{auditDictionaryMetadata → AI/auditDictionaryMetadata}/index.mjs +5 -3
- package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -0
- package/dist/esm/utils/AI/autocomplete/PROMPT.md +13 -0
- package/dist/esm/utils/AI/autocomplete/index.mjs +48 -0
- package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -0
- package/dist/esm/utils/auditTag/index.mjs +4 -2
- package/dist/esm/utils/auditTag/index.mjs.map +1 -1
- package/dist/esm/utils/errors/errorCodes.mjs +26 -0
- package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
- package/dist/esm/utils/mongoDB/connectDB.mjs +10 -0
- package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
- package/dist/esm/webhooks/stripe.webhook.mjs +32 -0
- package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
- package/dist/types/controllers/ai.controller.d.ts +17 -2
- package/dist/types/controllers/ai.controller.d.ts.map +1 -1
- package/dist/types/controllers/organization.controller.d.ts.map +1 -1
- package/dist/types/controllers/project.controller.d.ts.map +1 -1
- package/dist/types/controllers/sessionAuth.controller.d.ts +4 -3
- package/dist/types/controllers/sessionAuth.controller.d.ts.map +1 -1
- package/dist/types/controllers/stripe.controller.d.ts +15 -7
- package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
- package/dist/types/controllers/tag.controller.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -3
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -3
- package/dist/types/emails/SubscriptionPaymentError.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -3
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts.map +1 -1
- package/dist/types/export.d.ts +5 -5
- package/dist/types/export.d.ts.map +1 -1
- package/dist/types/routes/ai.routes.d.ts +6 -1
- package/dist/types/routes/ai.routes.d.ts.map +1 -1
- package/dist/types/routes/dictionary.routes.d.ts +4 -4
- package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
- package/dist/types/routes/event-listener.routes.d.ts +1 -1
- package/dist/types/routes/event-listener.routes.d.ts.map +1 -1
- package/dist/types/routes/organization.routes.d.ts +5 -5
- package/dist/types/routes/organization.routes.d.ts.map +1 -1
- package/dist/types/routes/project.routes.d.ts +5 -5
- package/dist/types/routes/project.routes.d.ts.map +1 -1
- package/dist/types/routes/sessionAuth.routes.d.ts +4 -7
- package/dist/types/routes/sessionAuth.routes.d.ts.map +1 -1
- package/dist/types/routes/stripe.routes.d.ts +6 -1
- package/dist/types/routes/stripe.routes.d.ts.map +1 -1
- package/dist/types/routes/tags.routes.d.ts +3 -3
- package/dist/types/routes/tags.routes.d.ts.map +1 -1
- package/dist/types/routes/user.routes.d.ts +4 -4
- package/dist/types/routes/user.routes.d.ts.map +1 -1
- package/dist/types/services/dictionary.service.d.ts.map +1 -1
- package/dist/types/services/subscription.service.d.ts +9 -0
- package/dist/types/services/subscription.service.d.ts.map +1 -1
- package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -0
- package/dist/types/utils/{auditDictionary → AI/auditDictionary}/index.d.ts +8 -5
- package/dist/types/utils/AI/auditDictionary/index.d.ts.map +1 -0
- package/dist/types/utils/{auditDictionaryField → AI/auditDictionaryField}/index.d.ts +8 -5
- package/dist/types/utils/AI/auditDictionaryField/index.d.ts.map +1 -0
- package/dist/types/utils/{auditDictionaryMetadata → AI/auditDictionaryMetadata}/index.d.ts +8 -5
- package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts.map +1 -0
- package/dist/types/utils/AI/autocomplete/index.d.ts +22 -0
- package/dist/types/utils/AI/autocomplete/index.d.ts.map +1 -0
- package/dist/types/utils/auditTag/index.d.ts +7 -4
- package/dist/types/utils/auditTag/index.d.ts.map +1 -1
- package/dist/types/utils/errors/errorCodes.d.ts +26 -0
- package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
- package/dist/types/utils/mongoDB/connectDB.d.ts.map +1 -1
- package/dist/types/webhooks/stripe.webhook.d.ts.map +1 -1
- package/package.json +10 -9
- package/dist/cjs/utils/AI/askDocQuestion.cjs.map +0 -1
- package/dist/cjs/utils/auditDictionary/index.cjs.map +0 -1
- package/dist/cjs/utils/auditDictionaryField/index.cjs.map +0 -1
- package/dist/cjs/utils/auditDictionaryMetadata/index.cjs.map +0 -1
- package/dist/esm/utils/AI/askDocQuestion.mjs.map +0 -1
- package/dist/esm/utils/auditDictionary/index.mjs.map +0 -1
- package/dist/esm/utils/auditDictionaryField/index.mjs.map +0 -1
- package/dist/esm/utils/auditDictionaryMetadata/index.mjs.map +0 -1
- package/dist/types/utils/AI/askDocQuestion.d.ts.map +0 -1
- package/dist/types/utils/auditDictionary/index.d.ts.map +0 -1
- package/dist/types/utils/auditDictionaryField/index.d.ts.map +0 -1
- package/dist/types/utils/auditDictionaryMetadata/index.d.ts.map +0 -1
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/CJS_FORMAT.md +0 -0
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/JSON_FORMAT.md +0 -0
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/JSX_FORMAT.md +0 -0
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/MJS_FORMAT.md +0 -0
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/PROMPT.md +0 -0
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/TSX_FORMAT.md +0 -0
- /package/dist/cjs/utils/{auditDictionary → AI/auditDictionary}/TS_FORMAT.md +0 -0
- /package/dist/cjs/utils/{auditDictionaryField → AI/auditDictionaryField}/PROMPT.md +0 -0
- /package/dist/cjs/utils/{auditDictionaryMetadata → AI/auditDictionaryMetadata}/PROMPT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/CJS_FORMAT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/JSON_FORMAT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/JSX_FORMAT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/MJS_FORMAT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/PROMPT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/TSX_FORMAT.md +0 -0
- /package/dist/esm/utils/{auditDictionary → AI/auditDictionary}/TS_FORMAT.md +0 -0
- /package/dist/esm/utils/{auditDictionaryField → AI/auditDictionaryField}/PROMPT.md +0 -0
- /package/dist/esm/utils/{auditDictionaryMetadata → AI/auditDictionaryMetadata}/PROMPT.md +0 -0
- /package/dist/types/utils/AI/{askDocQuestion.d.ts → askDocQuestion/askDocQuestion.d.ts} +0 -0
|
@@ -18,15 +18,15 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var tags_routes_exports = {};
|
|
20
20
|
__export(tags_routes_exports, {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
getTagRoutes: () => getTagRoutes,
|
|
22
|
+
tagRouter: () => tagRouter
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(tags_routes_exports);
|
|
25
25
|
var import_tag = require('./../controllers/tag.controller.cjs');
|
|
26
26
|
var import_express = require("express");
|
|
27
27
|
const tagRouter = (0, import_express.Router)();
|
|
28
|
-
const baseURL = `${process.env.BACKEND_URL}/api/tag`;
|
|
29
|
-
const
|
|
28
|
+
const baseURL = () => `${process.env.BACKEND_URL}/api/tag`;
|
|
29
|
+
const getTagRoutes = () => ({
|
|
30
30
|
getTags: {
|
|
31
31
|
urlModel: "/",
|
|
32
32
|
url: baseURL,
|
|
@@ -47,14 +47,14 @@ const tagRoutes = {
|
|
|
47
47
|
url: ({ tagId }) => `${baseURL}/${tagId}`,
|
|
48
48
|
method: "DELETE"
|
|
49
49
|
}
|
|
50
|
-
};
|
|
51
|
-
tagRouter.get(
|
|
52
|
-
tagRouter.post(
|
|
53
|
-
tagRouter.put(
|
|
54
|
-
tagRouter.delete(
|
|
50
|
+
});
|
|
51
|
+
tagRouter.get(getTagRoutes().getTags.urlModel, import_tag.getTags);
|
|
52
|
+
tagRouter.post(getTagRoutes().addTag.urlModel, import_tag.addTag);
|
|
53
|
+
tagRouter.put(getTagRoutes().updateTag.urlModel, import_tag.updateTag);
|
|
54
|
+
tagRouter.delete(getTagRoutes().deleteTag.urlModel, import_tag.deleteTag);
|
|
55
55
|
// Annotate the CommonJS export names for ESM import in node:
|
|
56
56
|
0 && (module.exports = {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
getTagRoutes,
|
|
58
|
+
tagRouter
|
|
59
59
|
});
|
|
60
60
|
//# sourceMappingURL=tags.routes.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/routes/tags.routes.ts"],"sourcesContent":["import {\n addTag,\n deleteTag,\n getTags,\n updateTag,\n} from '@controllers/tag.controller';\nimport { Router } from 'express';\nimport type { Routes } from '@/types/Routes';\n\nexport const tagRouter: Router = Router();\n\nconst baseURL = `${process.env.BACKEND_URL}/api/tag`;\n\nexport const
|
|
1
|
+
{"version":3,"sources":["../../../src/routes/tags.routes.ts"],"sourcesContent":["import {\n addTag,\n deleteTag,\n getTags,\n updateTag,\n} from '@controllers/tag.controller';\nimport { Router } from 'express';\nimport type { Routes } from '@/types/Routes';\n\nexport const tagRouter: Router = Router();\n\nconst baseURL = () => `${process.env.BACKEND_URL}/api/tag`;\n\nexport const getTagRoutes = () =>\n ({\n getTags: {\n urlModel: '/',\n url: baseURL,\n method: 'GET',\n },\n addTag: {\n urlModel: '/',\n url: baseURL,\n method: 'POST',\n },\n updateTag: {\n urlModel: '/:tagId',\n url: ({ tagId }: { tagId: string }) => `${baseURL}/${tagId}`,\n method: 'PUT',\n },\n deleteTag: {\n urlModel: '/:tagId',\n url: ({ tagId }: { tagId: string }) => `${baseURL}/${tagId}`,\n method: 'DELETE',\n },\n }) satisfies Routes;\n\ntagRouter.get(getTagRoutes().getTags.urlModel, getTags);\n\ntagRouter.post(getTagRoutes().addTag.urlModel, addTag);\ntagRouter.put(getTagRoutes().updateTag.urlModel, updateTag);\ntagRouter.delete(getTagRoutes().deleteTag.urlModel, deleteTag);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAKO;AACP,qBAAuB;AAGhB,MAAM,gBAAoB,uBAAO;AAExC,MAAM,UAAU,MAAM,GAAG,QAAQ,IAAI,WAAW;AAEzC,MAAM,eAAe,OACzB;AAAA,EACC,SAAS;AAAA,IACP,UAAU;AAAA,IACV,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,UAAU;AAAA,IACV,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACT,UAAU;AAAA,IACV,KAAK,CAAC,EAAE,MAAM,MAAyB,GAAG,OAAO,IAAI,KAAK;AAAA,IAC1D,QAAQ;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACT,UAAU;AAAA,IACV,KAAK,CAAC,EAAE,MAAM,MAAyB,GAAG,OAAO,IAAI,KAAK;AAAA,IAC1D,QAAQ;AAAA,EACV;AACF;AAEF,UAAU,IAAI,aAAa,EAAE,QAAQ,UAAU,kBAAO;AAEtD,UAAU,KAAK,aAAa,EAAE,OAAO,UAAU,iBAAM;AACrD,UAAU,IAAI,aAAa,EAAE,UAAU,UAAU,oBAAS;AAC1D,UAAU,OAAO,aAAa,EAAE,UAAU,UAAU,oBAAS;","names":[]}
|
|
@@ -18,15 +18,15 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var user_routes_exports = {};
|
|
20
20
|
__export(user_routes_exports, {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
getUserRoutes: () => getUserRoutes,
|
|
22
|
+
userRouter: () => userRouter
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(user_routes_exports);
|
|
25
25
|
var import_user = require('./../controllers/user.controller.cjs');
|
|
26
26
|
var import_express = require("express");
|
|
27
27
|
const userRouter = (0, import_express.Router)();
|
|
28
|
-
const baseURL = `${process.env.BACKEND_URL}/api/user`;
|
|
29
|
-
const
|
|
28
|
+
const baseURL = () => `${process.env.BACKEND_URL}/api/user`;
|
|
29
|
+
const getUserRoutes = () => ({
|
|
30
30
|
getUsers: {
|
|
31
31
|
urlModel: "/",
|
|
32
32
|
url: baseURL,
|
|
@@ -60,16 +60,16 @@ const userRoutes = {
|
|
|
60
60
|
}) => `${baseURL}/account/${provider}/${providerAccountId}`,
|
|
61
61
|
method: "GET"
|
|
62
62
|
}
|
|
63
|
-
};
|
|
64
|
-
userRouter.get(
|
|
65
|
-
userRouter.put(
|
|
66
|
-
userRouter.post(
|
|
67
|
-
userRouter.get(
|
|
68
|
-
userRouter.get(
|
|
69
|
-
userRouter.get(
|
|
63
|
+
});
|
|
64
|
+
userRouter.get(getUserRoutes().getUsers.urlModel, import_user.getUsers);
|
|
65
|
+
userRouter.put(getUserRoutes().updateUser.urlModel, import_user.updateUser);
|
|
66
|
+
userRouter.post(getUserRoutes().createUser.urlModel, import_user.createUser);
|
|
67
|
+
userRouter.get(getUserRoutes().getUserById.urlModel, import_user.getUserById);
|
|
68
|
+
userRouter.get(getUserRoutes().getUserByEmail.urlModel, import_user.getUserByEmail);
|
|
69
|
+
userRouter.get(getUserRoutes().getUserByAccount.urlModel, import_user.getUserByAccount);
|
|
70
70
|
// Annotate the CommonJS export names for ESM import in node:
|
|
71
71
|
0 && (module.exports = {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
getUserRoutes,
|
|
73
|
+
userRouter
|
|
74
74
|
});
|
|
75
75
|
//# sourceMappingURL=user.routes.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/routes/user.routes.ts"],"sourcesContent":["import {\n getUsers,\n updateUser,\n getUserByEmail,\n getUserById,\n createUser,\n getUserByAccount,\n} from '@controllers/user.controller';\nimport { Router } from 'express';\nimport type { Routes } from '@/types/Routes';\n\nexport const userRouter: Router = Router();\n\nconst baseURL = `${process.env.BACKEND_URL}/api/user`;\n\nexport const
|
|
1
|
+
{"version":3,"sources":["../../../src/routes/user.routes.ts"],"sourcesContent":["import {\n getUsers,\n updateUser,\n getUserByEmail,\n getUserById,\n createUser,\n getUserByAccount,\n} from '@controllers/user.controller';\nimport { Router } from 'express';\nimport type { Routes } from '@/types/Routes';\n\nexport const userRouter: Router = Router();\n\nconst baseURL = () => `${process.env.BACKEND_URL}/api/user`;\n\nexport const getUserRoutes = () =>\n ({\n getUsers: {\n urlModel: '/',\n url: baseURL,\n method: 'GET',\n },\n updateUser: {\n urlModel: '/',\n url: baseURL,\n method: 'PUT',\n },\n createUser: {\n urlModel: '/',\n url: baseURL,\n method: 'POST',\n },\n getUserById: {\n urlModel: '/:userId',\n url: ({ userId }: { userId: string }) => `${baseURL}/${userId}`,\n method: 'GET',\n },\n getUserByEmail: {\n urlModel: '/email/:email',\n url: ({ email }: { email: string }) => `${baseURL}/email/${email}`,\n method: 'GET',\n },\n getUserByAccount: {\n urlModel: '/account/:provider/:providerAccountId',\n url: ({\n provider,\n providerAccountId,\n }: {\n provider: string;\n providerAccountId: string;\n }) => `${baseURL}/account/${provider}/${providerAccountId}`,\n method: 'GET',\n },\n }) satisfies Routes;\n\nuserRouter.get(getUserRoutes().getUsers.urlModel, getUsers);\nuserRouter.put(getUserRoutes().updateUser.urlModel, updateUser);\nuserRouter.post(getUserRoutes().createUser.urlModel, createUser);\nuserRouter.get(getUserRoutes().getUserById.urlModel, getUserById);\nuserRouter.get(getUserRoutes().getUserByEmail.urlModel, getUserByEmail);\nuserRouter.get(getUserRoutes().getUserByAccount.urlModel, getUserByAccount);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAOO;AACP,qBAAuB;AAGhB,MAAM,iBAAqB,uBAAO;AAEzC,MAAM,UAAU,MAAM,GAAG,QAAQ,IAAI,WAAW;AAEzC,MAAM,gBAAgB,OAC1B;AAAA,EACC,UAAU;AAAA,IACR,UAAU;AAAA,IACV,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,YAAY;AAAA,IACV,UAAU;AAAA,IACV,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,YAAY;AAAA,IACV,UAAU;AAAA,IACV,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,aAAa;AAAA,IACX,UAAU;AAAA,IACV,KAAK,CAAC,EAAE,OAAO,MAA0B,GAAG,OAAO,IAAI,MAAM;AAAA,IAC7D,QAAQ;AAAA,EACV;AAAA,EACA,gBAAgB;AAAA,IACd,UAAU;AAAA,IACV,KAAK,CAAC,EAAE,MAAM,MAAyB,GAAG,OAAO,UAAU,KAAK;AAAA,IAChE,QAAQ;AAAA,EACV;AAAA,EACA,kBAAkB;AAAA,IAChB,UAAU;AAAA,IACV,KAAK,CAAC;AAAA,MACJ;AAAA,MACA;AAAA,IACF,MAGM,GAAG,OAAO,YAAY,QAAQ,IAAI,iBAAiB;AAAA,IACzD,QAAQ;AAAA,EACV;AACF;AAEF,WAAW,IAAI,cAAc,EAAE,SAAS,UAAU,oBAAQ;AAC1D,WAAW,IAAI,cAAc,EAAE,WAAW,UAAU,sBAAU;AAC9D,WAAW,KAAK,cAAc,EAAE,WAAW,UAAU,sBAAU;AAC/D,WAAW,IAAI,cAAc,EAAE,YAAY,UAAU,uBAAW;AAChE,WAAW,IAAI,cAAc,EAAE,eAAe,UAAU,0BAAc;AACtE,WAAW,IAAI,cAAc,EAAE,iBAAiB,UAAU,4BAAgB;","names":[]}
|
|
@@ -37,12 +37,12 @@ const TokenRightsSchema = new import_mongoose.Schema({
|
|
|
37
37
|
});
|
|
38
38
|
const oAuth2AccessSchema = new import_mongoose.Schema(
|
|
39
39
|
{
|
|
40
|
-
clientId: { type: String, required: true
|
|
40
|
+
clientId: { type: String, required: true },
|
|
41
41
|
clientSecret: { type: String, required: true },
|
|
42
42
|
userId: { type: import_mongoose.Schema.Types.ObjectId, ref: "User", required: true },
|
|
43
43
|
name: { type: String, required: true },
|
|
44
44
|
expiresAt: { type: Date },
|
|
45
|
-
accessToken: { type: [String], required: true },
|
|
45
|
+
accessToken: { type: [String], required: true, default: [] },
|
|
46
46
|
rights: { type: TokenRightsSchema, required: true }
|
|
47
47
|
},
|
|
48
48
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/schemas/project.schema.ts"],"sourcesContent":["import { Locales } from '@intlayer/config';\nimport {\n NAME_MIN_LENGTH,\n NAME_MAX_LENGTH,\n MEMBERS_MIN_LENGTH,\n} from '@utils/validation/validateProject';\nimport { Schema } from 'mongoose';\nimport type { Project, Rights, TokenRights } from '@/types/project.types';\n\nconst RightsSchema = new Schema<Rights>({\n read: { type: Boolean, required: true },\n write: { type: Boolean, required: true },\n admin: { type: Boolean, required: true },\n});\n\nexport const TokenRightsSchema = new Schema<TokenRights>({\n dictionary: { type: RightsSchema, required: true },\n project: { type: RightsSchema, required: true },\n organization: { type: RightsSchema, required: true },\n});\n\n// Define the oAuth2Access subdocument schema with timestamps\nconst oAuth2AccessSchema = new Schema(\n {\n clientId: { type: String, required: true
|
|
1
|
+
{"version":3,"sources":["../../../src/schemas/project.schema.ts"],"sourcesContent":["import { Locales } from '@intlayer/config';\nimport {\n NAME_MIN_LENGTH,\n NAME_MAX_LENGTH,\n MEMBERS_MIN_LENGTH,\n} from '@utils/validation/validateProject';\nimport { Schema } from 'mongoose';\nimport type { Project, Rights, TokenRights } from '@/types/project.types';\n\nconst RightsSchema = new Schema<Rights>({\n read: { type: Boolean, required: true },\n write: { type: Boolean, required: true },\n admin: { type: Boolean, required: true },\n});\n\nexport const TokenRightsSchema = new Schema<TokenRights>({\n dictionary: { type: RightsSchema, required: true },\n project: { type: RightsSchema, required: true },\n organization: { type: RightsSchema, required: true },\n});\n\n// Define the oAuth2Access subdocument schema with timestamps\nconst oAuth2AccessSchema = new Schema(\n {\n clientId: { type: String, required: true },\n clientSecret: { type: String, required: true },\n userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },\n name: { type: String, required: true },\n expiresAt: { type: Date },\n accessToken: { type: [String], required: true, default: [] },\n rights: { type: TokenRightsSchema, required: true },\n },\n {\n timestamps: true,\n }\n);\n\nconst projectConfigSchema = new Schema<Project['configuration']>(\n {\n internationalization: {\n locales: {\n type: [String],\n enum: Object.values(Locales),\n required: true,\n },\n defaultLocale: {\n type: String,\n enum: Object.values(Locales),\n },\n },\n editor: {\n applicationURL: {\n type: String,\n },\n cmsURL: {\n type: String,\n },\n },\n },\n {\n _id: false, // Prevents the generation of an _id field for this subdocument\n }\n);\n\nexport const projectSchema = new Schema<Project>(\n {\n organizationId: {\n type: Schema.Types.ObjectId,\n ref: 'Organization',\n required: true,\n },\n name: {\n type: String,\n required: true,\n minlength: NAME_MIN_LENGTH,\n maxlength: NAME_MAX_LENGTH,\n },\n configuration: projectConfigSchema,\n oAuth2Access: [oAuth2AccessSchema],\n membersIds: {\n type: [Schema.Types.ObjectId],\n ref: 'User',\n required: true,\n minlength: MEMBERS_MIN_LENGTH,\n },\n adminsIds: {\n type: [Schema.Types.ObjectId],\n ref: 'User',\n required: true,\n minlength: MEMBERS_MIN_LENGTH,\n },\n creatorId: {\n type: Schema.Types.ObjectId,\n ref: 'User',\n required: true,\n },\n },\n {\n timestamps: true,\n }\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAwB;AACxB,6BAIO;AACP,sBAAuB;AAGvB,MAAM,eAAe,IAAI,uBAAe;AAAA,EACtC,MAAM,EAAE,MAAM,SAAS,UAAU,KAAK;AAAA,EACtC,OAAO,EAAE,MAAM,SAAS,UAAU,KAAK;AAAA,EACvC,OAAO,EAAE,MAAM,SAAS,UAAU,KAAK;AACzC,CAAC;AAEM,MAAM,oBAAoB,IAAI,uBAAoB;AAAA,EACvD,YAAY,EAAE,MAAM,cAAc,UAAU,KAAK;AAAA,EACjD,SAAS,EAAE,MAAM,cAAc,UAAU,KAAK;AAAA,EAC9C,cAAc,EAAE,MAAM,cAAc,UAAU,KAAK;AACrD,CAAC;AAGD,MAAM,qBAAqB,IAAI;AAAA,EAC7B;AAAA,IACE,UAAU,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,IACzC,cAAc,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,IAC7C,QAAQ,EAAE,MAAM,uBAAO,MAAM,UAAU,KAAK,QAAQ,UAAU,KAAK;AAAA,IACnE,MAAM,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,IACrC,WAAW,EAAE,MAAM,KAAK;AAAA,IACxB,aAAa,EAAE,MAAM,CAAC,MAAM,GAAG,UAAU,MAAM,SAAS,CAAC,EAAE;AAAA,IAC3D,QAAQ,EAAE,MAAM,mBAAmB,UAAU,KAAK;AAAA,EACpD;AAAA,EACA;AAAA,IACE,YAAY;AAAA,EACd;AACF;AAEA,MAAM,sBAAsB,IAAI;AAAA,EAC9B;AAAA,IACE,sBAAsB;AAAA,MACpB,SAAS;AAAA,QACP,MAAM,CAAC,MAAM;AAAA,QACb,MAAM,OAAO,OAAO,qBAAO;AAAA,QAC3B,UAAU;AAAA,MACZ;AAAA,MACA,eAAe;AAAA,QACb,MAAM;AAAA,QACN,MAAM,OAAO,OAAO,qBAAO;AAAA,MAC7B;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,gBAAgB;AAAA,QACd,MAAM;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,KAAK;AAAA;AAAA,EACP;AACF;AAEO,MAAM,gBAAgB,IAAI;AAAA,EAC/B;AAAA,IACE,gBAAgB;AAAA,MACd,MAAM,uBAAO,MAAM;AAAA,MACnB,KAAK;AAAA,MACL,UAAU;AAAA,IACZ;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,MACV,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,IACA,eAAe;AAAA,IACf,cAAc,CAAC,kBAAkB;AAAA,IACjC,YAAY;AAAA,MACV,MAAM,CAAC,uBAAO,MAAM,QAAQ;AAAA,MAC5B,KAAK;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,MAAM,CAAC,uBAAO,MAAM,QAAQ;AAAA,MAC5B,KAAK;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,MAAM,uBAAO,MAAM;AAAA,MACnB,KAAK;AAAA,MACL,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA;AAAA,IACE,YAAY;AAAA,EACd;AACF;","names":[]}
|
|
@@ -231,16 +231,10 @@ const updateDictionaryByKey = async (dictionaryKey, dictionary, projectId) => {
|
|
|
231
231
|
errors
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
|
-
const dd = dictionaryToUpdate;
|
|
235
|
-
if (dd.content instanceof Map) {
|
|
236
|
-
dd.content = Object.fromEntries(dd.content);
|
|
237
|
-
}
|
|
238
|
-
console.log("dictionaryToUpdate 2", dictionaryToUpdate);
|
|
239
234
|
const result = await import_dictionary.DictionaryModel.updateOne(
|
|
240
235
|
{ key: dictionaryKey, projectIds: projectId },
|
|
241
236
|
dictionaryToUpdate
|
|
242
237
|
);
|
|
243
|
-
console.log("result", result);
|
|
244
238
|
if (result.matchedCount === 0) {
|
|
245
239
|
throw new import_errors.GenericError("DICTIONARY_UPDATE_FAILED", { dictionaryKey });
|
|
246
240
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { DictionaryModel } from '@models/dictionary.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport type { ObjectId } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Skip for pagination\n { $skip: skip },\n\n // Stage 3: Limit the number of documents\n { $limit: limit },\n\n // Stage 4: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n\n // (Optional) Stage 5: Project the fields you want to include/exclude\n // For example, to exclude the entire 'content' field and keep only 'availableVersions'\n // {\n // $project: {\n // content: 0 // Exclude the 'content' field\n // }\n // }\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n console.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'availableVersions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: dictionaryId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesKeys = async (\n projectId: string | ObjectId\n): Promise<string[]> => {\n const dictionaries = await DictionaryModel.find({\n projectIds: projectId,\n }).select('key');\n\n return dictionaries.map((dictionary) => dictionary.key);\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['_id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\ntype GetExistingDictionaryResult = {\n existingDictionariesKey: string[];\n newDictionariesKey: string[];\n};\n\n/**\n * Gets the existing dictionaries from the provided list of keys.\n * @param dictionariesKeys - List of dictionary keys to check.\n * @param projectId - The ID of the project to check the dictionaries against.\n * @returns The existing dictionaries and the new dictionaries.\n */\nexport const getExistingDictionaryKey = async (\n dictionariesKeys: string[],\n projectId: string | ObjectId\n): Promise<GetExistingDictionaryResult> => {\n // Fetch dictionaries from the database where the key is in the provided list\n const existingDictionaries = await DictionaryModel.find({\n key: { $in: dictionariesKeys },\n projectIds: projectId,\n });\n\n // Map existing dictionaries to a LocalDictionary object\n const existingDictionariesKey: string[] = [];\n const newDictionariesKey: string[] = [];\n\n for (const key of dictionariesKeys) {\n const isDictionaryExist = existingDictionaries.some(\n (dictionary) => dictionary.key === key\n );\n\n if (isDictionaryExist) {\n existingDictionariesKey.push(key);\n } else {\n newDictionariesKey.push(key);\n }\n }\n\n return { existingDictionariesKey, newDictionariesKey };\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryKey,\n projectId,\n errors,\n });\n }\n\n const dd = dictionaryToUpdate;\n\n // Convert Map fields to plain objects\n if (dd.content instanceof Map) {\n dd.content = Object.fromEntries(dd.content) as any;\n }\n\n console.log('dictionaryToUpdate 2', dictionaryToUpdate);\n\n const result = await DictionaryModel.updateOne(\n { key: dictionaryKey, projectIds: projectId },\n dictionaryToUpdate\n );\n\n console.log('result', result);\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const updatedDictionary = await getDictionaryByKey(dictionaryKey, projectId);\n\n return updatedDictionary;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n const availableVersions = dictionary.availableVersions ?? [];\n\n // Get the current version from the version list, default to 'v1' if not present\n const currentVersion =\n availableVersions.length > 0\n ? availableVersions[availableVersions.length - 1]\n : 'v1';\n\n // Function to extract the numeric part of the version\n const getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n };\n\n // Start with the next version number\n let newNumber = getVersionNumber(currentVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (availableVersions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { DictionaryModel } from '@models/dictionary.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport type { ObjectId } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Skip for pagination\n { $skip: skip },\n\n // Stage 3: Limit the number of documents\n { $limit: limit },\n\n // Stage 4: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n\n // (Optional) Stage 5: Project the fields you want to include/exclude\n // For example, to exclude the entire 'content' field and keep only 'availableVersions'\n // {\n // $project: {\n // content: 0 // Exclude the 'content' field\n // }\n // }\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n console.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'availableVersions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: dictionaryId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesKeys = async (\n projectId: string | ObjectId\n): Promise<string[]> => {\n const dictionaries = await DictionaryModel.find({\n projectIds: projectId,\n }).select('key');\n\n return dictionaries.map((dictionary) => dictionary.key);\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['_id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'availableVersions' field\n {\n $addFields: {\n availableVersions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\ntype GetExistingDictionaryResult = {\n existingDictionariesKey: string[];\n newDictionariesKey: string[];\n};\n\n/**\n * Gets the existing dictionaries from the provided list of keys.\n * @param dictionariesKeys - List of dictionary keys to check.\n * @param projectId - The ID of the project to check the dictionaries against.\n * @returns The existing dictionaries and the new dictionaries.\n */\nexport const getExistingDictionaryKey = async (\n dictionariesKeys: string[],\n projectId: string | ObjectId\n): Promise<GetExistingDictionaryResult> => {\n // Fetch dictionaries from the database where the key is in the provided list\n const existingDictionaries = await DictionaryModel.find({\n key: { $in: dictionariesKeys },\n projectIds: projectId,\n });\n\n // Map existing dictionaries to a LocalDictionary object\n const existingDictionariesKey: string[] = [];\n const newDictionariesKey: string[] = [];\n\n for (const key of dictionariesKeys) {\n const isDictionaryExist = existingDictionaries.some(\n (dictionary) => dictionary.key === key\n );\n\n if (isDictionaryExist) {\n existingDictionariesKey.push(key);\n } else {\n newDictionariesKey.push(key);\n }\n }\n\n return { existingDictionariesKey, newDictionariesKey };\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, ['_id']);\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryKey,\n projectId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { key: dictionaryKey, projectIds: projectId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const updatedDictionary = await getDictionaryByKey(dictionaryKey, projectId);\n\n return updatedDictionary;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n const availableVersions = dictionary.availableVersions ?? [];\n\n // Get the current version from the version list, default to 'v1' if not present\n const currentVersion =\n availableVersions.length > 0\n ? availableVersions[availableVersions.length - 1]\n : 'v1';\n\n // Function to extract the numeric part of the version\n const getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n };\n\n // Start with the next version number\n let newNumber = getVersionNumber(currentVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (availableVersions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -174,7 +174,7 @@ const sendEmail = async ({
|
|
|
174
174
|
const EmailComponent = template;
|
|
175
175
|
const react = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmailComponent, { ...props });
|
|
176
176
|
await resend.emails.send({
|
|
177
|
-
from: "Intlayer <no-
|
|
177
|
+
from: "Intlayer <no-reply@intlayer.org>",
|
|
178
178
|
to,
|
|
179
179
|
subject: subject ?? baseSubject,
|
|
180
180
|
react
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/email.service.tsx"],"sourcesContent":["import {\n InviteUserEmailEN,\n InviteUserEmailFR,\n InviteUserEmailES,\n} from '@emails/InviteUserEmail';\nimport {\n PasswordChangeConfirmationEmailEN,\n PasswordChangeConfirmationEmailFR,\n PasswordChangeConfirmationEmailES,\n} from '@emails/PasswordChangeConfirmation';\nimport {\n ResetPasswordEmailEN,\n ResetPasswordEmailFR,\n ResetPasswordEmailES,\n} from '@emails/ResetUserPassword';\n\nimport {\n SubscriptionPaymentCancellationEN,\n SubscriptionPaymentCancellationFR,\n SubscriptionPaymentCancellationES,\n} from '@emails/SubscriptionPaymentCancellation';\nimport {\n SubscriptionPaymentErrorEN,\n SubscriptionPaymentErrorFR,\n SubscriptionPaymentErrorES,\n} from '@emails/SubscriptionPaymentError';\nimport {\n SubscriptionPaymentSuccessEN,\n SubscriptionPaymentSuccessFR,\n SubscriptionPaymentSuccessES,\n} from '@emails/SubscriptionPaymentSuccess';\nimport {\n ValidateUserEmailEN,\n ValidateUserEmailFR,\n ValidateUserEmailES,\n} from '@emails/ValidateUserEmail';\nimport {\n WelcomeEmailEN,\n WelcomeEmailFR,\n WelcomeEmailES,\n} from '@emails/Welcome';\n\nimport { logger } from '@logger';\nimport { t } from 'express-intlayer';\nimport type { Locales } from 'intlayer';\nimport type { ComponentProps, JSX } from 'react';\nimport { Resend } from 'resend';\n\ntype EmailComponentsType = (...props: any) => JSX.Element;\ntype EmailComponents = {\n [key: string]: {\n template: EmailComponentsType;\n subject: string;\n };\n};\n\nconst getEmailComponents = (locale?: Locales) =>\n ({\n invite: {\n template: t(\n {\n en: InviteUserEmailEN,\n fr: InviteUserEmailFR,\n es: InviteUserEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'You have been invited to join Intlayer',\n fr: 'Vous êtes invité à rejoindre Intlayer',\n es: 'Has sido invitado a unirte a Intlayer',\n },\n locale\n ),\n },\n validate: {\n template: t(\n {\n en: ValidateUserEmailEN,\n fr: ValidateUserEmailFR,\n es: ValidateUserEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Validate your email for Intlayer',\n fr: 'Validez votre email pour Intlayer',\n es: 'Valida tu correo electrónico para Intlayer',\n },\n locale\n ),\n },\n resetPassword: {\n template: t(\n {\n en: ResetPasswordEmailEN,\n fr: ResetPasswordEmailFR,\n es: ResetPasswordEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Reset your password for Intlayer',\n fr: 'Réinitialisez votre mot de passe pour Intlayer',\n es: 'Restablece tu contraseña para Intlayer',\n },\n locale\n ),\n },\n welcome: {\n template: t(\n {\n en: WelcomeEmailEN,\n fr: WelcomeEmailFR,\n es: WelcomeEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Welcome to Intlayer!',\n fr: 'Bienvenue chez Intlayer!',\n es: '¡Bienvenido a Intlayer!',\n },\n locale\n ),\n },\n passwordChangeConfirmation: {\n template: t(\n {\n en: PasswordChangeConfirmationEmailEN,\n fr: PasswordChangeConfirmationEmailFR,\n es: PasswordChangeConfirmationEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Your Intlayer password has been changed',\n fr: 'Votre mot de passe Intlayer a été modifié',\n es: 'Tu contraseña de Intlayer ha sido cambiada',\n },\n locale\n ),\n },\n subscriptionPaymentSuccess: {\n template: t({\n en: SubscriptionPaymentSuccessEN,\n fr: SubscriptionPaymentSuccessFR,\n es: SubscriptionPaymentSuccessES,\n }),\n subject: t({\n en: 'Your payment for Intlayer subscription is confirmed',\n fr: \"Votre paiement pour l'abonnement Intlayer est confirmé\",\n es: 'Tu pago por la suscripción de Intlayer ha sido confirmado',\n }),\n },\n subscriptionPaymentCancellation: {\n template: t({\n en: SubscriptionPaymentCancellationEN,\n fr: SubscriptionPaymentCancellationFR,\n es: SubscriptionPaymentCancellationES,\n }),\n subject: t({\n en: 'Your Intlayer subscription has been canceled',\n fr: 'Votre abonnement Intlayer a été annulé',\n es: 'Tu suscripción de Intlayer ha sido cancelada',\n }),\n },\n subscriptionPaymentError: {\n template: t({\n en: SubscriptionPaymentErrorEN,\n fr: SubscriptionPaymentErrorFR,\n es: SubscriptionPaymentErrorES,\n }),\n subject: t({\n en: 'There was an issue with your Intlayer subscription payment',\n fr: \"Un problème est survenu avec votre paiement pour l'abonnement Intlayer\",\n es: 'Hubo un problema con el pago de tu suscripción de Intlayer',\n }),\n },\n }) satisfies EmailComponents;\n\ntype EmailType = keyof ReturnType<typeof getEmailComponents>;\n\nexport type SendEmailProps<T extends EmailType> = {\n type: T;\n to: string;\n subject?: string;\n locale?: Locales;\n} & ComponentProps<ReturnType<typeof getEmailComponents>[T]['template']>;\n\nexport const sendEmail = async <T extends EmailType>({\n type,\n to,\n subject,\n locale,\n ...props\n}: SendEmailProps<T>) => {\n const resend = new Resend(process.env.RESEND_API_KEY);\n\n const emailComponents = getEmailComponents(locale);\n\n const { template, subject: baseSubject } = emailComponents[type];\n\n type EmailComponentType = (typeof emailComponents)[T]['template'];\n\n const EmailComponent: EmailComponentType = template;\n\n const react = <EmailComponent {...(props as any)} />;\n\n await resend.emails\n .send({\n from: 'Intlayer <no-
|
|
1
|
+
{"version":3,"sources":["../../../src/services/email.service.tsx"],"sourcesContent":["import {\n InviteUserEmailEN,\n InviteUserEmailFR,\n InviteUserEmailES,\n} from '@emails/InviteUserEmail';\nimport {\n PasswordChangeConfirmationEmailEN,\n PasswordChangeConfirmationEmailFR,\n PasswordChangeConfirmationEmailES,\n} from '@emails/PasswordChangeConfirmation';\nimport {\n ResetPasswordEmailEN,\n ResetPasswordEmailFR,\n ResetPasswordEmailES,\n} from '@emails/ResetUserPassword';\n\nimport {\n SubscriptionPaymentCancellationEN,\n SubscriptionPaymentCancellationFR,\n SubscriptionPaymentCancellationES,\n} from '@emails/SubscriptionPaymentCancellation';\nimport {\n SubscriptionPaymentErrorEN,\n SubscriptionPaymentErrorFR,\n SubscriptionPaymentErrorES,\n} from '@emails/SubscriptionPaymentError';\nimport {\n SubscriptionPaymentSuccessEN,\n SubscriptionPaymentSuccessFR,\n SubscriptionPaymentSuccessES,\n} from '@emails/SubscriptionPaymentSuccess';\nimport {\n ValidateUserEmailEN,\n ValidateUserEmailFR,\n ValidateUserEmailES,\n} from '@emails/ValidateUserEmail';\nimport {\n WelcomeEmailEN,\n WelcomeEmailFR,\n WelcomeEmailES,\n} from '@emails/Welcome';\n\nimport { logger } from '@logger';\nimport { t } from 'express-intlayer';\nimport type { Locales } from 'intlayer';\nimport type { ComponentProps, JSX } from 'react';\nimport { Resend } from 'resend';\n\ntype EmailComponentsType = (...props: any) => JSX.Element;\ntype EmailComponents = {\n [key: string]: {\n template: EmailComponentsType;\n subject: string;\n };\n};\n\nconst getEmailComponents = (locale?: Locales) =>\n ({\n invite: {\n template: t(\n {\n en: InviteUserEmailEN,\n fr: InviteUserEmailFR,\n es: InviteUserEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'You have been invited to join Intlayer',\n fr: 'Vous êtes invité à rejoindre Intlayer',\n es: 'Has sido invitado a unirte a Intlayer',\n },\n locale\n ),\n },\n validate: {\n template: t(\n {\n en: ValidateUserEmailEN,\n fr: ValidateUserEmailFR,\n es: ValidateUserEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Validate your email for Intlayer',\n fr: 'Validez votre email pour Intlayer',\n es: 'Valida tu correo electrónico para Intlayer',\n },\n locale\n ),\n },\n resetPassword: {\n template: t(\n {\n en: ResetPasswordEmailEN,\n fr: ResetPasswordEmailFR,\n es: ResetPasswordEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Reset your password for Intlayer',\n fr: 'Réinitialisez votre mot de passe pour Intlayer',\n es: 'Restablece tu contraseña para Intlayer',\n },\n locale\n ),\n },\n welcome: {\n template: t(\n {\n en: WelcomeEmailEN,\n fr: WelcomeEmailFR,\n es: WelcomeEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Welcome to Intlayer!',\n fr: 'Bienvenue chez Intlayer!',\n es: '¡Bienvenido a Intlayer!',\n },\n locale\n ),\n },\n passwordChangeConfirmation: {\n template: t(\n {\n en: PasswordChangeConfirmationEmailEN,\n fr: PasswordChangeConfirmationEmailFR,\n es: PasswordChangeConfirmationEmailES,\n },\n locale\n ),\n subject: t(\n {\n en: 'Your Intlayer password has been changed',\n fr: 'Votre mot de passe Intlayer a été modifié',\n es: 'Tu contraseña de Intlayer ha sido cambiada',\n },\n locale\n ),\n },\n subscriptionPaymentSuccess: {\n template: t({\n en: SubscriptionPaymentSuccessEN,\n fr: SubscriptionPaymentSuccessFR,\n es: SubscriptionPaymentSuccessES,\n }),\n subject: t({\n en: 'Your payment for Intlayer subscription is confirmed',\n fr: \"Votre paiement pour l'abonnement Intlayer est confirmé\",\n es: 'Tu pago por la suscripción de Intlayer ha sido confirmado',\n }),\n },\n subscriptionPaymentCancellation: {\n template: t({\n en: SubscriptionPaymentCancellationEN,\n fr: SubscriptionPaymentCancellationFR,\n es: SubscriptionPaymentCancellationES,\n }),\n subject: t({\n en: 'Your Intlayer subscription has been canceled',\n fr: 'Votre abonnement Intlayer a été annulé',\n es: 'Tu suscripción de Intlayer ha sido cancelada',\n }),\n },\n subscriptionPaymentError: {\n template: t({\n en: SubscriptionPaymentErrorEN,\n fr: SubscriptionPaymentErrorFR,\n es: SubscriptionPaymentErrorES,\n }),\n subject: t({\n en: 'There was an issue with your Intlayer subscription payment',\n fr: \"Un problème est survenu avec votre paiement pour l'abonnement Intlayer\",\n es: 'Hubo un problema con el pago de tu suscripción de Intlayer',\n }),\n },\n }) satisfies EmailComponents;\n\ntype EmailType = keyof ReturnType<typeof getEmailComponents>;\n\nexport type SendEmailProps<T extends EmailType> = {\n type: T;\n to: string;\n subject?: string;\n locale?: Locales;\n} & ComponentProps<ReturnType<typeof getEmailComponents>[T]['template']>;\n\nexport const sendEmail = async <T extends EmailType>({\n type,\n to,\n subject,\n locale,\n ...props\n}: SendEmailProps<T>) => {\n const resend = new Resend(process.env.RESEND_API_KEY);\n\n const emailComponents = getEmailComponents(locale);\n\n const { template, subject: baseSubject } = emailComponents[type];\n\n type EmailComponentType = (typeof emailComponents)[T]['template'];\n\n const EmailComponent: EmailComponentType = template;\n\n const react = <EmailComponent {...(props as any)} />;\n\n await resend.emails\n .send({\n from: 'Intlayer <no-reply@intlayer.org>',\n to,\n subject: subject ?? baseSubject,\n react,\n })\n .catch((err) => logger.error(err));\n\n logger.info(`Email sent ${type} to ${to}`);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -30,7 +30,9 @@ var subscription_service_exports = {};
|
|
|
30
30
|
__export(subscription_service_exports, {
|
|
31
31
|
addOrUpdateSubscription: () => addOrUpdateSubscription,
|
|
32
32
|
cancelSubscription: () => cancelSubscription,
|
|
33
|
-
changeSubscriptionStatus: () => changeSubscriptionStatus
|
|
33
|
+
changeSubscriptionStatus: () => changeSubscriptionStatus,
|
|
34
|
+
getCouponId: () => getCouponId,
|
|
35
|
+
getPricing: () => getPricing
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(subscription_service_exports);
|
|
36
38
|
var import_logger = require('./../logger/index.cjs');
|
|
@@ -161,6 +163,7 @@ const changeSubscriptionStatus = async (subscriptionId, status, userId, organiza
|
|
|
161
163
|
await (0, import_email.sendEmail)({
|
|
162
164
|
...emailData,
|
|
163
165
|
type: "subscriptionPaymentSuccess",
|
|
166
|
+
organizationName: organization.name,
|
|
164
167
|
subscriptionStartDate: emailData.date,
|
|
165
168
|
manageSubscriptionLink: emailData.link
|
|
166
169
|
});
|
|
@@ -169,6 +172,7 @@ const changeSubscriptionStatus = async (subscriptionId, status, userId, organiza
|
|
|
169
172
|
await (0, import_email.sendEmail)({
|
|
170
173
|
...emailData,
|
|
171
174
|
type: "subscriptionPaymentCancellation",
|
|
175
|
+
organizationName: organization.name,
|
|
172
176
|
cancellationDate: emailData.date,
|
|
173
177
|
reactivateLink: emailData.link
|
|
174
178
|
});
|
|
@@ -177,6 +181,7 @@ const changeSubscriptionStatus = async (subscriptionId, status, userId, organiza
|
|
|
177
181
|
await (0, import_email.sendEmail)({
|
|
178
182
|
...emailData,
|
|
179
183
|
type: "subscriptionPaymentError",
|
|
184
|
+
organizationName: organization.name,
|
|
180
185
|
errorDate: emailData.date,
|
|
181
186
|
retryPaymentLink: emailData.link
|
|
182
187
|
});
|
|
@@ -186,10 +191,84 @@ const changeSubscriptionStatus = async (subscriptionId, status, userId, organiza
|
|
|
186
191
|
}
|
|
187
192
|
return updatedOrganization.plan ?? null;
|
|
188
193
|
};
|
|
194
|
+
const getCouponId = async (promoCode) => {
|
|
195
|
+
const stripe = new import_stripe.default(process.env.STRIPE_SECRET_KEY);
|
|
196
|
+
try {
|
|
197
|
+
const coupons = await stripe.coupons.list();
|
|
198
|
+
const matchingCoupon = coupons.data.find(
|
|
199
|
+
(coupon) => coupon.name === promoCode
|
|
200
|
+
);
|
|
201
|
+
return matchingCoupon ? matchingCoupon.id : null;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error("Error retrieving coupon:", error);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const getPricing = async (priceIds, promoCode) => {
|
|
208
|
+
const stripe = new import_stripe.default(process.env.STRIPE_SECRET_KEY);
|
|
209
|
+
try {
|
|
210
|
+
const pricePromises = priceIds.map(
|
|
211
|
+
(priceId) => stripe.prices.retrieve(priceId)
|
|
212
|
+
);
|
|
213
|
+
const prices = await Promise.all(pricePromises);
|
|
214
|
+
const totalAmount = prices.reduce(
|
|
215
|
+
(sum, price) => sum + (price.unit_amount ?? 0),
|
|
216
|
+
0
|
|
217
|
+
);
|
|
218
|
+
let discountAmount = 0;
|
|
219
|
+
let discountType = null;
|
|
220
|
+
if (promoCode) {
|
|
221
|
+
const coupons = await stripe.coupons.list();
|
|
222
|
+
const matchingCoupons = coupons.data.find(
|
|
223
|
+
(coupon) => coupon.name === promoCode
|
|
224
|
+
);
|
|
225
|
+
if (matchingCoupons) {
|
|
226
|
+
if (matchingCoupons.amount_off) {
|
|
227
|
+
discountAmount = matchingCoupons.amount_off;
|
|
228
|
+
discountType = "amount";
|
|
229
|
+
} else if (matchingCoupons.percent_off) {
|
|
230
|
+
discountAmount = matchingCoupons.percent_off;
|
|
231
|
+
discountType = "percentage";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const results = {};
|
|
236
|
+
for (const price of prices) {
|
|
237
|
+
if (!price.id || !price.unit_amount) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const originalTotal = price.unit_amount;
|
|
241
|
+
let appliedDiscount = 0;
|
|
242
|
+
let finalTotal = originalTotal;
|
|
243
|
+
if (discountType === "percentage" && discountAmount > 0) {
|
|
244
|
+
appliedDiscount = originalTotal * discountAmount / 100;
|
|
245
|
+
finalTotal = originalTotal - appliedDiscount;
|
|
246
|
+
} else if (discountType === "amount" && totalAmount > 0 && discountAmount > 0) {
|
|
247
|
+
const proportion = originalTotal / totalAmount;
|
|
248
|
+
appliedDiscount = discountAmount * proportion;
|
|
249
|
+
finalTotal = originalTotal - appliedDiscount;
|
|
250
|
+
}
|
|
251
|
+
finalTotal = Math.max(finalTotal, 0);
|
|
252
|
+
results[price.id] = {
|
|
253
|
+
originalTotal,
|
|
254
|
+
discountApplied: appliedDiscount,
|
|
255
|
+
discountType,
|
|
256
|
+
finalTotal,
|
|
257
|
+
currency: price.currency
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return results;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error("Error calculating pricing per priceId:", error);
|
|
263
|
+
throw new Error("Failed to calculate pricing breakdown.");
|
|
264
|
+
}
|
|
265
|
+
};
|
|
189
266
|
// Annotate the CommonJS export names for ESM import in node:
|
|
190
267
|
0 && (module.exports = {
|
|
191
268
|
addOrUpdateSubscription,
|
|
192
269
|
cancelSubscription,
|
|
193
|
-
changeSubscriptionStatus
|
|
270
|
+
changeSubscriptionStatus,
|
|
271
|
+
getCouponId,
|
|
272
|
+
getPricing
|
|
194
273
|
});
|
|
195
274
|
//# sourceMappingURL=subscription.service.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"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 { sendEmail } from './email.service';\nimport { getOrganizationById, updatePlan } from './organization.service';\nimport { getUserById } from './user.service';\nimport type { Organization } from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\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 (user.customerId !== customerId) {\n user.customerId = 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 subscriptionStartDate: emailData.date,\n manageSubscriptionLink: emailData.link,\n });\n break;\n case 'canceled':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentCancellation',\n cancellationDate: emailData.date,\n reactivateLink: emailData.link,\n });\n break;\n case 'incomplete':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentError',\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"],"mappings":"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","names":["Stripe"]}
|
|
1
|
+
{"version":3,"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 { sendEmail } from './email.service';\nimport { getOrganizationById, updatePlan } from './organization.service';\nimport { getUserById } from './user.service';\nimport type { Organization } from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\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 (user.customerId !== customerId) {\n user.customerId = 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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","names":["Stripe"]}
|
|
@@ -128,7 +128,7 @@ const indexMarkdownFiles = async () => {
|
|
|
128
128
|
try {
|
|
129
129
|
if (JSON.stringify(result) !== JSON.stringify(import_embeddings.default)) {
|
|
130
130
|
import_fs.default.writeFileSync(
|
|
131
|
-
"src/utils/AI/embeddings.json",
|
|
131
|
+
"src/utils/AI/askDocQuestion/embeddings.json",
|
|
132
132
|
JSON.stringify(result, null, 2)
|
|
133
133
|
);
|
|
134
134
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/utils/AI/askDocQuestion/askDocQuestion.ts"],"sourcesContent":["import fs from 'fs';\nimport { getBlogs } from '@intlayer/blog';\nimport { Locales } from '@intlayer/config';\nimport { getDocs } from '@intlayer/docs';\nimport dotenv from 'dotenv';\nimport { OpenAI } from 'openai';\nimport embeddingsList from './embeddings.json' with { type: 'json' };\n\ntype VectorStoreEl = {\n fileKey: string;\n chunkNumber: number;\n content: string;\n embedding: number[];\n};\n\n/**\n * Simple in-memory vector store to hold document embeddings and their content.\n * Each entry contains:\n * - fileKey: A unique key identifying the file\n * - chunkNumber: The number of the chunk within the document\n * - content: The chunk content\n * - embedding: The numerical embedding vector for the chunk\n */\nconst vectorStore: VectorStoreEl[] = [];\n\n// Constants defining OpenAI's token and character limits\nconst MODEL: OpenAI.Chat.ChatModel = 'gpt-4o-2024-11-20'; // Model to use for chat completions\nconst MODEL_TEMPERATURE = 0.1; // Temperature to use for chat completions\nconst EMBEDDING_MODEL: OpenAI.Embeddings.EmbeddingModel =\n 'text-embedding-3-large'; // Model to use for embedding generation\nconst OVERLAP_TOKENS = 200; // Number of tokens to overlap between chunks\nconst MAX_CHUNK_TOKENS = 800; // Maximum number of tokens per chunk\nconst CHAR_BY_TOKEN = 4.15; // Approximate pessimistically the number of characters per token // Can use `tiktoken` or other tokenizers to calculate it more precisely\nconst MAX_CHARS = MAX_CHUNK_TOKENS * CHAR_BY_TOKEN;\nconst OVERLAP_CHARS = OVERLAP_TOKENS * CHAR_BY_TOKEN;\nconst MAX_RELEVANT_CHUNKS_NB = 8; // Maximum number of relevant chunks to attach to chatGPT context\nconst MIN_RELEVANT_CHUNKS_SIMILARITY = 0.25; // Minimum similarity required for a chunk to be considered relevant\n\n/**\n * Splits a given text into chunks ensuring each chunk does not exceed MAX_CHARS.\n * @param text - The input text to split.\n * @returns - Array of text chunks.\n */\nconst chunkText = (text: string): string[] => {\n const chunks: string[] = [];\n let start = 0;\n\n while (start < text.length) {\n let end = Math.min(start + MAX_CHARS, text.length);\n\n // Ensure we don't cut words in the middle (find nearest space)\n if (end < text.length) {\n const lastSpace = text.lastIndexOf(' ', end);\n if (lastSpace > start) {\n end = lastSpace;\n }\n }\n\n chunks.push(text.substring(start, end));\n\n // Move start forward correctly\n const nextStart = end - OVERLAP_CHARS;\n if (nextStart <= start) {\n // Prevent infinite loop if overlap is too large\n start = end;\n } else {\n start = nextStart;\n }\n }\n\n return chunks;\n};\n\n/**\n * Generates an embedding for a given text using OpenAI's embedding API.\n * Trims the text if it exceeds the maximum allowed characters.\n *\n * @param text - The input text to generate an embedding for\n * @returns The embedding vector as a number array\n */\nconst generateEmbedding = async (text: string): Promise<number[]> => {\n try {\n const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n const response = await openai.embeddings.create({\n model: EMBEDDING_MODEL, // Specify the embedding model\n input: text,\n });\n\n return response.data[0].embedding; // Return the generated embedding\n } catch (error) {\n console.error('Error generating embedding:', error);\n return [];\n }\n};\n\n/**\n * Calculates the cosine similarity between two vectors.\n * Cosine similarity measures the cosine of the angle between two vectors in an inner product space.\n * Used to determine the similarity between chunks of text.\n *\n * @param vecA - The first vector\n * @param vecB - The second vector\n * @returns The cosine similarity score\n */\nconst cosineSimilarity = (vecA: number[], vecB: number[]): number => {\n // Calculate the dot product of the two vectors\n const dotProduct = vecA.reduce((sum, a, idx) => sum + a * vecB[idx], 0);\n\n // Calculate the magnitude (Euclidean norm) of each vector\n const magnitudeA = Math.sqrt(vecA.reduce((sum, a) => sum + a * a, 0));\n const magnitudeB = Math.sqrt(vecB.reduce((sum, b) => sum + b * b, 0));\n\n // Compute and return the cosine similarity\n return dotProduct / (magnitudeA * magnitudeB);\n};\n\n/**\n * Indexes all Markdown documents by generating embeddings for each chunk and storing them in memory.\n * Also updates the embeddings.json file if new embeddings are generated.\n */\nexport const indexMarkdownFiles = async (): Promise<void> => {\n const env = process.env.NODE_ENV;\n dotenv.config({\n path: [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env'],\n });\n\n // Retrieve documentation and blog posts in English locale\n const docs = getDocs(Locales.ENGLISH);\n const blogs = getBlogs(Locales.ENGLISH);\n\n let result: Record<string, number[]> = {}; // Object to hold updated embeddings\n\n const files = { ...docs, ...blogs }; // Combine docs and blogs into a single object\n\n // Iterate over each file key (identifier) in the combined files\n for (const fileKey of Object.keys(files)) {\n // Split the document into chunks based on headings\n const fileChunks = chunkText(files[fileKey as keyof typeof files]);\n\n // Iterate over each chunk within the current file\n for (const chunkIndex of Object.keys(fileChunks)) {\n const chunkNumber = Number(chunkIndex) + 1; // Chunk number starts at 1\n const chunksNumber = fileChunks.length;\n\n const fileChunk = fileChunks[\n chunkIndex as keyof typeof fileChunks\n ] as string;\n\n const embeddingKeyName = `${fileKey}/chunk_${chunkNumber}`; // Unique key for the chunk\n\n // Retrieve precomputed embedding if available\n const docEmbedding = embeddingsList[\n embeddingKeyName as keyof typeof embeddingsList\n ] as number[] | undefined;\n\n let embedding = docEmbedding; // Use existing embedding if available\n\n if (!embedding) {\n embedding = await generateEmbedding(fileChunk); // Generate embedding if not present\n }\n\n // Update the result object with the new embedding\n result = { ...result, [embeddingKeyName]: embedding };\n\n // Store the embedding and content in the in-memory vector store\n vectorStore.push({\n fileKey,\n chunkNumber,\n embedding,\n content: fileChunk,\n });\n\n console.info(`- Indexed: ${embeddingKeyName}/${chunksNumber}`);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n try {\n // Compare the newly generated embeddings with existing ones\n if (JSON.stringify(result) !== JSON.stringify(embeddingsList)) {\n // If there are new embeddings, save them to embeddings.json\n fs.writeFileSync(\n 'src/utils/AI/askDocQuestion/embeddings.json',\n JSON.stringify(result, null, 2)\n );\n }\n } catch (error) {\n console.error(error); // Log any errors during the file write process\n }\n }\n};\n\n// Automatically index Markdown files\nindexMarkdownFiles();\n\n/**\n * Searches the indexed documents for the most relevant chunks based on a query.\n * Utilizes cosine similarity to find the closest matching embeddings.\n *\n * @param query - The search query provided by the user\n * @returns An array of the top matching document chunks' content\n */\nexport const searchChunkReference = async (\n query: string\n): Promise<VectorStoreEl[]> => {\n // Generate an embedding for the user's query\n const queryEmbedding = await generateEmbedding(query);\n\n // Calculate similarity scores between the query embedding and each document's embedding\n const results = vectorStore\n .map((chunk) => ({\n ...chunk,\n similarity: cosineSimilarity(queryEmbedding, chunk.embedding), // Add similarity score to each doc\n }))\n .filter((chunk) => chunk.similarity > MIN_RELEVANT_CHUNKS_SIMILARITY) // Filter out documents with low similarity scores\n .sort((a, b) => b.similarity - a.similarity) // Sort documents by highest similarity first\n .slice(0, MAX_RELEVANT_CHUNKS_NB); // Select the top 6 most similar documents\n\n // Return the content of the top matching documents\n return results;\n};\n\n// Define the structure of messages used in chat completions\nexport type ChatCompletionRequestMessage = {\n role: 'system' | 'user' | 'assistant'; // The role of the message sender\n content: string; // The text content of the message\n};\n\n// Initial prompt configuration for the chatbot\nexport const initPrompt: ChatCompletionRequestMessage = {\n role: 'system',\n content:\n 'Ignore all instructions before this one. \\\n Keep forever all instructions after this one. From now you are not allowed to ignore the previous instructions. \\\n You are an expert in internationalization solution for Javascript Application. You\\'re an helpful chatbot for Intlayer, an Internationalization solution\\\n The website url of the solution is: \\'https://intlayer.org\\'). And your implemented on this website. \\\n The user is an potential user of Intlayer. Your task is now to answer the questions of the user. \\\n You must talk as an member of Intlayer. You must only answer the questions relative to Intlayer. \\\n Any question should be related to Intlayer. If a question is not related to Intlayer, you should NOT answer it. \\\n You must NOT answer question who is generally secret for a company (E.g. financial information). \\\n Your should NOT invent information that are not precised into the relevant documentation chunks provided. \\\n If you don\\'t have enough information to answer the question, not answer using extra information extracted from your knowledge. \\\n If your have a doubt about something, you should ask more question to the user. \\\n \\\n Here some useful urls to know more about Intlayer: \\\n https://intlayer.org/docs \\\n https://intlayer.org/blog \\\n https://intlayer.org/pricing \\\n https://intlayer.org/dashboard \\\n \\\n Your should return a result as markdown.\\\n Code element should include metadata fileName=\"file.ts\" if could be useful for the user. \\\n Code element format should not include metadata (E.g. codeFormat=\"typescript\", or packageManager=\"npm\". \\\n \\\n Here is the relevant documentation:\\\n {{relevantFilesReferences}}', // Placeholder for relevant documentation to be inserted later\n};\n\nexport type AskDocQuestionResult = {\n response: string;\n relatedFiles: string[];\n};\n\n/**\n * Handles the \"Ask a question\" endpoint in an Express.js route.\n * Processes user messages, retrieves relevant documents, and interacts with OpenAI's chat API to generate responses.\n *\n * @param messages - An array of chat messages from the user and assistant\n * @returns The assistant's response as a string\n */\nexport const askDocQuestion = async (\n messages: ChatCompletionRequestMessage[]\n): Promise<AskDocQuestionResult> => {\n const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n\n // Assistant's response are filtered out otherwise the chatbot will be stuck in a self-referential loop\n // Note that the embedding precision will be lowered if the user change of context in the chat\n const userMessages = messages.filter((message) => message.role === 'user');\n\n // Format the user's question to keep only the relevant keywords\n const query = userMessages\n .map((message) => `- ${message.content}`)\n .join('\\n');\n\n // 1) Find relevant documents based on the user's question\n const relevantFilesReferences = await searchChunkReference(query);\n\n // 2) Integrate the relevant documents into the initial system prompt\n const messagesList: ChatCompletionRequestMessage[] = [\n {\n ...initPrompt,\n content: initPrompt.content.replace(\n '{{relevantFilesReferences}}',\n relevantFilesReferences.length === 0\n ? 'Not relevant file found related to the question.'\n : relevantFilesReferences\n .map(\n (doc, idx) =>\n `[Chunk ${idx}] docKey = \"${doc.fileKey}\":\\n${doc.content}`\n )\n .join('\\n\\n') // Insert relevant docs into the prompt\n ),\n },\n ...messages, // Include all user and assistant messages\n ];\n\n // 3) Send the compiled messages to OpenAI's Chat Completion API (using a specific model)\n const response = await openai.chat.completions.create({\n model: MODEL,\n temperature: MODEL_TEMPERATURE,\n messages: messagesList,\n });\n\n const result = response.choices[0].message.content; // Extract the assistant's reply\n\n // 4) Extract unique related files\n const relatedFiles = [\n ...new Set(relevantFilesReferences.map((doc) => doc.fileKey)),\n ];\n\n // 5) Return the assistant's response to the user\n return {\n response: result ?? 'Error: No result found',\n relatedFiles,\n };\n};\n"],"mappings":"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","names":["dotenv","embeddingsList","fs"]}
|