@intlayer/backend 8.7.11 → 8.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/benchmark/solid.json +4990 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/benchmark/svelte.json +4987 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/benchmark/vue.json +4975 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_astro_lit.json +9936 -8930
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_astro_vanilla.json +7963 -6949
- package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/readme.json +12908 -12288
- package/dist/esm/controllers/oAuth2.controller.mjs +21 -1
- package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
- package/dist/esm/controllers/stripe.controller.mjs +86 -2
- package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
- package/dist/esm/controllers/translation.controller.mjs +2 -0
- package/dist/esm/controllers/translation.controller.mjs.map +1 -1
- package/dist/esm/emails/InviteUserEmail.mjs +1541 -1
- package/dist/esm/emails/InviteUserEmail.mjs.map +1 -1
- package/dist/esm/emails/MagicLinkEmail.mjs +1128 -1
- package/dist/esm/emails/MagicLinkEmail.mjs.map +1 -1
- package/dist/esm/emails/OAuthTokenCreatedEmail.mjs +1389 -1
- package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
- package/dist/esm/emails/PasswordChangeConfirmation.mjs +814 -1
- package/dist/esm/emails/PasswordChangeConfirmation.mjs.map +1 -1
- package/dist/esm/emails/ResetUserPassword.mjs +1132 -1
- package/dist/esm/emails/ResetUserPassword.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentCancellation.mjs +913 -1
- package/dist/esm/emails/SubscriptionPaymentCancellation.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentError.mjs +908 -1
- package/dist/esm/emails/SubscriptionPaymentError.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentSuccess.mjs +935 -1
- package/dist/esm/emails/SubscriptionPaymentSuccess.mjs.map +1 -1
- package/dist/esm/emails/ValidateUserEmail.mjs +1111 -1
- package/dist/esm/emails/ValidateUserEmail.mjs.map +1 -1
- package/dist/esm/emails/Welcome.mjs +1004 -1
- package/dist/esm/emails/Welcome.mjs.map +1 -1
- package/dist/esm/emails/index.mjs +7 -7
- package/dist/esm/index.mjs +8 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/middlewares/oAuth2.middleware.mjs +2 -2
- package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
- package/dist/esm/routes/audit.routes.mjs +5 -4
- package/dist/esm/routes/audit.routes.mjs.map +1 -1
- package/dist/esm/routes/dictionary.routes.mjs +4 -3
- package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
- package/dist/esm/routes/organization.routes.mjs +3 -2
- package/dist/esm/routes/organization.routes.mjs.map +1 -1
- package/dist/esm/routes/paramsSchemas.mjs +67 -0
- package/dist/esm/routes/paramsSchemas.mjs.map +1 -0
- package/dist/esm/routes/project.routes.mjs +2 -1
- package/dist/esm/routes/project.routes.mjs.map +1 -1
- package/dist/esm/routes/showcaseProject.routes.mjs +5 -4
- package/dist/esm/routes/showcaseProject.routes.mjs.map +1 -1
- package/dist/esm/routes/stripe.routes.mjs +19 -1
- package/dist/esm/routes/stripe.routes.mjs.map +1 -1
- package/dist/esm/routes/tags.routes.mjs +3 -2
- package/dist/esm/routes/tags.routes.mjs.map +1 -1
- package/dist/esm/routes/translate.routes.mjs +6 -5
- package/dist/esm/routes/translate.routes.mjs.map +1 -1
- package/dist/esm/routes/user.routes.mjs +5 -4
- package/dist/esm/routes/user.routes.mjs.map +1 -1
- package/dist/esm/schemas/oAuth2.schema.mjs +1 -1
- package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
- package/dist/esm/services/email.service.mjs +338 -38
- package/dist/esm/services/email.service.mjs.map +1 -1
- package/dist/esm/services/oAuth2.service.mjs +20 -2
- package/dist/esm/services/oAuth2.service.mjs.map +1 -1
- package/dist/esm/services/subscription.service.mjs +5 -2
- package/dist/esm/services/subscription.service.mjs.map +1 -1
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/benchmark/solid.json +4990 -0
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/benchmark/svelte.json +4987 -0
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/benchmark/vue.json +4975 -0
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_astro_lit.json +9936 -8930
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_astro_vanilla.json +7963 -6949
- package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/readme.json +12908 -12288
- package/dist/esm/utils/auth/getAuth.mjs +6 -0
- package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
- package/dist/esm/utils/errors/errorCodes.mjs +3917 -287
- package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
- package/dist/esm/utils/mongoDB/connectDB.mjs +5 -0
- package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
- package/dist/esm/utils/oAuth2.mjs +6 -2
- package/dist/esm/utils/oAuth2.mjs.map +1 -1
- package/dist/esm/utils/plan.mjs +13 -1
- package/dist/esm/utils/plan.mjs.map +1 -1
- package/dist/types/controllers/oAuth2.controller.d.ts +11 -1
- package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
- package/dist/types/controllers/stripe.controller.d.ts +22 -2
- package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
- package/dist/types/controllers/translation.controller.d.ts.map +1 -1
- package/dist/types/emails/InviteUserEmail.d.ts +181 -1
- package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
- package/dist/types/emails/MagicLinkEmail.d.ts +106 -1
- package/dist/types/emails/MagicLinkEmail.d.ts.map +1 -1
- package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +166 -1
- package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
- package/dist/types/emails/PasswordChangeConfirmation.d.ts +91 -1
- package/dist/types/emails/PasswordChangeConfirmation.d.ts.map +1 -1
- package/dist/types/emails/ResetUserPassword.d.ts +106 -1
- package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +151 -1
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentError.d.ts +151 -1
- package/dist/types/emails/SubscriptionPaymentError.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +151 -1
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts.map +1 -1
- package/dist/types/emails/ValidateUserEmail.d.ts +106 -1
- package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
- package/dist/types/emails/Welcome.d.ts +106 -1
- package/dist/types/emails/Welcome.d.ts.map +1 -1
- package/dist/types/emails/index.d.ts +7 -7
- package/dist/types/export.d.ts +3 -3
- package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
- package/dist/types/routes/audit.routes.d.ts.map +1 -1
- package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
- package/dist/types/routes/organization.routes.d.ts.map +1 -1
- package/dist/types/routes/paramsSchemas.d.ts +102 -0
- package/dist/types/routes/paramsSchemas.d.ts.map +1 -0
- package/dist/types/routes/project.routes.d.ts.map +1 -1
- package/dist/types/routes/showcaseProject.routes.d.ts.map +1 -1
- package/dist/types/routes/stripe.routes.d.ts +15 -0
- package/dist/types/routes/stripe.routes.d.ts.map +1 -1
- package/dist/types/routes/tags.routes.d.ts.map +1 -1
- package/dist/types/routes/translate.routes.d.ts.map +1 -1
- package/dist/types/routes/user.routes.d.ts.map +1 -1
- package/dist/types/schemas/dictionary.schema.d.ts +6 -6
- package/dist/types/schemas/discussion.schema.d.ts +9 -9
- package/dist/types/schemas/organization.schema.d.ts +9 -9
- package/dist/types/schemas/plans.schema.d.ts +6 -6
- package/dist/types/schemas/project.schema.d.ts +12 -12
- package/dist/types/schemas/session.schema.d.ts +8 -8
- package/dist/types/schemas/showcaseProject.schema.d.ts +19 -19
- package/dist/types/schemas/tag.schema.d.ts +7 -7
- package/dist/types/services/email.service.d.ts.map +1 -1
- package/dist/types/services/oAuth2.service.d.ts +6 -1
- package/dist/types/services/oAuth2.service.d.ts.map +1 -1
- package/dist/types/types/plan.types.d.ts +2 -2
- package/dist/types/utils/errors/errorCodes.d.ts +3634 -4
- 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/utils/oAuth2.d.ts +3 -1
- package/dist/types/utils/oAuth2.d.ts.map +1 -1
- package/dist/types/utils/plan.d.ts +2 -1
- package/dist/types/utils/plan.d.ts.map +1 -1
- package/package.json +14 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oAuth2.service.mjs","names":[],"sources":["../../../src/services/oAuth2.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { OAuth2AccessTokenModel } from '@models/oAuth2.model';\nimport { ProjectModel } from '@models/project.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { getTokenExpireAt } from '@utils/oAuth2';\nimport type { Types } from 'mongoose';\nimport type { Callback, Client } from 'oauth2-server';\nimport type { OAuth2Token } from '@/types/oAuth2.types';\nimport type { Organization } from '@/types/organization.types';\nimport type {\n OAuth2Access,\n OAuth2AccessContext,\n Project,\n ProjectDocument,\n} from '@/types/project.types';\nimport type { User, UserAPI, UserDocument } from '@/types/user.types';\nimport type { Token } from '../schemas/oAuth2.schema';\nimport { getOrganizationById } from './organization.service';\nimport { getUserById } from './user.service';\n\n/**\n * Function to generate client credentials\n *\n * @returns The client id and client secret\n */\nexport const generateClientCredentials = (): {\n clientId: string;\n clientSecret: string;\n} => {\n const clientId = randomBytes(16).toString('hex'); // Generate a 16 character hexadecimal string\n const clientSecret = randomBytes(32).toString('hex'); // Generate a 32 character hexadecimal string\n\n return { clientId, clientSecret };\n};\n\n/**\n * Method to get the client and the project\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The an object containing the client, the rights and the project or false if not found\n */\nexport const getClientAndProjectByClientId = async (\n clientId: string\n): Promise<\n | {\n client: Client;\n oAuth2Access: OAuth2Access;\n project: ProjectDocument;\n grants: Token['grants'];\n }\n | false\n> => {\n const project = await ProjectModel.findOne({\n 'oAuth2Access.clientId': clientId,\n });\n\n if (!project) {\n return false;\n }\n\n const oAuth2Access = project.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!oAuth2Access) {\n return false;\n }\n\n const formattedClient: Client = {\n id: oAuth2Access.clientId,\n clientId,\n clientSecret: oAuth2Access.clientSecret,\n grants: ['client_credentials'],\n };\n\n return {\n client: formattedClient,\n oAuth2Access,\n grants: oAuth2Access.grants,\n project,\n };\n};\n\n/**\n * Get the client and verify that the client secret is correct\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The client or false if not found\n */\nexport const getClient = async (\n clientId: string,\n clientSecret: string\n): Promise<Client | false> => {\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client } = result;\n\n if (!client || client.clientSecret !== clientSecret) {\n return false;\n }\n\n return client;\n};\n\n/**\n * Format an OAuth2Token\n *\n * @param token - The token to format\n * @param client - The client\n * @param user - The user\n * @param project - The project\n * @param organization - The organization\n * @param grants - The grants\n * @returns The formatted token\n */\nexport const formatOAuth2Token = (\n token: Token,\n client: Client,\n user: UserAPI,\n project: Project,\n organization: Organization,\n grants: Token['grants']\n): OAuth2Token => {\n // biome-ignore lint/correctness/noUnusedVariables: Just filter out clientId\n const { clientId, userId, ...restToken } = token;\n\n if (String(userId) !== String(user.id)) {\n throw new GenericError('USER_ID_MISMATCH');\n }\n\n const formattedToken: OAuth2Token = {\n ...restToken,\n client,\n user: mapUserToAPI(user),\n organization: mapOrganizationToAPI(organization),\n project: mapProjectToAPI(project),\n accessToken: token.accessToken,\n accessTokenExpiresAt: token.accessTokenExpiresAt ?? new Date('999-99-99'),\n grants,\n };\n\n return formattedToken;\n};\n\n/**\n * Format a auth token for the database\n *\n * @param token - The oAuth2 token to format\n * @param clientId - The client ID\n * @param userId - The user ID\n * @returns\n */\nexport const formatDBToken = (\n token: OAuth2Token,\n clientId: Client['id'],\n userId: User['id'] | string\n): Token => {\n const formattedToken: Token = {\n id: token.id,\n clientId: clientId,\n userId: userId as Types.ObjectId,\n accessToken: token.accessToken,\n expiresIn: token.accessTokenExpiresAt ?? getTokenExpireAt(),\n };\n\n return formattedToken;\n};\n\n/**\n * Method to save the token\n *\n * @param token - The token\n * @param client - The client\n * @param user - The user\n * @returns The saved token or false if not saved\n */\nexport const saveToken = async (\n token: OAuth2Token,\n client: Client,\n user: UserAPI\n): Promise<OAuth2Token | false> => {\n const formattedAccessToken: Token = formatDBToken(token, client.id, user.id);\n\n const result = await OAuth2AccessTokenModel.create(formattedAccessToken);\n\n if (!result) {\n return false;\n }\n\n const result2 = await getClientAndProjectByClientId(result.clientId);\n\n if (!result2) {\n return false;\n }\n\n const { project } = result2;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedResult = formatOAuth2Token(\n formattedAccessToken,\n client,\n user,\n project,\n organization,\n token.rights\n );\n return formattedResult;\n};\n\n/**\n * Method to get the access token\n *\n * @param accessToken - The access token\n * @returns The access token or false if not found\n */\nexport const getAccessToken = async (\n accessToken: string\n): Promise<OAuth2Token | false> => {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken,\n });\n\n if (!token) {\n return false;\n }\n\n const { userId, clientId } = token;\n\n const user = await getUserById(userId);\n\n if (!user) {\n return false;\n }\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client, project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedAccessToken = formatOAuth2Token(\n token,\n client,\n user,\n project,\n organization,\n grants\n );\n\n return formattedAccessToken;\n};\n\n/**\n * Method to get the user from the client\n *\n * @param client - The client\n * @returns The user or false if not found\n */\nexport const getUserFromClient = async (\n client: Client\n): Promise<UserDocument | false> => {\n const response = await getClientAndProjectByClientId(client.id);\n\n if (!response) {\n return false;\n }\n\n const { userId } = response.oAuth2Access;\n\n if (!userId) {\n return false;\n }\n\n const user = await getUserById(userId);\n\n return user ?? false;\n};\n\n/**\n * Method to verify the permissions (grants)\n *\n * @param token - The token\n * @param scope - The scope\n * @returns True if the token has the required scope, false otherwise\n */\nexport const verifyScope = async (\n _token: OAuth2Token,\n _scope: string,\n _callback?: Callback<boolean> | undefined\n): Promise<boolean> => {\n // Implement the verification of scopes if necessary\n return true;\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const validateOAuth2AccessToken = async (\n accessToken: string\n): Promise<Token> => {\n try {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken,\n });\n\n if (!token) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n // Check if token is expired\n if (new Date() > new Date(token.expiresIn)) {\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n return ensureMongoDocumentToObject(token);\n } catch (_error) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const getOAuth2AccessTokenContext = async (\n token: Token\n): Promise<OAuth2AccessContext> => {\n const { userId, clientId } = token;\n\n const user = await getUserById(String(userId));\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const { project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n return {\n accessToken: token.accessToken,\n user: user ? mapUserToAPI(user) : undefined,\n project: project ? mapProjectToAPI(project) : undefined,\n organization: organization ? mapOrganizationToAPI(organization) : undefined,\n grants,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oAuth2.service.mjs","names":[],"sources":["../../../src/services/oAuth2.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { OAuth2AccessTokenModel } from '@models/oAuth2.model';\nimport { ProjectModel } from '@models/project.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { getTokenExpireAt, shouldExtendOAuth2Token } from '@utils/oAuth2';\nimport type { Types } from 'mongoose';\nimport type { Callback, Client } from 'oauth2-server';\nimport type { OAuth2Token } from '@/types/oAuth2.types';\nimport type { Organization } from '@/types/organization.types';\nimport type {\n OAuth2Access,\n OAuth2AccessContext,\n Project,\n ProjectDocument,\n} from '@/types/project.types';\nimport type { User, UserAPI, UserDocument } from '@/types/user.types';\nimport type { Token } from '../schemas/oAuth2.schema';\nimport { getOrganizationById } from './organization.service';\nimport { getUserById } from './user.service';\n\n/**\n * Function to generate client credentials\n *\n * @returns The client id and client secret\n */\nexport const generateClientCredentials = (): {\n clientId: string;\n clientSecret: string;\n} => {\n const clientId = randomBytes(16).toString('hex'); // Generate a 16 character hexadecimal string\n const clientSecret = randomBytes(32).toString('hex'); // Generate a 32 character hexadecimal string\n\n return { clientId, clientSecret };\n};\n\n/**\n * Method to get the client and the project\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The an object containing the client, the rights and the project or false if not found\n */\nexport const getClientAndProjectByClientId = async (\n clientId: string\n): Promise<\n | {\n client: Client;\n oAuth2Access: OAuth2Access;\n project: ProjectDocument;\n grants: Token['grants'];\n }\n | false\n> => {\n const project = await ProjectModel.findOne({\n 'oAuth2Access.clientId': clientId,\n });\n\n if (!project) {\n return false;\n }\n\n const oAuth2Access = project.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!oAuth2Access) {\n return false;\n }\n\n const formattedClient: Client = {\n id: oAuth2Access.clientId,\n clientId,\n clientSecret: oAuth2Access.clientSecret,\n grants: ['client_credentials'],\n };\n\n return {\n client: formattedClient,\n oAuth2Access,\n grants: oAuth2Access.grants,\n project,\n };\n};\n\n/**\n * Get the client and verify that the client secret is correct\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The client or false if not found\n */\nexport const getClient = async (\n clientId: string,\n clientSecret: string\n): Promise<Client | false> => {\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client } = result;\n\n if (!client || client.clientSecret !== clientSecret) {\n return false;\n }\n\n return client;\n};\n\n/**\n * Format an OAuth2Token\n *\n * @param token - The token to format\n * @param client - The client\n * @param user - The user\n * @param project - The project\n * @param organization - The organization\n * @param grants - The grants\n * @returns The formatted token\n */\nexport const formatOAuth2Token = (\n token: Token,\n client: Client,\n user: UserAPI,\n project: Project,\n organization: Organization,\n grants: Token['grants']\n): OAuth2Token => {\n // biome-ignore lint/correctness/noUnusedVariables: Just filter out clientId\n const { clientId, userId, ...restToken } = token;\n\n if (String(userId) !== String(user.id)) {\n throw new GenericError('USER_ID_MISMATCH');\n }\n\n const formattedToken: OAuth2Token = {\n ...restToken,\n client,\n user: mapUserToAPI(user),\n organization: mapOrganizationToAPI(organization),\n project: mapProjectToAPI(project),\n accessToken: token.accessToken,\n accessTokenExpiresAt: token.accessTokenExpiresAt ?? new Date('999-99-99'),\n grants,\n };\n\n return formattedToken;\n};\n\n/**\n * Format a auth token for the database\n *\n * @param token - The oAuth2 token to format\n * @param clientId - The client ID\n * @param userId - The user ID\n * @returns\n */\nexport const formatDBToken = (\n token: OAuth2Token,\n clientId: Client['id'],\n userId: User['id'] | string\n): Token => {\n const formattedToken: Token = {\n id: token.id,\n clientId: clientId,\n userId: userId as Types.ObjectId,\n accessToken: token.accessToken,\n expiresIn: token.accessTokenExpiresAt ?? getTokenExpireAt(),\n };\n\n return formattedToken;\n};\n\n/**\n * Method to save the token\n *\n * @param token - The token\n * @param client - The client\n * @param user - The user\n * @returns The saved token or false if not saved\n */\nexport const saveToken = async (\n token: OAuth2Token,\n client: Client,\n user: UserAPI\n): Promise<OAuth2Token | false> => {\n const formattedAccessToken: Token = formatDBToken(token, client.id, user.id);\n\n const result = await OAuth2AccessTokenModel.create(formattedAccessToken);\n\n if (!result) {\n return false;\n }\n\n const result2 = await getClientAndProjectByClientId(result.clientId);\n\n if (!result2) {\n return false;\n }\n\n const { project } = result2;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedResult = formatOAuth2Token(\n formattedAccessToken,\n client,\n user,\n project,\n organization,\n token.rights\n );\n return formattedResult;\n};\n\n/**\n * Sliding-refresh: push the token's expiry forward when it has been used\n * within the refresh threshold. Idempotent and cheap when no extension is due.\n */\nexport const extendOAuth2AccessToken = async (\n accessToken: string\n): Promise<Date> => {\n const nextExpiresAt = getTokenExpireAt();\n await OAuth2AccessTokenModel.updateOne(\n { accessToken },\n { $set: { accessTokenExpiresAt: nextExpiresAt, expiresIn: nextExpiresAt } }\n );\n return nextExpiresAt;\n};\n\n/**\n * Method to get the access token\n *\n * @param accessToken - The access token\n * @returns The access token or false if not found\n */\nexport const getAccessToken = async (\n accessToken: string\n): Promise<OAuth2Token | false> => {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken,\n });\n\n if (!token) {\n return false;\n }\n\n // Slide the expiry forward when this active token is approaching its\n // deadline so a long-lived integration doesn't have to re-authenticate.\n const currentExpiresAt = token.accessTokenExpiresAt ?? token.expiresIn;\n if (currentExpiresAt && shouldExtendOAuth2Token(currentExpiresAt)) {\n const nextExpiresAt = await extendOAuth2AccessToken(accessToken);\n token.accessTokenExpiresAt = nextExpiresAt;\n token.expiresIn = nextExpiresAt;\n }\n\n const { userId, clientId } = token;\n\n const user = await getUserById(userId);\n\n if (!user) {\n return false;\n }\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client, project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedAccessToken = formatOAuth2Token(\n token,\n client,\n user,\n project,\n organization,\n grants\n );\n\n return formattedAccessToken;\n};\n\n/**\n * Method to get the user from the client\n *\n * @param client - The client\n * @returns The user or false if not found\n */\nexport const getUserFromClient = async (\n client: Client\n): Promise<UserDocument | false> => {\n const response = await getClientAndProjectByClientId(client.id);\n\n if (!response) {\n return false;\n }\n\n const { userId } = response.oAuth2Access;\n\n if (!userId) {\n return false;\n }\n\n const user = await getUserById(userId);\n\n return user ?? false;\n};\n\n/**\n * Method to verify the permissions (grants)\n *\n * @param token - The token\n * @param scope - The scope\n * @returns True if the token has the required scope, false otherwise\n */\nexport const verifyScope = async (\n _token: OAuth2Token,\n _scope: string,\n _callback?: Callback<boolean> | undefined\n): Promise<boolean> => {\n // Implement the verification of scopes if necessary\n return true;\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const validateOAuth2AccessToken = async (\n accessToken: string\n): Promise<Token> => {\n try {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken,\n });\n\n if (!token) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n // Check if token is expired\n if (new Date() > new Date(token.expiresIn)) {\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n return ensureMongoDocumentToObject(token);\n } catch (_error) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const getOAuth2AccessTokenContext = async (\n token: Token\n): Promise<OAuth2AccessContext> => {\n const { userId, clientId } = token;\n\n const user = await getUserById(String(userId));\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const { project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n return {\n accessToken: token.accessToken,\n user: user ? mapUserToAPI(user) : undefined,\n project: project ? mapProjectToAPI(project) : undefined,\n organization: organization ? mapOrganizationToAPI(organization) : undefined,\n grants,\n };\n};\n"],"mappings":"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"}
|
|
@@ -124,8 +124,11 @@ const getCouponId = async (promoCode) => {
|
|
|
124
124
|
const getPricing = async (priceIds, promoCode) => {
|
|
125
125
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
126
126
|
try {
|
|
127
|
-
const
|
|
128
|
-
|
|
127
|
+
const prices = (await Promise.allSettled(priceIds.map((priceId) => stripe.prices.retrieve(priceId)))).map((result, index) => {
|
|
128
|
+
if (result.status === "fulfilled") return result.value;
|
|
129
|
+
logger.warn(`Skipping price ${priceIds[index]} — retrieval failed: ${result.reason?.message ?? "unknown error"}`);
|
|
130
|
+
return null;
|
|
131
|
+
}).filter((price) => price !== null);
|
|
129
132
|
const totalAmount = prices.reduce((sum, price) => sum + (price.unit_amount ?? 0), 0);
|
|
130
133
|
let discountAmount = 0;
|
|
131
134
|
let discountType = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.service.mjs","names":[],"sources":["../../../src/services/subscription.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { GenericError } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport Stripe from 'stripe';\nimport type { Organization } from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\nimport { sendEmail } from './email.service';\nimport { getOrganizationById, updatePlan } from './organization.service';\nimport { getUserById } from './user.service';\n\nexport const addOrUpdateSubscription = async (\n subscriptionId: string,\n priceId: string,\n customerId: string,\n userId: string,\n organization: Organization,\n status: Plan['status']\n): Promise<Plan | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n });\n }\n\n if (String(user.customerId) !== customerId) {\n (user.customerId as unknown as string) = customerId;\n await user.save();\n }\n\n const planInfo = retrievePlanInformation(priceId);\n\n const subscriptions = await stripe.subscriptions.list({\n customer: customerId,\n status: 'active',\n });\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 | undefined, // Changed to optional\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 organizationId,\n });\n }\n\n // If there is no plan, we consider it already \"canceled\" or free.\n // We can return a default free plan or just null.\n if (!organization.plan) {\n return {\n type: 'FREE',\n status: 'active',\n } as Plan;\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}${subscriptionId ? ` (Subscription ID: ${subscriptionId})` : ''}`\n );\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const changeSubscriptionStatus = async (\n subscriptionId: string,\n status: Plan['status'],\n userId: string,\n organizationId: string\n): Promise<Plan | null> => {\n const organization = await getOrganizationById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n if (!organization.plan) {\n throw new GenericError('ORGANIZATION_PLAN_NOT_FOUND', {\n userId,\n subscriptionId,\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await updatePlan(organization, {\n status,\n subscriptionId,\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n logger.info(\n `Updated plan status for organization ${organization.id} - Status: ${status}`\n );\n\n const emailData = {\n to: user.email,\n username: user.name,\n email: user.email,\n planName: organization.plan.type,\n date: new Date().toLocaleDateString(),\n link: `${process.env.APP_URL}/dashboard`,\n };\n\n switch (status) {\n case 'active':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentSuccess',\n organizationName: organization.name,\n subscriptionStartDate: emailData.date,\n manageSubscriptionLink: emailData.link,\n });\n break;\n case 'canceled':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentCancellation',\n organizationName: organization.name,\n cancellationDate: emailData.date,\n reactivateLink: emailData.link,\n });\n break;\n case 'incomplete':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentError',\n organizationName: organization.name,\n errorDate: emailData.date,\n retryPaymentLink: emailData.link,\n });\n break;\n default:\n logger.warn(`Unhandled subscription status: ${status}`);\n }\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const getCouponId = async (\n promoCode: string\n): Promise<string | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Retrieve the coupon details by name\n const coupons = await stripe.coupons.list();\n const matchingCoupon = coupons.data.find(\n (coupon) => coupon.name === promoCode\n );\n\n return matchingCoupon ? matchingCoupon.id : null;\n } catch (error) {\n logger.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 logger.error('Error calculating pricing per priceId:', error);\n throw new Error('Failed to calculate pricing breakdown.');\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription.service.mjs","names":[],"sources":["../../../src/services/subscription.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { GenericError } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport Stripe from 'stripe';\nimport type { Organization } from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\nimport { sendEmail } from './email.service';\nimport { getOrganizationById, updatePlan } from './organization.service';\nimport { getUserById } from './user.service';\n\nexport const addOrUpdateSubscription = async (\n subscriptionId: string,\n priceId: string,\n customerId: string,\n userId: string,\n organization: Organization,\n status: Plan['status']\n): Promise<Plan | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n });\n }\n\n if (String(user.customerId) !== customerId) {\n (user.customerId as unknown as string) = customerId;\n await user.save();\n }\n\n const planInfo = retrievePlanInformation(priceId);\n\n const subscriptions = await stripe.subscriptions.list({\n customer: customerId,\n status: 'active',\n });\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 | undefined, // Changed to optional\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 organizationId,\n });\n }\n\n // If there is no plan, we consider it already \"canceled\" or free.\n // We can return a default free plan or just null.\n if (!organization.plan) {\n return {\n type: 'FREE',\n status: 'active',\n } as Plan;\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}${subscriptionId ? ` (Subscription ID: ${subscriptionId})` : ''}`\n );\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const changeSubscriptionStatus = async (\n subscriptionId: string,\n status: Plan['status'],\n userId: string,\n organizationId: string\n): Promise<Plan | null> => {\n const organization = await getOrganizationById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n if (!organization.plan) {\n throw new GenericError('ORGANIZATION_PLAN_NOT_FOUND', {\n userId,\n subscriptionId,\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await updatePlan(organization, {\n status,\n subscriptionId,\n });\n\n if (!updatedOrganization) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n const user = await getUserById(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', {\n userId,\n subscriptionId,\n });\n }\n\n logger.info(\n `Updated plan status for organization ${organization.id} - Status: ${status}`\n );\n\n const emailData = {\n to: user.email,\n username: user.name,\n email: user.email,\n planName: organization.plan.type,\n date: new Date().toLocaleDateString(),\n link: `${process.env.APP_URL}/dashboard`,\n };\n\n switch (status) {\n case 'active':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentSuccess',\n organizationName: organization.name,\n subscriptionStartDate: emailData.date,\n manageSubscriptionLink: emailData.link,\n });\n break;\n case 'canceled':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentCancellation',\n organizationName: organization.name,\n cancellationDate: emailData.date,\n reactivateLink: emailData.link,\n });\n break;\n case 'incomplete':\n await sendEmail({\n ...emailData,\n type: 'subscriptionPaymentError',\n organizationName: organization.name,\n errorDate: emailData.date,\n retryPaymentLink: emailData.link,\n });\n break;\n default:\n logger.warn(`Unhandled subscription status: ${status}`);\n }\n\n return updatedOrganization.plan ?? null;\n};\n\nexport const getCouponId = async (\n promoCode: string\n): Promise<string | null> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Retrieve the coupon details by name\n const coupons = await stripe.coupons.list();\n const matchingCoupon = coupons.data.find(\n (coupon) => coupon.name === promoCode\n );\n\n return matchingCoupon ? matchingCoupon.id : null;\n } catch (error) {\n logger.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, skipping any that fail (e.g. unset / invalid\n // env IDs). One bad ID should not break pricing for the entire page.\n const priceResults = await Promise.allSettled(\n priceIds.map((priceId) => stripe.prices.retrieve(priceId))\n );\n\n const prices = priceResults\n .map((result, index) => {\n if (result.status === 'fulfilled') return result.value;\n logger.warn(\n `Skipping price ${priceIds[index]} — retrieval failed: ${\n (result.reason as Error)?.message ?? 'unknown error'\n }`\n );\n return null;\n })\n .filter((price): price is Stripe.Price => price !== null);\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 logger.error('Error calculating pricing per priceId:', error);\n throw new Error('Failed to calculate pricing breakdown.');\n }\n};\n"],"mappings":";;;;;;;;;AAUA,MAAa,0BAA0B,OACrC,gBACA,SACA,YACA,QACA,cACA,WACyB;CACzB,MAAM,SAAS,IAAI,OAAO,QAAQ,IAAI,kBAAmB;CACzD,MAAM,OAAO,MAAM,YAAY,OAAO;AAEtC,KAAI,CAAC,KACH,OAAM,IAAI,aAAa,kBAAkB,EACvC,QACD,CAAC;AAGJ,KAAI,OAAO,KAAK,WAAW,KAAK,YAAY;AAC1C,EAAC,KAAK,aAAmC;AACzC,QAAM,KAAK,MAAM;;CAGnB,MAAM,WAAW,wBAAwB,QAAQ;CAEjD,MAAM,gBAAgB,MAAM,OAAO,cAAc,KAAK;EACpD,UAAU;EACV,QAAQ;EACT,CAAC;AAEF,KAAI,cAAc,KAAK,UAAU,GAAG;EAElC,MAAM,yBAAyB,cAAc,KAAK,QAC/C,iBAAiB,aAAa,OAAO,eACvC;AAED,OAAK,MAAM,gBAAgB,uBACzB,OAAM,OAAO,cAAc,OAAO,aAAa,GAAG;;CAItD,MAAM,sBAAsB,MAAM,WAAW,cAAc;EACzD,WAAW,KAAK;EAChB;EACA;EACA;EACA,MAAM,SAAS;EACf,QAAQ,SAAS;EACjB;EACD,CAAC;AAEF,KAAI,CAAC,oBACH,OAAM,IAAI,aAAa,8BAA8B,EACnD,gBAAgB,aAAa,IAC9B,CAAC;AAGJ,QAAO,KACL,iCAAiC,aAAa,GAAG,KAAK,SAAS,KAAK,KAAK,SAAS,SACnF;AAED,QAAO,oBAAoB,QAAQ;;AAGrC,MAAa,qBAAqB,OAChC,gBACA,mBACyB;CACzB,MAAM,eAAe,MAAM,oBAAoB,eAAe;AAE9D,KAAI,CAAC,aACH,OAAM,IAAI,aAAa,0BAA0B,EAC/C,gBACD,CAAC;AAKJ,KAAI,CAAC,aAAa,KAChB,QAAO;EACL,MAAM;EACN,QAAQ;EACT;CAGH,MAAM,sBAAsB,MAAM,WAAW,cAAc,EACzD,QAAQ,YACT,CAAC;AAEF,KAAI,CAAC,oBACH,OAAM,IAAI,aAAa,8BAA8B,EACnD,gBAAgB,aAAa,IAC9B,CAAC;AAGJ,QAAO,KACL,mCAAmC,oBAAoB,GAAG,KAAK,oBAAoB,MAAM,KAAK,KAAK,oBAAoB,MAAM,SAAS,iBAAiB,sBAAsB,eAAe,KAAK,KAClM;AAED,QAAO,oBAAoB,QAAQ;;AAGrC,MAAa,2BAA2B,OACtC,gBACA,QACA,QACA,mBACyB;CACzB,MAAM,eAAe,MAAM,oBAAoB,eAAe;AAE9D,KAAI,CAAC,aACH,OAAM,IAAI,aAAa,0BAA0B;EAC/C;EACA;EACD,CAAC;AAGJ,KAAI,CAAC,aAAa,KAChB,OAAM,IAAI,aAAa,+BAA+B;EACpD;EACA;EACA,gBAAgB,aAAa;EAC9B,CAAC;CAGJ,MAAM,sBAAsB,MAAM,WAAW,cAAc;EACzD;EACA;EACD,CAAC;AAEF,KAAI,CAAC,oBACH,OAAM,IAAI,aAAa,8BAA8B,EACnD,gBAAgB,aAAa,IAC9B,CAAC;CAGJ,MAAM,OAAO,MAAM,YAAY,OAAO;AAEtC,KAAI,CAAC,KACH,OAAM,IAAI,aAAa,kBAAkB;EACvC;EACA;EACD,CAAC;AAGJ,QAAO,KACL,wCAAwC,aAAa,GAAG,aAAa,SACtE;CAED,MAAM,YAAY;EAChB,IAAI,KAAK;EACT,UAAU,KAAK;EACf,OAAO,KAAK;EACZ,UAAU,aAAa,KAAK;EAC5B,uBAAM,IAAI,MAAM,EAAC,oBAAoB;EACrC,MAAM,GAAG,QAAQ,IAAI,QAAQ;EAC9B;AAED,SAAQ,QAAR;EACE,KAAK;AACH,SAAM,UAAU;IACd,GAAG;IACH,MAAM;IACN,kBAAkB,aAAa;IAC/B,uBAAuB,UAAU;IACjC,wBAAwB,UAAU;IACnC,CAAC;AACF;EACF,KAAK;AACH,SAAM,UAAU;IACd,GAAG;IACH,MAAM;IACN,kBAAkB,aAAa;IAC/B,kBAAkB,UAAU;IAC5B,gBAAgB,UAAU;IAC3B,CAAC;AACF;EACF,KAAK;AACH,SAAM,UAAU;IACd,GAAG;IACH,MAAM;IACN,kBAAkB,aAAa;IAC/B,WAAW,UAAU;IACrB,kBAAkB,UAAU;IAC7B,CAAC;AACF;EACF,QACE,QAAO,KAAK,kCAAkC,SAAS;;AAG3D,QAAO,oBAAoB,QAAQ;;AAGrC,MAAa,cAAc,OACzB,cAC2B;CAC3B,MAAM,SAAS,IAAI,OAAO,QAAQ,IAAI,kBAAmB;AAEzD,KAAI;EAGF,MAAM,kBAAiB,MADD,OAAO,QAAQ,MAAM,EACZ,KAAK,MACjC,WAAW,OAAO,SAAS,UAC7B;AAED,SAAO,iBAAiB,eAAe,KAAK;UACrC,OAAO;AACd,SAAO,MAAM,4BAA4B,MAAM;AAC/C,SAAO;;;AAeX,MAAa,aAAa,OACxB,UACA,cAC2B;CAC3B,MAAM,SAAS,IAAI,OAAO,QAAQ,IAAI,kBAAmB;AAEzD,KAAI;EAOF,MAAM,UAAS,MAJY,QAAQ,WACjC,SAAS,KAAK,YAAY,OAAO,OAAO,SAAS,QAAQ,CAAC,CAC3D,EAGE,KAAK,QAAQ,UAAU;AACtB,OAAI,OAAO,WAAW,YAAa,QAAO,OAAO;AACjD,UAAO,KACL,kBAAkB,SAAS,OAAO,uBAC/B,OAAO,QAAkB,WAAW,kBAExC;AACD,UAAO;IACP,CACD,QAAQ,UAAiC,UAAU,KAAK;EAG3D,MAAM,cAAc,OAAO,QACxB,KAAK,UAAU,OAAO,MAAM,eAAe,IAC5C,EACD;EAGD,IAAI,iBAAiB;EACrB,IAAI,eAA+C;AAEnD,MAAI,WAAW;GAEb,MAAM,mBAAkB,MADF,OAAO,QAAQ,MAAM,EACX,KAAK,MAClC,WAAW,OAAO,SAAS,UAC7B;AACD,OAAI,iBACF;QAAI,gBAAgB,YAAY;AAC9B,sBAAiB,gBAAgB;AACjC,oBAAe;eACN,gBAAgB,aAAa;AAGtC,sBAAiB,gBAAgB;AACjC,oBAAe;;;;EAMrB,MAAM,UAAyB,EAAE;AAEjC,OAAK,MAAM,SAAS,QAAQ;AAC1B,OAAI,CAAC,MAAM,MAAM,CAAC,MAAM,YACtB;GAGF,MAAM,gBAAgB,MAAM;GAC5B,IAAI,kBAAkB;GACtB,IAAI,aAAa;AAGjB,OAAI,iBAAiB,gBAAgB,iBAAiB,GAAG;AAEvD,sBAAmB,gBAAgB,iBAAkB;AACrD,iBAAa,gBAAgB;cAE7B,iBAAiB,YACjB,cAAc,KACd,iBAAiB,GACjB;IAEA,MAAM,aAAa,gBAAgB;AACnC,sBAAkB,iBAAiB;AACnC,iBAAa,gBAAgB;;AAI/B,gBAAa,KAAK,IAAI,YAAY,EAAE;AAEpC,WAAQ,MAAM,MAAM;IACH;IACf,iBAAiB;IACjB;IACY;IACZ,UAAU,MAAM;IACjB;;AAGH,SAAO;UACA,OAAO;AACd,SAAO,MAAM,0CAA0C,MAAM;AAC7D,QAAM,IAAI,MAAM,yCAAyC"}
|