@intlayer/backend 7.5.12 → 7.5.14
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/assets/utils/AI/askDocQuestion/embeddings/blog/en/per-component_vs_centralized_i18n.json +6158 -0
- package/dist/assets/utils/AI/askDocQuestion/embeddings/frequent_questions/en/error-vite-env-only.json +2054 -0
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -1
- package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
- package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
- package/dist/esm/controllers/github.controller.mjs.map +1 -1
- package/dist/esm/controllers/gitlab.controller.mjs.map +1 -1
- package/dist/esm/controllers/project.controller.mjs.map +1 -1
- package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
- package/dist/esm/controllers/tag.controller.mjs.map +1 -1
- package/dist/esm/controllers/user.controller.mjs.map +1 -1
- package/dist/esm/emails/InviteUserEmail.mjs.map +1 -1
- package/dist/esm/emails/MagicLinkEmail.mjs.map +1 -1
- package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
- package/dist/esm/emails/PasswordChangeConfirmation.mjs.map +1 -1
- package/dist/esm/emails/ResetUserPassword.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentCancellation.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentError.mjs.map +1 -1
- package/dist/esm/emails/SubscriptionPaymentSuccess.mjs.map +1 -1
- package/dist/esm/emails/ValidateUserEmail.mjs.map +1 -1
- package/dist/esm/emails/Welcome.mjs.map +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/services/bitbucket.service.mjs.map +1 -1
- package/dist/esm/services/github.service.mjs.map +1 -1
- package/dist/esm/services/gitlab.service.mjs.map +1 -1
- package/dist/esm/services/oAuth2.service.mjs.map +1 -1
- package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
- package/dist/esm/services/subscription.service.mjs.map +1 -1
- package/dist/esm/services/user.service.mjs.map +1 -1
- package/dist/esm/services/webhook.service.mjs.map +1 -1
- package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
- package/dist/esm/utils/AI/askDocQuestion/embeddings/blog/en/per-component_vs_centralized_i18n.json +6158 -0
- package/dist/esm/utils/AI/askDocQuestion/embeddings/frequent_questions/en/error-vite-env-only.json +2054 -0
- package/dist/esm/utils/AI/askDocQuestion/indexMarkdownFiles.mjs.map +1 -1
- package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -1
- package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
- package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -1
- package/dist/esm/utils/AI/auditTag/index.mjs.map +1 -1
- package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -1
- package/dist/esm/utils/AI/customQuery/index.mjs.map +1 -1
- package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -1
- package/dist/esm/utils/accessControl.mjs.map +1 -1
- package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
- package/dist/esm/utils/cors.mjs.map +1 -1
- package/dist/esm/utils/ensureArrayQueryFilter.mjs.map +1 -1
- package/dist/esm/utils/ensureMongoDocumentToObject.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
- package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
- package/dist/esm/utils/oAuth2.mjs.map +1 -1
- package/dist/esm/utils/permissions.mjs.map +1 -1
- package/dist/esm/utils/plan.mjs.map +1 -1
- package/dist/esm/utils/rateLimiter.mjs.map +1 -1
- package/dist/esm/utils/validation/validateArray.mjs.map +1 -1
- package/dist/esm/utils/validation/validateDictionary.mjs.map +1 -1
- package/dist/esm/utils/validation/validateOrganization.mjs.map +1 -1
- package/dist/esm/utils/validation/validateProject.mjs.map +1 -1
- package/dist/esm/utils/validation/validateString.mjs.map +1 -1
- package/dist/esm/utils/validation/validateTag.mjs.map +1 -1
- package/dist/esm/utils/validation/validateUser.mjs.map +1 -1
- package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
- package/dist/types/controllers/ai.controller.d.ts.map +1 -1
- package/dist/types/controllers/gitlab.controller.d.ts.map +1 -1
- package/dist/types/emails/InviteUserEmail.d.ts +4 -4
- package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
- package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
- package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
- package/dist/types/emails/PasswordChangeConfirmation.d.ts +4 -4
- package/dist/types/emails/ResetUserPassword.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
- package/dist/types/emails/SubscriptionPaymentError.d.ts.map +1 -1
- package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
- package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
- package/dist/types/emails/Welcome.d.ts +4 -4
- package/dist/types/models/dictionary.model.d.ts +4 -4
- package/dist/types/models/discussion.model.d.ts +3 -3
- package/dist/types/models/oAuth2.model.d.ts +3 -3
- package/dist/types/schemas/dictionary.schema.d.ts +6 -6
- package/dist/types/schemas/discussion.schema.d.ts +6 -6
- package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
- package/dist/types/schemas/organization.schema.d.ts +6 -6
- package/dist/types/schemas/plans.schema.d.ts +6 -6
- package/dist/types/schemas/plans.schema.d.ts.map +1 -1
- package/dist/types/schemas/project.schema.d.ts +6 -6
- package/dist/types/schemas/project.schema.d.ts.map +1 -1
- package/dist/types/schemas/session.schema.d.ts +6 -6
- package/dist/types/schemas/tag.schema.d.ts +6 -6
- package/dist/types/schemas/user.schema.d.ts +6 -6
- package/dist/types/schemas/user.schema.d.ts.map +1 -1
- package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +2 -2
- package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +2 -2
- package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +2 -2
- package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +2 -2
- package/package.json +20 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.controller.mjs","names":["projectService.findProjects","projectService.countProjects","project: ProjectData","projectService.createProject","projectService.updateProjectById","existingUsers: UserAndAdmin[]","userService.getUsersByIds","userMap: UserAndAdmin[]","user","formattedMembers: Types.ObjectId[]","formattedAdmin: Types.ObjectId[]","projectService.getProjectById","webhooksService.triggerAll","webhooksService.triggerSingleWebhook","projectService.deleteProjectById","ciService.getCIStatus","ciService.installCI"],"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { SessionModel } from '@models/session.model';\nimport * as ciService from '@services/ci.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport * as webhooksService from '@services/webhook.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getProjectFiltersAndPagination,\n type ProjectFiltersParams,\n} from '@utils/filtersAndPagination/getProjectFiltersAndPagination';\nimport { mapProjectsToAPI, mapProjectToAPI } from '@utils/mapper/project';\nimport { hasPermission } from '@utils/permissions';\nimport { getPlanDetails } from '@utils/plan';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { Types } from 'mongoose';\nimport type {\n ProjectAPI,\n ProjectConfiguration,\n ProjectCreationData,\n ProjectData,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\n\nexport type GetProjectsParams = FiltersAndPagination<ProjectFiltersParams>;\nexport type GetProjectsResult = PaginatedResponse<ProjectAPI>;\n\n/**\n * Retrieves a list of projects based on filters and pagination.\n */\nexport const getProjects = async (\n request: FastifyRequest<{ Querystring: GetProjectsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization && !roles?.includes('admin')) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const projects = await projectService.findProjects(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.locals,\n targetProjects: projects,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await projectService.countProjects(filters);\n\n const formattedProjects = mapProjectsToAPI(projects);\n\n const responseData = formatPaginatedResponse<ProjectAPI>({\n data: formattedProjects,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddProjectBody = ProjectCreationData;\nexport type AddProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Adds a new project to the database.\n */\nexport const addProject = async (\n request: FastifyRequest<{ Body: AddProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.locals || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!projectData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n const { plan } = organization;\n\n const planType = getPlanDetails(plan);\n\n if (planType.numberOfProjects) {\n const projectCount = await projectService.countProjects({\n organizationId: organization.id,\n });\n\n if (projectCount >= planType.numberOfProjects) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_PROJECT_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n }\n }\n\n const project: ProjectData = {\n membersIds: [user.id],\n adminsIds: [user.id],\n creatorId: user.id,\n organizationId: organization.id,\n ...projectData,\n };\n\n try {\n const newProject = await projectService.createProject(project);\n\n const formattedProject = mapProjectToAPI(newProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project created successfully',\n fr: 'Projet créé avec succès',\n es: 'Proyecto creado con éxito',\n }),\n description: t({\n en: 'Your project has been created successfully',\n fr: 'Votre projet a été créé avec succès',\n es: 'Su proyecto ha sido creado con éxito',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateProjectBody = Partial<ProjectAPI>;\nexport type UpdateProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Updates an existing project in the database.\n */\nexport const updateProject = async (\n request: FastifyRequest<{ Body: UpdateProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, project, user, roles } = request.locals || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (String(project.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedProject = await projectService.updateProjectById(\n project.id,\n projectData\n );\n\n const formattedProject = mapProjectToAPI(updatedProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project updated successfully',\n fr: 'Projet mis à jour avec succès',\n es: 'Proyecto actualizado con éxito',\n }),\n description: t({\n en: 'Your project has been updated successfully',\n fr: 'Votre projet a été mis à jour avec succès',\n es: 'Su proyecto ha sido actualizado con éxito',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype UserAndAdmin = { user: User; isAdmin: boolean };\nexport type ProjectMemberByIdOption = {\n userId: string | Types.ObjectId;\n isAdmin?: boolean;\n};\n\nexport type UpdateProjectMembersBody = Partial<{\n membersIds: ProjectMemberByIdOption[];\n}>;\nexport type UpdateProjectMembersResult = ResponseData<ProjectAPI>;\n\n/**\n * Update members to the dictionary in the database.\n */\nexport const updateProjectMembers = async (\n request: FastifyRequest<{ Body: UpdateProjectMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, organization, roles } = request.locals || {};\n const { membersIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_MEMBER'\n );\n }\n\n if (membersIds?.map((el) => el.isAdmin)?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_ADMIN'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingUsers: UserAndAdmin[] = [];\n\n if (membersIds) {\n const userIdList = membersIds\n ?.filter(\n (member) =>\n // Remove members that are not in the organization\n !organization?.membersIds.includes(member.userId as Types.ObjectId)\n )\n .map((member) => member.userId);\n\n const users = await userService.getUsersByIds(userIdList);\n\n if (users) {\n const userMap: UserAndAdmin[] = users.map((user) => ({\n user,\n isAdmin:\n membersIds.find(\n (member) => String(member.userId) === String(user.id)\n )?.isAdmin ?? false,\n }));\n\n existingUsers.push(...userMap);\n }\n }\n\n const formattedMembers: Types.ObjectId[] = existingUsers.map(\n (user) => user.user.id\n );\n const formattedAdmin: Types.ObjectId[] = existingUsers\n .filter((el) => el.isAdmin)\n .map((user) => user.user.id);\n\n const updatedOrganization = await projectService.updateProjectById(\n project.id,\n {\n ...project,\n membersIds: formattedMembers,\n adminsIds: formattedAdmin,\n }\n );\n\n const formattedProject = mapProjectToAPI(updatedOrganization);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project members updated successfully',\n fr: 'Membres du projet mis à jour avec succès',\n es: 'Miembros del proyecto actualizados con éxito',\n }),\n description: t({\n en: 'Your project members have been updated successfully',\n fr: 'Les membres de votre projet ont été mis à jour avec succès',\n es: 'Los miembros de su proyecto han sido actualizados con éxito',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushProjectConfigurationBody = ProjectConfiguration;\nexport type PushProjectConfigurationResult = ResponseData<ProjectConfiguration>;\n\n/**\n * Pushes a project configuration to the database.\n */\nexport const pushProjectConfiguration = async (\n request: FastifyRequest<{ Body: PushProjectConfigurationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const projectConfiguration = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectObject = await projectService.getProjectById(project.id);\n\n // Preserve existing API key if not provided in the update\n if (projectConfiguration.ai && projectObject.configuration?.ai?.apiKey) {\n projectConfiguration.ai.apiKey = projectObject.configuration.ai.apiKey;\n }\n\n projectObject.configuration = projectConfiguration;\n\n await projectObject.save();\n\n if (!projectObject.configuration) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_UPDATE_FAILED',\n {\n projectId: project.id,\n }\n );\n }\n\n const responseData = formatResponse<ProjectConfiguration>({\n message: t({\n en: 'Project configuration updated successfully',\n fr: 'Configuration du projet mise à jour avec succès',\n es: 'Configuración del proyecto actualizada con éxito',\n }),\n description: t({\n en: 'Your project configuration has been updated successfully',\n fr: 'La configuration du projet a été mise à jour avec succès',\n es: 'Su configuración del proyecto ha sido actualizada con éxito',\n }),\n data: mapProjectToAPI(projectObject) as ProjectConfiguration,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TriggerBuildResult = ResponseData<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n}>;\n\nexport type TriggerWebhookBody = {\n webhookIndex: number;\n};\n\nexport type TriggerWebhookResult = ResponseData<{\n target: string;\n success: boolean;\n message?: string;\n}>;\n\n/**\n * Triggers CI builds for a project (Git provider pipelines and webhooks)\n */\nexport const triggerBuild = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const results = await webhooksService.triggerAll(fullProject);\n\n const responseData = formatResponse<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n }>({\n message: t({\n en: 'Build triggers initiated',\n fr: 'Déclenchement des builds initié',\n es: 'Inicio de los triggers de build',\n }),\n description: t({\n en: 'CI pipelines and webhooks have been triggered',\n fr: 'Les pipelines CI et webhooks ont été déclenchés',\n es: 'Los pipelines CI y webhooks han sido activados',\n }),\n data: { results },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n/**\n * Triggers a single webhook by index\n */\nexport const triggerWebhook = async (\n request: FastifyRequest<{ Body: TriggerWebhookBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const { webhookIndex } = request.body;\n\n if (typeof webhookIndex !== 'number' || webhookIndex < 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const result = await webhooksService.triggerSingleWebhook(\n fullProject,\n webhookIndex\n );\n\n const responseData = formatResponse<{\n target: string;\n success: boolean;\n message?: string;\n }>({\n message: t({\n en: 'Webhook triggered',\n fr: 'Webhook déclenché',\n es: 'Webhook activado',\n }),\n description: t({\n en: `Webhook \"${result.target}\" has been triggered`,\n fr: `Le webhook \"${result.target}\" a été déclenché`,\n es: `El webhook \"${result.target}\" ha sido activado`,\n }),\n data: result,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Deletes a project from the database by its ID.\n */\nexport const deleteProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, project, session, roles } = _request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:admin'\n )({\n ..._request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectToDelete = await projectService.getProjectById(project.id);\n\n if (String(projectToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED',\n {\n projectId: project.id,\n }\n );\n }\n\n logger.info(`Project deleted: ${String(deletedProject.id)}`);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project deleted successfully',\n fr: 'Projet supprimé avec succès',\n es: 'Proyecto eliminado con éxito',\n }),\n description: t({\n en: 'Your project has been deleted successfully',\n fr: 'Votre projet a été supprimé avec succès',\n es: 'Su proyecto ha sido eliminado con éxito',\n }),\n data: mapProjectToAPI(deletedProject),\n });\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type SelectProjectParam = { projectId: string | Types.ObjectId };\nexport type SelectProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Select a project.\n */\nexport const selectProject = async (\n request: FastifyRequest<{ Params: SelectProjectParam }>,\n reply: FastifyReply\n) => {\n const { projectId } = request.params;\n const { session } = request.locals || {};\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_ID_NOT_FOUND'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n const project = await projectService.getProjectById(projectId);\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: String(projectId) } }\n );\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project selected successfully',\n fr: 'Projet sélectionné avec succès',\n es: 'Proyecto seleccionado con éxito',\n }),\n description: t({\n en: 'Your project has been selected successfully',\n fr: 'Votre projet a été sélectionné avec succès',\n es: 'Su proyecto ha sido seleccionado con éxito',\n }),\n data: mapProjectToAPI(project),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectProjectResult = ResponseData<null>;\n\n/**\n * Unselect a project.\n */\nexport const unselectProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { session } = _request.locals || {};\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Project unselected successfully',\n fr: 'Projet désélectionné avec succès',\n es: 'Proyecto deseleccionado con éxito',\n }),\n description: t({\n en: 'Your project has been unselected successfully',\n fr: 'Votre projet a été désélectionné avec succès',\n es: 'Su proyecto ha sido deseleccionado con éxito',\n }),\n data: null,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetCIConfigurationResult = ResponseData<ciService.CIStatus>;\n\n/**\n * Get CI configuration status for the current project\n */\nexport const getCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const ciStatus = await ciService.getCIStatus(project);\n\n const responseData = formatResponse<ciService.CIStatus>({\n data: ciStatus,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushCIConfigurationResult = ResponseData<{ success: boolean }>;\n\n/**\n * Push CI configuration file to the repository\n */\nexport const pushCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n await ciService.installCI(project);\n\n const responseData = formatResponse<{ success: boolean }>({\n message: t({\n en: 'CI configuration installed successfully',\n fr: 'Configuration CI installée avec succès',\n es: 'Configuración CI instalada con éxito',\n }),\n description: t({\n en: 'The CI workflow file has been added to your repository',\n fr: 'Le fichier de workflow CI a été ajouté à votre dépôt',\n es: 'El archivo de flujo de trabajo CI se ha agregado a su repositorio',\n }),\n data: { success: true },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"project.controller.mjs","names":["projectService.findProjects","projectService.countProjects","projectService.createProject","projectService.updateProjectById","userService.getUsersByIds","user","projectService.getProjectById","webhooksService.triggerAll","webhooksService.triggerSingleWebhook","projectService.deleteProjectById","ciService.getCIStatus","ciService.installCI"],"sources":["../../../src/controllers/project.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { SessionModel } from '@models/session.model';\nimport * as ciService from '@services/ci.service';\nimport * as projectService from '@services/project.service';\nimport * as userService from '@services/user.service';\nimport * as webhooksService from '@services/webhook.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getProjectFiltersAndPagination,\n type ProjectFiltersParams,\n} from '@utils/filtersAndPagination/getProjectFiltersAndPagination';\nimport { mapProjectsToAPI, mapProjectToAPI } from '@utils/mapper/project';\nimport { hasPermission } from '@utils/permissions';\nimport { getPlanDetails } from '@utils/plan';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { Types } from 'mongoose';\nimport type {\n ProjectAPI,\n ProjectConfiguration,\n ProjectCreationData,\n ProjectData,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\n\nexport type GetProjectsParams = FiltersAndPagination<ProjectFiltersParams>;\nexport type GetProjectsResult = PaginatedResponse<ProjectAPI>;\n\n/**\n * Retrieves a list of projects based on filters and pagination.\n */\nexport const getProjects = async (\n request: FastifyRequest<{ Querystring: GetProjectsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getProjectFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization && !roles?.includes('admin')) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const projects = await projectService.findProjects(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'project:read'\n )({\n ...request.locals,\n targetProjects: projects,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await projectService.countProjects(filters);\n\n const formattedProjects = mapProjectsToAPI(projects);\n\n const responseData = formatPaginatedResponse<ProjectAPI>({\n data: formattedProjects,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddProjectBody = ProjectCreationData;\nexport type AddProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Adds a new project to the database.\n */\nexport const addProject = async (\n request: FastifyRequest<{ Body: AddProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user, roles } = request.locals || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!projectData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'organization:admin'\n )({\n ...request.locals,\n targetOrganizations: [organization],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n const { plan } = organization;\n\n const planType = getPlanDetails(plan);\n\n if (planType.numberOfProjects) {\n const projectCount = await projectService.countProjects({\n organizationId: organization.id,\n });\n\n if (projectCount >= planType.numberOfProjects) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PLAN_PROJECT_LIMIT_REACHED',\n {\n organizationId: organization.id,\n }\n );\n }\n }\n\n const project: ProjectData = {\n membersIds: [user.id],\n adminsIds: [user.id],\n creatorId: user.id,\n organizationId: organization.id,\n ...projectData,\n };\n\n try {\n const newProject = await projectService.createProject(project);\n\n const formattedProject = mapProjectToAPI(newProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project created successfully',\n fr: 'Projet créé avec succès',\n es: 'Proyecto creado con éxito',\n }),\n description: t({\n en: 'Your project has been created successfully',\n fr: 'Votre projet a été créé avec succès',\n es: 'Su proyecto ha sido creado con éxito',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateProjectBody = Partial<ProjectAPI>;\nexport type UpdateProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Updates an existing project in the database.\n */\nexport const updateProject = async (\n request: FastifyRequest<{ Body: UpdateProjectBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, project, user, roles } = request.locals || {};\n const projectData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (String(project.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedProject = await projectService.updateProjectById(\n project.id,\n projectData\n );\n\n const formattedProject = mapProjectToAPI(updatedProject);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project updated successfully',\n fr: 'Projet mis à jour avec succès',\n es: 'Proyecto actualizado con éxito',\n }),\n description: t({\n en: 'Your project has been updated successfully',\n fr: 'Votre projet a été mis à jour avec succès',\n es: 'Su proyecto ha sido actualizado con éxito',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype UserAndAdmin = { user: User; isAdmin: boolean };\nexport type ProjectMemberByIdOption = {\n userId: string | Types.ObjectId;\n isAdmin?: boolean;\n};\n\nexport type UpdateProjectMembersBody = Partial<{\n membersIds: ProjectMemberByIdOption[];\n}>;\nexport type UpdateProjectMembersResult = ResponseData<ProjectAPI>;\n\n/**\n * Update members to the dictionary in the database.\n */\nexport const updateProjectMembers = async (\n request: FastifyRequest<{ Body: UpdateProjectMembersBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, organization, roles } = request.locals || {};\n const { membersIds } = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (membersIds?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_MEMBER'\n );\n }\n\n if (membersIds?.map((el) => el.isAdmin)?.length === 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_MUST_HAVE_ADMIN'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const existingUsers: UserAndAdmin[] = [];\n\n if (membersIds) {\n const userIdList = membersIds\n ?.filter(\n (member) =>\n // Remove members that are not in the organization\n !organization?.membersIds.includes(member.userId as Types.ObjectId)\n )\n .map((member) => member.userId);\n\n const users = await userService.getUsersByIds(userIdList);\n\n if (users) {\n const userMap: UserAndAdmin[] = users.map((user) => ({\n user,\n isAdmin:\n membersIds.find(\n (member) => String(member.userId) === String(user.id)\n )?.isAdmin ?? false,\n }));\n\n existingUsers.push(...userMap);\n }\n }\n\n const formattedMembers: Types.ObjectId[] = existingUsers.map(\n (user) => user.user.id\n );\n const formattedAdmin: Types.ObjectId[] = existingUsers\n .filter((el) => el.isAdmin)\n .map((user) => user.user.id);\n\n const updatedOrganization = await projectService.updateProjectById(\n project.id,\n {\n ...project,\n membersIds: formattedMembers,\n adminsIds: formattedAdmin,\n }\n );\n\n const formattedProject = mapProjectToAPI(updatedOrganization);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project members updated successfully',\n fr: 'Membres du projet mis à jour avec succès',\n es: 'Miembros del proyecto actualizados con éxito',\n }),\n description: t({\n en: 'Your project members have been updated successfully',\n fr: 'Les membres de votre projet ont été mis à jour avec succès',\n es: 'Los miembros de su proyecto han sido actualizados con éxito',\n }),\n data: formattedProject,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushProjectConfigurationBody = ProjectConfiguration;\nexport type PushProjectConfigurationResult = ResponseData<ProjectConfiguration>;\n\n/**\n * Pushes a project configuration to the database.\n */\nexport const pushProjectConfiguration = async (\n request: FastifyRequest<{ Body: PushProjectConfigurationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project, roles } = request.locals || {};\n const projectConfiguration = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectObject = await projectService.getProjectById(project.id);\n\n // Preserve existing API key if not provided in the update\n if (projectConfiguration.ai && projectObject.configuration?.ai?.apiKey) {\n projectConfiguration.ai.apiKey = projectObject.configuration.ai.apiKey;\n }\n\n projectObject.configuration = projectConfiguration;\n\n await projectObject.save();\n\n if (!projectObject.configuration) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_UPDATE_FAILED',\n {\n projectId: project.id,\n }\n );\n }\n\n const responseData = formatResponse<ProjectConfiguration>({\n message: t({\n en: 'Project configuration updated successfully',\n fr: 'Configuration du projet mise à jour avec succès',\n es: 'Configuración del proyecto actualizada con éxito',\n }),\n description: t({\n en: 'Your project configuration has been updated successfully',\n fr: 'La configuration du projet a été mise à jour avec succès',\n es: 'Su configuración del proyecto ha sido actualizada con éxito',\n }),\n data: mapProjectToAPI(projectObject) as ProjectConfiguration,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TriggerBuildResult = ResponseData<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n}>;\n\nexport type TriggerWebhookBody = {\n webhookIndex: number;\n};\n\nexport type TriggerWebhookResult = ResponseData<{\n target: string;\n success: boolean;\n message?: string;\n}>;\n\n/**\n * Triggers CI builds for a project (Git provider pipelines and webhooks)\n */\nexport const triggerBuild = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const results = await webhooksService.triggerAll(fullProject);\n\n const responseData = formatResponse<{\n results: Array<{\n target: string;\n success: boolean;\n message?: string;\n }>;\n }>({\n message: t({\n en: 'Build triggers initiated',\n fr: 'Déclenchement des builds initié',\n es: 'Inicio de los triggers de build',\n }),\n description: t({\n en: 'CI pipelines and webhooks have been triggered',\n fr: 'Les pipelines CI et webhooks ont été déclenchés',\n es: 'Los pipelines CI y webhooks han sido activados',\n }),\n data: { results },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\n/**\n * Triggers a single webhook by index\n */\nexport const triggerWebhook = async (\n request: FastifyRequest<{ Body: TriggerWebhookBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, roles } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:write'\n )({\n ...request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const { webhookIndex } = request.body;\n\n if (typeof webhookIndex !== 'number' || webhookIndex < 0) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'INVALID_REQUEST_BODY'\n );\n }\n\n // Get full project with all relations\n const fullProject = await projectService.getProjectById(project.id);\n const result = await webhooksService.triggerSingleWebhook(\n fullProject,\n webhookIndex\n );\n\n const responseData = formatResponse<{\n target: string;\n success: boolean;\n message?: string;\n }>({\n message: t({\n en: 'Webhook triggered',\n fr: 'Webhook déclenché',\n es: 'Webhook activado',\n }),\n description: t({\n en: `Webhook \"${result.target}\" has been triggered`,\n fr: `Le webhook \"${result.target}\" a été déclenché`,\n es: `El webhook \"${result.target}\" ha sido activado`,\n }),\n data: result,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Deletes a project from the database by its ID.\n */\nexport const deleteProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, project, session, roles } = _request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n if (\n !hasPermission(\n roles || [],\n 'project:admin'\n )({\n ..._request.locals,\n targetProjectIds: [String(project.id)],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const projectToDelete = await projectService.getProjectById(project.id);\n\n if (String(projectToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_IN_ORGANIZATION'\n );\n }\n\n const deletedProject = await projectService.deleteProjectById(project.id);\n\n if (!deletedProject) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED',\n {\n projectId: project.id,\n }\n );\n }\n\n logger.info(`Project deleted: ${String(deletedProject.id)}`);\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project deleted successfully',\n fr: 'Projet supprimé avec succès',\n es: 'Proyecto eliminado con éxito',\n }),\n description: t({\n en: 'Your project has been deleted successfully',\n fr: 'Votre projet a été supprimé avec succès',\n es: 'Su proyecto ha sido eliminado con éxito',\n }),\n data: mapProjectToAPI(deletedProject),\n });\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type SelectProjectParam = { projectId: string | Types.ObjectId };\nexport type SelectProjectResult = ResponseData<ProjectAPI>;\n\n/**\n * Select a project.\n */\nexport const selectProject = async (\n request: FastifyRequest<{ Params: SelectProjectParam }>,\n reply: FastifyReply\n) => {\n const { projectId } = request.params;\n const { session } = request.locals || {};\n\n if (!projectId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_ID_NOT_FOUND'\n );\n }\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n const project = await projectService.getProjectById(projectId);\n\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: String(projectId) } }\n );\n\n const responseData = formatResponse<ProjectAPI>({\n message: t({\n en: 'Project selected successfully',\n fr: 'Projet sélectionné avec succès',\n es: 'Proyecto seleccionado con éxito',\n }),\n description: t({\n en: 'Your project has been selected successfully',\n fr: 'Votre projet a été sélectionné avec succès',\n es: 'Su proyecto ha sido seleccionado con éxito',\n }),\n data: mapProjectToAPI(project),\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UnselectProjectResult = ResponseData<null>;\n\n/**\n * Unselect a project.\n */\nexport const unselectProject = async (\n _request: FastifyRequest,\n reply: FastifyReply\n) => {\n const { session } = _request.locals || {};\n\n if (typeof session === 'undefined') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SESSION_NOT_DEFINED'\n );\n }\n\n try {\n await SessionModel.updateOne(\n { _id: session.id },\n { $set: { activeProjectId: null } }\n );\n\n const responseData = formatResponse<null>({\n message: t({\n en: 'Project unselected successfully',\n fr: 'Projet désélectionné avec succès',\n es: 'Proyecto deseleccionado con éxito',\n }),\n description: t({\n en: 'Your project has been unselected successfully',\n fr: 'Votre projet a été désélectionné avec succès',\n es: 'Su proyecto ha sido deseleccionado con éxito',\n }),\n data: null,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetCIConfigurationResult = ResponseData<ciService.CIStatus>;\n\n/**\n * Get CI configuration status for the current project\n */\nexport const getCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const ciStatus = await ciService.getCIStatus(project);\n\n const responseData = formatResponse<ciService.CIStatus>({\n data: ciStatus,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type PushCIConfigurationResult = ResponseData<{ success: boolean }>;\n\n/**\n * Push CI configuration file to the repository\n */\nexport const pushCIConfiguration = async (\n request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n await ciService.installCI(project);\n\n const responseData = formatResponse<{ success: boolean }>({\n message: t({\n en: 'CI configuration installed successfully',\n fr: 'Configuration CI installée avec succès',\n es: 'Configuración CI instalada con éxito',\n }),\n description: t({\n en: 'The CI workflow file has been added to your repository',\n fr: 'Le fichier de workflow CI a été ajouté à votre dépôt',\n es: 'El archivo de flujo de trabajo CI se ha agregado a su repositorio',\n }),\n data: { success: true },\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stripe.controller.mjs","names":["subscriptionService.getPricing","subscriptionService.getCouponId","discounts: Stripe.SubscriptionCreateParams.Discount[]","subscriptionService.cancelSubscription","emailService.sendEmail"],"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types';\nimport * as emailService from '@services/email.service';\nimport * as subscriptionService from '@services/subscription.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport { Stripe } from 'stripe';\nimport type { Organization } from '@/types/organization.types';\n\nexport type GetPricingBody = {\n priceIds: string[];\n promoCode?: string;\n};\n\nexport type GetPricingResult = ResponseData<subscriptionService.PricingResult>;\n\n/**\n * Simulate pricing for a given set of prices and a promotion code.\n *\n * @param request - The request object containing the price IDs and promotion code.\n * @param reply - The response object to send the simulated pricing result.\n */\nexport const getPricing = async (\n request: FastifyRequest<{ Body: GetPricingBody }>,\n reply: FastifyReply\n) => {\n const { priceIds, promoCode } = request.body;\n\n const pricingResult = await subscriptionService.getPricing(\n priceIds,\n promoCode\n );\n\n const formattedPricingResult =\n formatResponse<subscriptionService.PricingResult>({\n data: pricingResult,\n });\n\n reply.code(200).send(formattedPricingResult);\n};\n\nexport type GetCheckoutSessionBody = {\n priceId: string;\n promoCode?: string;\n};\n\nexport type GetCheckoutSessionResult = ResponseData<{\n subscription: Stripe.Response<Stripe.Subscription>;\n clientSecret: string;\n}>;\n\n/**\n * Handles subscription creation or update with Stripe and returns a ClientSecret.\n */\nexport const getSubscription = async (\n request: FastifyRequest<{ Body: GetCheckoutSessionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n try {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n // Extract organization and user from request locals (set by authentication middleware)\n const { organization, user } = request.locals || {};\n // Get the price ID (Stripe Price ID) from the request body\n const { priceId, promoCode } = request.body;\n\n // Validate that the organization exists\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n // Validate that the user exists\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const { period, type } = retrievePlanInformation(priceId);\n\n if (\n organization.plan?.subscriptionId &&\n organization.plan?.type === type &&\n organization.plan?.period === period &&\n organization.plan?.status === 'active'\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ALREADY_SUBSCRIBED',\n {\n organizationId: organization.id,\n }\n );\n }\n\n // Attempt to retrieve the Stripe customer ID from the organization's plan\n let customerId = organization.plan?.customerId;\n\n if (!customerId) {\n // If no customer ID exists, create a new Stripe customer for the organization\n const customer = await stripe.customers.create({\n metadata: {\n organizationId: String(organization.id),\n userId: String(user.id),\n // Include the locale for potential localization\n locale: (request.locals as unknown as { locale: Locale }).locale,\n },\n });\n customerId = customer.id;\n }\n\n const promoCodeId = promoCode\n ? await subscriptionService.getCouponId(promoCode)\n : null;\n\n const discounts: Stripe.SubscriptionCreateParams.Discount[] = promoCodeId\n ? [{ coupon: promoCodeId }]\n : [];\n\n // If no subscription exists, create a new one\n const subscription = await stripe.subscriptions.create({\n customer: customerId, // Associate the subscription with the customer\n items: [{ price: priceId }], // Set the price ID for the subscription\n expand: ['latest_invoice.confirmation_secret'],\n payment_settings: {\n payment_method_types: ['card'], // Specify payment method types\n },\n payment_behavior: 'default_incomplete', // Create the subscription in an incomplete state until payment is confirmed\n discounts,\n });\n\n // Handle subscription creation failure\n if (!subscription) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n }\n\n const clientSecret = (\n subscription.latest_invoice as Stripe.Invoice & {\n confirmation_secret?: { client_secret: string };\n }\n )?.confirmation_secret?.client_secret;\n\n // Handle subscription creation failure\n if (!clientSecret) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n }\n\n // Prepare the response data with subscription details\n const responseData = formatResponse<GetCheckoutSessionResult['data']>({\n data: { subscription, clientSecret },\n });\n\n // Send the response back to the client\n return reply.send(responseData);\n } catch (error) {\n // Handle any errors that occur during the process\n\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype CancelSubscriptionData = Organization['plan'];\nexport type CancelSubscriptionResult = ResponseData<CancelSubscriptionData>;\n\n/**\n * Cancels a subscription for an organization.\n */\nexport const cancelSubscription = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Extract the organization and user from the request locals\n // These are typically set by authentication middleware earlier in the request pipeline\n const { organization, user } = _request.locals || {};\n\n // Validate that the organization exists\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n // Validate that the user exists\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n // Check if the organization has an active subscription to cancel\n if (!organization.plan?.subscriptionId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n }\n\n // Cancel the subscription on Stripe immediately using the subscription ID\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n\n // Update the organization's plan in the database to reflect the cancellation\n const plan = await subscriptionService.cancelSubscription(\n organization.plan.subscriptionId,\n String(organization.id)\n );\n\n // If the plan could not be updated in the database, handle the error\n if (!plan) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n }\n\n // Prepare a formatted response with a success message and the updated plan data\n const formattedPlan = formatResponse<CancelSubscriptionData>({\n message: t({\n en: 'Subscription cancelled successfully',\n fr: 'Souscription annulée avec succès',\n es: 'Suscripción cancelada con éxito',\n }),\n description: t({\n en: 'Your subscription has been cancelled successfully',\n fr: 'Votre souscription a été annulée avec succès',\n es: 'Su suscripción ha sido cancelada con éxito',\n }),\n data: plan!,\n });\n\n // Send the response back to the client\n reply.send(formattedPlan);\n\n await emailService.sendEmail({\n type: 'subscriptionPaymentCancellation',\n to: user.email,\n email: user.email,\n cancellationDate: new Date().toLocaleDateString(),\n reactivateLink: `${process.env.APP_URL}/pricing`,\n username: user.name,\n organizationName: organization.name,\n planName: plan.type,\n });\n } catch (error) {\n // Handle any errors that occur during the cancellation process\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stripe.controller.mjs","names":["subscriptionService.getPricing","subscriptionService.getCouponId","subscriptionService.cancelSubscription","emailService.sendEmail"],"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types';\nimport * as emailService from '@services/email.service';\nimport * as subscriptionService from '@services/subscription.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport { Stripe } from 'stripe';\nimport type { Organization } from '@/types/organization.types';\n\nexport type GetPricingBody = {\n priceIds: string[];\n promoCode?: string;\n};\n\nexport type GetPricingResult = ResponseData<subscriptionService.PricingResult>;\n\n/**\n * Simulate pricing for a given set of prices and a promotion code.\n *\n * @param request - The request object containing the price IDs and promotion code.\n * @param reply - The response object to send the simulated pricing result.\n */\nexport const getPricing = async (\n request: FastifyRequest<{ Body: GetPricingBody }>,\n reply: FastifyReply\n) => {\n const { priceIds, promoCode } = request.body;\n\n const pricingResult = await subscriptionService.getPricing(\n priceIds,\n promoCode\n );\n\n const formattedPricingResult =\n formatResponse<subscriptionService.PricingResult>({\n data: pricingResult,\n });\n\n reply.code(200).send(formattedPricingResult);\n};\n\nexport type GetCheckoutSessionBody = {\n priceId: string;\n promoCode?: string;\n};\n\nexport type GetCheckoutSessionResult = ResponseData<{\n subscription: Stripe.Response<Stripe.Subscription>;\n clientSecret: string;\n}>;\n\n/**\n * Handles subscription creation or update with Stripe and returns a ClientSecret.\n */\nexport const getSubscription = async (\n request: FastifyRequest<{ Body: GetCheckoutSessionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n try {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n // Extract organization and user from request locals (set by authentication middleware)\n const { organization, user } = request.locals || {};\n // Get the price ID (Stripe Price ID) from the request body\n const { priceId, promoCode } = request.body;\n\n // Validate that the organization exists\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n // Validate that the user exists\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n const { period, type } = retrievePlanInformation(priceId);\n\n if (\n organization.plan?.subscriptionId &&\n organization.plan?.type === type &&\n organization.plan?.period === period &&\n organization.plan?.status === 'active'\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ALREADY_SUBSCRIBED',\n {\n organizationId: organization.id,\n }\n );\n }\n\n // Attempt to retrieve the Stripe customer ID from the organization's plan\n let customerId = organization.plan?.customerId;\n\n if (!customerId) {\n // If no customer ID exists, create a new Stripe customer for the organization\n const customer = await stripe.customers.create({\n metadata: {\n organizationId: String(organization.id),\n userId: String(user.id),\n // Include the locale for potential localization\n locale: (request.locals as unknown as { locale: Locale }).locale,\n },\n });\n customerId = customer.id;\n }\n\n const promoCodeId = promoCode\n ? await subscriptionService.getCouponId(promoCode)\n : null;\n\n const discounts: Stripe.SubscriptionCreateParams.Discount[] = promoCodeId\n ? [{ coupon: promoCodeId }]\n : [];\n\n // If no subscription exists, create a new one\n const subscription = await stripe.subscriptions.create({\n customer: customerId, // Associate the subscription with the customer\n items: [{ price: priceId }], // Set the price ID for the subscription\n expand: ['latest_invoice.confirmation_secret'],\n payment_settings: {\n payment_method_types: ['card'], // Specify payment method types\n },\n payment_behavior: 'default_incomplete', // Create the subscription in an incomplete state until payment is confirmed\n discounts,\n });\n\n // Handle subscription creation failure\n if (!subscription) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n }\n\n const clientSecret = (\n subscription.latest_invoice as Stripe.Invoice & {\n confirmation_secret?: { client_secret: string };\n }\n )?.confirmation_secret?.client_secret;\n\n // Handle subscription creation failure\n if (!clientSecret) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n }\n\n // Prepare the response data with subscription details\n const responseData = formatResponse<GetCheckoutSessionResult['data']>({\n data: { subscription, clientSecret },\n });\n\n // Send the response back to the client\n return reply.send(responseData);\n } catch (error) {\n // Handle any errors that occur during the process\n\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\ntype CancelSubscriptionData = Organization['plan'];\nexport type CancelSubscriptionResult = ResponseData<CancelSubscriptionData>;\n\n/**\n * Cancels a subscription for an organization.\n */\nexport const cancelSubscription = async (\n _request: FastifyRequest,\n reply: FastifyReply\n): Promise<void> => {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\n try {\n // Extract the organization and user from the request locals\n // These are typically set by authentication middleware earlier in the request pipeline\n const { organization, user } = _request.locals || {};\n\n // Validate that the organization exists\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_FOUND'\n );\n }\n\n // Validate that the user exists\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n // Check if the organization has an active subscription to cancel\n if (!organization.plan?.subscriptionId) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n }\n\n // Cancel the subscription on Stripe immediately using the subscription ID\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n\n // Update the organization's plan in the database to reflect the cancellation\n const plan = await subscriptionService.cancelSubscription(\n organization.plan.subscriptionId,\n String(organization.id)\n );\n\n // If the plan could not be updated in the database, handle the error\n if (!plan) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n }\n\n // Prepare a formatted response with a success message and the updated plan data\n const formattedPlan = formatResponse<CancelSubscriptionData>({\n message: t({\n en: 'Subscription cancelled successfully',\n fr: 'Souscription annulée avec succès',\n es: 'Suscripción cancelada con éxito',\n }),\n description: t({\n en: 'Your subscription has been cancelled successfully',\n fr: 'Votre souscription a été annulée avec succès',\n es: 'Su suscripción ha sido cancelada con éxito',\n }),\n data: plan!,\n });\n\n // Send the response back to the client\n reply.send(formattedPlan);\n\n await emailService.sendEmail({\n type: 'subscriptionPaymentCancellation',\n to: user.email,\n email: user.email,\n cancellationDate: new Date().toLocaleDateString(),\n reactivateLink: `${process.env.APP_URL}/pricing`,\n username: user.name,\n organizationName: organization.name,\n planName: plan.type,\n });\n } catch (error) {\n // Handle any errors that occur during the cancellation process\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tag.controller.mjs","names":["tagService.findTags","tagService.countTags","tag: TagData","tagService.createTag","tagService.getTagById","tagService.updateTagById","tagService.deleteTagById"],"sources":["../../../src/controllers/tag.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport * as tagService from '@services/tag.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getTagFiltersAndPagination,\n type TagFiltersParams,\n} from '@utils/filtersAndPagination/getTagFiltersAndPagination';\nimport { mapTagsToAPI, mapTagToAPI } from '@utils/mapper/tag';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type {\n Tag,\n TagAPI,\n TagCreationData,\n TagData,\n TagSchema,\n} from '@/types/tag.types';\n\nexport type GetTagsParams = FiltersAndPagination<TagFiltersParams>;\nexport type GetTagsResult = PaginatedResponse<TagAPI>;\n\n/**\n * Retrieves a list of tags based on filters and pagination.\n */\nexport const getTags = async (\n request: FastifyRequest<{ Querystring: GetTagsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getTagFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const tags = await tagService.findTags(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'tag:read'\n )({\n ...request.locals,\n targetTags: tags,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await tagService.countTags(filters);\n\n const formattedTags = mapTagsToAPI(tags);\n\n const responseData = formatPaginatedResponse<TagAPI>({\n data: formattedTags,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddTagBody = TagCreationData;\nexport type AddTagResult = ResponseData<TagAPI>;\n\n/**\n * Adds a new tag to the database.\n */\nexport const addTag = async (\n request: FastifyRequest<{ Body: AddTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, project, user, roles } = request.locals || {};\n const tagData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!tagData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n const tag: TagData = {\n creatorId: user.id,\n organizationId: organization.id,\n projectId: project.id,\n ...tagData,\n };\n\n if (\n !hasPermission(\n roles || [],\n 'tag:admin'\n )({\n ...request.locals,\n targetTags: [tag as Tag],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const newTag = await tagService.createTag(tag);\n\n const formattedTag = mapTagToAPI(newTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag created successfully',\n fr: 'Tag créé avec succès',\n es: 'Tag creado con éxito',\n }),\n description: t({\n en: 'Your tag has been created successfully',\n fr: 'Votre tag a été créé avec succès',\n es: 'Su tag ha sido creado con éxito',\n }),\n data: formattedTag,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateTagParams = { tagId: string | Tag['id'] };\nexport type UpdateTagBody = Partial<TagData>;\nexport type UpdateTagResult = ResponseData<TagAPI>;\n\n/**\n * Updates an existing tag in the database.\n */\nexport const updateTag = async (\n request: FastifyRequest<{ Params: UpdateTagParams; Body: UpdateTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { tagId } = request.params;\n const { organization, user, roles } = request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const tag = {\n _id: tagId,\n name: request.body.name,\n key: request.body.key,\n description: request.body.description,\n instructions: request.body.instructions,\n } as Partial<TagSchema> & { _id: Tag['id'] };\n\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles || [],\n 'tag:write'\n )({\n ...request.locals,\n targetTags: [tagToDelete],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TAG_NOT_IN_ORGANIZATION'\n );\n }\n\n const updatedTag = await tagService.updateTagById(tag._id, tag);\n\n const formattedTag = mapTagToAPI(updatedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag updated successfully',\n fr: 'Tag mis à jour avec succès',\n es: 'Tag actualizado con éxito',\n }),\n description: t({\n en: 'Your tag has been updated successfully',\n fr: 'Votre tag a été mis à jour avec succès',\n es: 'Su tag ha sido actualizado con éxito',\n }),\n data: formattedTag,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteTagParams = { tagId: string | Tag['id'] };\nexport type DeleteTagResult = ResponseData<TagAPI>;\n\n/**\n * Deletes a tag from the database by its ID.\n */\nexport const deleteTag = async (\n request: FastifyRequest<{ Params: DeleteTagParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, roles } = request.locals || {};\n const { tagId } = request.params;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!tagId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'TAG_ID_NOT_FOUND');\n }\n\n try {\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles || [],\n 'tag:admin'\n )({\n ...request.locals,\n targetTags: [tagToDelete],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TAG_NOT_IN_ORGANIZATION'\n );\n }\n\n const deletedTag = await tagService.deleteTagById(tagId);\n\n if (!deletedTag) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'TAG_NOT_FOUND', {\n tagId,\n });\n }\n\n logger.info(`Tag deleted: ${String(deletedTag.id)}`);\n\n const formattedTag = mapTagToAPI(deletedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag deleted successfully',\n fr: 'Tag supprimé avec succès',\n es: 'Tag eliminado con éxito',\n }),\n description: t({\n en: 'Your tag has been deleted successfully',\n fr: 'Votre tag a été supprimé avec succès',\n es: 'Su tag ha sido eliminado con éxito',\n }),\n data: formattedTag,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tag.controller.mjs","names":["tagService.findTags","tagService.countTags","tagService.createTag","tagService.getTagById","tagService.updateTagById","tagService.deleteTagById"],"sources":["../../../src/controllers/tag.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport * as tagService from '@services/tag.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getTagFiltersAndPagination,\n type TagFiltersParams,\n} from '@utils/filtersAndPagination/getTagFiltersAndPagination';\nimport { mapTagsToAPI, mapTagToAPI } from '@utils/mapper/tag';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type {\n Tag,\n TagAPI,\n TagCreationData,\n TagData,\n TagSchema,\n} from '@/types/tag.types';\n\nexport type GetTagsParams = FiltersAndPagination<TagFiltersParams>;\nexport type GetTagsResult = PaginatedResponse<TagAPI>;\n\n/**\n * Retrieves a list of tags based on filters and pagination.\n */\nexport const getTags = async (\n request: FastifyRequest<{ Querystring: GetTagsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getTagFiltersAndPagination(request);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const tags = await tagService.findTags(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'tag:read'\n )({\n ...request.locals,\n targetTags: tags,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await tagService.countTags(filters);\n\n const formattedTags = mapTagsToAPI(tags);\n\n const responseData = formatPaginatedResponse<TagAPI>({\n data: formattedTags,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AddTagBody = TagCreationData;\nexport type AddTagResult = ResponseData<TagAPI>;\n\n/**\n * Adds a new tag to the database.\n */\nexport const addTag = async (\n request: FastifyRequest<{ Body: AddTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, project, user, roles } = request.locals || {};\n const tagData = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!project) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_NOT_DEFINED'\n );\n }\n\n if (!tagData) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PROJECT_DATA_NOT_FOUND'\n );\n }\n\n const tag: TagData = {\n creatorId: user.id,\n organizationId: organization.id,\n projectId: project.id,\n ...tagData,\n };\n\n if (\n !hasPermission(\n roles || [],\n 'tag:admin'\n )({\n ...request.locals,\n targetTags: [tag as Tag],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const newTag = await tagService.createTag(tag);\n\n const formattedTag = mapTagToAPI(newTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag created successfully',\n fr: 'Tag créé avec succès',\n es: 'Tag creado con éxito',\n }),\n description: t({\n en: 'Your tag has been created successfully',\n fr: 'Votre tag a été créé avec succès',\n es: 'Su tag ha sido creado con éxito',\n }),\n data: formattedTag,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateTagParams = { tagId: string | Tag['id'] };\nexport type UpdateTagBody = Partial<TagData>;\nexport type UpdateTagResult = ResponseData<TagAPI>;\n\n/**\n * Updates an existing tag in the database.\n */\nexport const updateTag = async (\n request: FastifyRequest<{ Params: UpdateTagParams; Body: UpdateTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { tagId } = request.params;\n const { organization, user, roles } = request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n try {\n const tag = {\n _id: tagId,\n name: request.body.name,\n key: request.body.key,\n description: request.body.description,\n instructions: request.body.instructions,\n } as Partial<TagSchema> & { _id: Tag['id'] };\n\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles || [],\n 'tag:write'\n )({\n ...request.locals,\n targetTags: [tagToDelete],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TAG_NOT_IN_ORGANIZATION'\n );\n }\n\n const updatedTag = await tagService.updateTagById(tag._id, tag);\n\n const formattedTag = mapTagToAPI(updatedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag updated successfully',\n fr: 'Tag mis à jour avec succès',\n es: 'Tag actualizado con éxito',\n }),\n description: t({\n en: 'Your tag has been updated successfully',\n fr: 'Votre tag a été mis à jour avec succès',\n es: 'Su tag ha sido actualizado con éxito',\n }),\n data: formattedTag,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteTagParams = { tagId: string | Tag['id'] };\nexport type DeleteTagResult = ResponseData<TagAPI>;\n\n/**\n * Deletes a tag from the database by its ID.\n */\nexport const deleteTag = async (\n request: FastifyRequest<{ Params: DeleteTagParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, organization, roles } = request.locals || {};\n const { tagId } = request.params;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (!organization) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'ORGANIZATION_NOT_DEFINED'\n );\n }\n\n if (!tagId) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'TAG_ID_NOT_FOUND');\n }\n\n try {\n const tagToDelete = await tagService.getTagById(tagId);\n\n if (\n !hasPermission(\n roles || [],\n 'tag:admin'\n )({\n ...request.locals,\n targetTags: [tagToDelete],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n if (String(tagToDelete.organizationId) !== String(organization.id)) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'TAG_NOT_IN_ORGANIZATION'\n );\n }\n\n const deletedTag = await tagService.deleteTagById(tagId);\n\n if (!deletedTag) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'TAG_NOT_FOUND', {\n tagId,\n });\n }\n\n logger.info(`Tag deleted: ${String(deletedTag.id)}`);\n\n const formattedTag = mapTagToAPI(deletedTag);\n\n const responseData = formatResponse<TagAPI>({\n message: t({\n en: 'Tag deleted successfully',\n fr: 'Tag supprimé avec succès',\n es: 'Tag eliminado con éxito',\n }),\n description: t({\n en: 'Your tag has been deleted successfully',\n fr: 'Votre tag a été supprimé avec succès',\n es: 'Su tag ha sido eliminado con éxito',\n }),\n data: formattedTag,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.controller.mjs","names":["user: User | undefined","userService.createUser","userService.findUsers","userService.countUsers","userService.getUserById","userService.getUserByEmail","userService.updateUserById","userService.deleteUser","clients: Array<{\n id: number;\n userId: string;\n res: { raw: FastifyReply['raw'] };\n}>"],"sources":["../../../src/controllers/user.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { sendEmail } from '@services/email.service';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getUserFiltersAndPagination,\n type UserFiltersParam,\n} from '@utils/filtersAndPagination/getUserFiltersAndPagination';\nimport { mapUsersToAPI, mapUserToAPI } from '@utils/mapper/user';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type CreateUserBody = { email: string; password?: string };\nexport type CreateUserResult = ResponseData<UserAPI>;\n\n/**\n * Creates a new user.\n */\nexport const createUser = async (\n request: FastifyRequest<{ Body: User }>,\n reply: FastifyReply\n): Promise<void> => {\n const user: User | undefined = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const newUser = await userService.createUser(user);\n\n await sendEmail({\n type: 'welcome',\n to: newUser.email,\n username: newUser.name,\n loginLink: `${process.env.APP_URL}/auth/login`,\n });\n\n const formattedUser = mapUserToAPI(newUser);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'User created',\n fr: 'Utilisateur créé',\n es: 'Usuario creado',\n }),\n description: t({\n en: 'User created successfully',\n fr: 'Utilisateur créé avec succès',\n es: 'Usuario creado con éxito',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetUsersParams = FiltersAndPagination<UserFiltersParam>;\nexport type GetUsersResult = PaginatedResponse<UserAPI>;\n\n/**\n * Retrieves a list of users based on filters and pagination.\n */\nexport const getUsers = async (\n request: FastifyRequest<{ Querystring: GetUsersParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getUserFiltersAndPagination(request);\n\n try {\n const users = await userService.findUsers(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'user:read'\n )({\n ...request.locals,\n targetUsers: users,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await userService.countUsers(filters);\n\n const formattedUsers = mapUsersToAPI(users);\n\n const responseData = formatPaginatedResponse<UserAPI>({\n data: formattedUsers,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetUserByIdParams = { userId: UserAPI['id'] };\nexport type GetUserByIdResult = ResponseData<UserAPI>;\n\nexport const getUserById = async (\n request: FastifyRequest<{ Params: GetUserByIdParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { userId } = request.params;\n\n try {\n const user = await userService.getUserById(userId);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n const formattedUser = mapUserToAPI(user);\n const responseData = formatResponse<UserAPI>({ data: formattedUser });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetUserByEmailParams = { email: string };\nexport type GetUserByEmailResult = ResponseData<UserAPI>;\n\nexport const getUserByEmail = async (\n request: FastifyRequest<{ Params: GetUserByEmailParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { email } = request.params;\n const { roles } = request.locals || {};\n\n try {\n const user = await userService.getUserByEmail(email);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:read'\n )({\n ...request.locals,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const formattedUser = mapUserToAPI(user);\n const responseData = formatResponse<UserAPI>({ data: formattedUser });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateUserBody = Partial<UserAPI>;\nexport type UpdateUserResult = ResponseData<UserAPI>;\n\n/**\n * Updates user information (phone number, date of birth).\n */\nexport const updateUser = async (\n request: FastifyRequest<{ Body: UpdateUserBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const userData = request.body;\n const { user, roles } = request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (typeof userData !== 'object') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n if (!userData.id) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_INVALID_FIELDS'\n );\n }\n\n const userDB = await userService.getUserById(userData.id);\n\n if (!userDB) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:write'\n )({\n ...request.locals,\n targetUsers: [userDB],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedUser = await userService.updateUserById(userDB.id, userData);\n\n logger.info(\n `User updated: Name: ${updatedUser.name}, id: ${String(updatedUser.id)}`\n );\n\n const formattedUser = mapUserToAPI(updatedUser);\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'User updated',\n fr: 'Utilisateur mis à jour',\n es: 'Usuario actualizado',\n }),\n description: t({\n en: 'User updated successfully',\n fr: 'Utilisateur mis à jour avec succès',\n es: 'Usuario actualizado con éxito',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteUserParams = { userId: string };\nexport type DeleteUserResult = ResponseData<UserAPI>;\n\n/**\n * Deletes a user based on the provided ID.\n */\nexport const deleteUser = async (\n request: FastifyRequest<{ Params: DeleteUserParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { userId } = request.params;\n const { roles } = request.locals || {};\n\n try {\n const user = await userService.getUserById(userId);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:admin'\n )({\n ...request.locals,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n await userService.deleteUser(userId);\n\n const formattedUser = mapUserToAPI(user);\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'User deleted',\n fr: 'Utilisateur supprimé',\n es: 'Usuario eliminado',\n }),\n description: t({\n en: 'User deleted successfully',\n fr: 'Utilisateur supprimé avec succès',\n es: 'Usuario eliminado con éxito',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nlet clients: Array<{\n id: number;\n userId: string;\n res: { raw: FastifyReply['raw'] };\n}> = [];\n\nexport const sendVerificationUpdate = (user: User) => {\n const filteredClients = clients.filter(\n (client) => String(client.userId) === String(user.id)\n );\n\n for (const client of filteredClients) {\n if (user.emailVerified) {\n client.res.raw.write(\n `data: ${JSON.stringify({ userId: user.id, status: 'verified' })}\\n\\n`\n );\n }\n }\n};\n\nexport type VerifyEmailStatusSSEParams = { userId: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const verifyEmailStatusSSE = async (\n request: FastifyRequest<{ Params: VerifyEmailStatusSSEParams }>,\n reply: FastifyReply\n) => {\n // Set headers for SSE\n reply.raw.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n reply.raw.write(':\\n\\n'); // Comment to keep connection alive\n reply.raw.flushHeaders?.();\n\n const { userId } = request.params; // Get user ID from params\n const clientId = Date.now();\n\n const user = await userService.getUserById(userId);\n\n if (!user) {\n logger.error(`User not found - User ID: ${userId}`);\n reply.raw.write(`data: ${JSON.stringify({ userId, status: 'error' })}\\n\\n`);\n reply.raw.end();\n return;\n }\n\n // Add client to the list\n const newClient = { id: clientId, userId, res: { raw: reply.raw } };\n clients.push(newClient);\n\n sendVerificationUpdate(user);\n\n // Remove client on connection close\n request.raw.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user.controller.mjs","names":["userService.createUser","userService.findUsers","userService.countUsers","userService.getUserById","userService.getUserByEmail","userService.updateUserById","userService.deleteUser"],"sources":["../../../src/controllers/user.controller.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { sendEmail } from '@services/email.service';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport {\n getUserFiltersAndPagination,\n type UserFiltersParam,\n} from '@utils/filtersAndPagination/getUserFiltersAndPagination';\nimport { mapUsersToAPI, mapUserToAPI } from '@utils/mapper/user';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { t } from 'fastify-intlayer';\nimport type { User, UserAPI } from '@/types/user.types';\n\nexport type CreateUserBody = { email: string; password?: string };\nexport type CreateUserResult = ResponseData<UserAPI>;\n\n/**\n * Creates a new user.\n */\nexport const createUser = async (\n request: FastifyRequest<{ Body: User }>,\n reply: FastifyReply\n): Promise<void> => {\n const user: User | undefined = request.body;\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const newUser = await userService.createUser(user);\n\n await sendEmail({\n type: 'welcome',\n to: newUser.email,\n username: newUser.name,\n loginLink: `${process.env.APP_URL}/auth/login`,\n });\n\n const formattedUser = mapUserToAPI(newUser);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'User created',\n fr: 'Utilisateur créé',\n es: 'Usuario creado',\n }),\n description: t({\n en: 'User created successfully',\n fr: 'Utilisateur créé avec succès',\n es: 'Usuario creado con éxito',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetUsersParams = FiltersAndPagination<UserFiltersParam>;\nexport type GetUsersResult = PaginatedResponse<UserAPI>;\n\n/**\n * Retrieves a list of users based on filters and pagination.\n */\nexport const getUsers = async (\n request: FastifyRequest<{ Querystring: GetUsersParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getUserFiltersAndPagination(request);\n\n try {\n const users = await userService.findUsers(\n filters,\n skip,\n pageSize,\n sortOptions\n );\n\n if (\n !hasPermission(\n roles || [],\n 'user:read'\n )({\n ...request.locals,\n targetUsers: users,\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await userService.countUsers(filters);\n\n const formattedUsers = mapUsersToAPI(users);\n\n const responseData = formatPaginatedResponse<UserAPI>({\n data: formattedUsers,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetUserByIdParams = { userId: UserAPI['id'] };\nexport type GetUserByIdResult = ResponseData<UserAPI>;\n\nexport const getUserById = async (\n request: FastifyRequest<{ Params: GetUserByIdParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { userId } = request.params;\n\n try {\n const user = await userService.getUserById(userId);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n const formattedUser = mapUserToAPI(user);\n const responseData = formatResponse<UserAPI>({ data: formattedUser });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetUserByEmailParams = { email: string };\nexport type GetUserByEmailResult = ResponseData<UserAPI>;\n\nexport const getUserByEmail = async (\n request: FastifyRequest<{ Params: GetUserByEmailParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { email } = request.params;\n const { roles } = request.locals || {};\n\n try {\n const user = await userService.getUserByEmail(email);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:read'\n )({\n ...request.locals,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const formattedUser = mapUserToAPI(user);\n const responseData = formatResponse<UserAPI>({ data: formattedUser });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type UpdateUserBody = Partial<UserAPI>;\nexport type UpdateUserResult = ResponseData<UserAPI>;\n\n/**\n * Updates user information (phone number, date of birth).\n */\nexport const updateUser = async (\n request: FastifyRequest<{ Body: UpdateUserBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const userData = request.body;\n const { user, roles } = request.locals || {};\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n if (typeof userData !== 'object') {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_DATA_NOT_FOUND'\n );\n }\n\n if (!userData.id) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'USER_INVALID_FIELDS'\n );\n }\n\n const userDB = await userService.getUserById(userData.id);\n\n if (!userDB) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:write'\n )({\n ...request.locals,\n targetUsers: [userDB],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'PERMISSION_DENIED');\n }\n\n try {\n const updatedUser = await userService.updateUserById(userDB.id, userData);\n\n logger.info(\n `User updated: Name: ${updatedUser.name}, id: ${String(updatedUser.id)}`\n );\n\n const formattedUser = mapUserToAPI(updatedUser);\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'User updated',\n fr: 'Utilisateur mis à jour',\n es: 'Usuario actualizado',\n }),\n description: t({\n en: 'User updated successfully',\n fr: 'Utilisateur mis à jour avec succès',\n es: 'Usuario actualizado con éxito',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type DeleteUserParams = { userId: string };\nexport type DeleteUserResult = ResponseData<UserAPI>;\n\n/**\n * Deletes a user based on the provided ID.\n */\nexport const deleteUser = async (\n request: FastifyRequest<{ Params: DeleteUserParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { userId } = request.params;\n const { roles } = request.locals || {};\n\n try {\n const user = await userService.getUserById(userId);\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_FOUND');\n }\n\n if (\n !hasPermission(\n roles || [],\n 'user:admin'\n )({\n ...request.locals,\n targetUsers: [user],\n })\n ) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n await userService.deleteUser(userId);\n\n const formattedUser = mapUserToAPI(user);\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'User deleted',\n fr: 'Utilisateur supprimé',\n es: 'Usuario eliminado',\n }),\n description: t({\n en: 'User deleted successfully',\n fr: 'Utilisateur supprimé avec succès',\n es: 'Usuario eliminado con éxito',\n }),\n data: formattedUser,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nlet clients: Array<{\n id: number;\n userId: string;\n res: { raw: FastifyReply['raw'] };\n}> = [];\n\nexport const sendVerificationUpdate = (user: User) => {\n const filteredClients = clients.filter(\n (client) => String(client.userId) === String(user.id)\n );\n\n for (const client of filteredClients) {\n if (user.emailVerified) {\n client.res.raw.write(\n `data: ${JSON.stringify({ userId: user.id, status: 'verified' })}\\n\\n`\n );\n }\n }\n};\n\nexport type VerifyEmailStatusSSEParams = { userId: string };\n\n/**\n * SSE to check the email verification status\n */\nexport const verifyEmailStatusSSE = async (\n request: FastifyRequest<{ Params: VerifyEmailStatusSSEParams }>,\n reply: FastifyReply\n) => {\n // Set headers for SSE\n reply.raw.setHeader('Content-Type', 'text/event-stream;charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no'); // For Nginx buffering\n\n // Send initial data to ensure the connection is open\n reply.raw.write(':\\n\\n'); // Comment to keep connection alive\n reply.raw.flushHeaders?.();\n\n const { userId } = request.params; // Get user ID from params\n const clientId = Date.now();\n\n const user = await userService.getUserById(userId);\n\n if (!user) {\n logger.error(`User not found - User ID: ${userId}`);\n reply.raw.write(`data: ${JSON.stringify({ userId, status: 'error' })}\\n\\n`);\n reply.raw.end();\n return;\n }\n\n // Add client to the list\n const newClient = { id: clientId, userId, res: { raw: reply.raw } };\n clients.push(newClient);\n\n sendVerificationUpdate(user);\n\n // Remove client on connection close\n request.raw.on('close', () => {\n clients = clients.filter((client) => client.id !== clientId);\n });\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InviteUserEmail.mjs","names":["PreviewProps: InviteUserEmailProps"],"sources":["../../../src/emails/InviteUserEmail.tsx"],"sourcesContent":["import {\n Body,\n Button,\n Container,\n Head,\n Heading,\n Hr,\n Html,\n Img,\n Link,\n Preview,\n Section,\n Tailwind,\n Text,\n} from '@react-email/components';\n\nexport type InviteUserEmailProps = {\n username: string;\n invitedByUsername: string;\n invitedByEmail: string;\n organizationName: string;\n inviteLink: string;\n inviteFromIp: string;\n inviteFromLocation: string;\n};\n\nexport const InviteUserEmailEN = ({\n username,\n invitedByUsername,\n invitedByEmail,\n organizationName,\n inviteLink,\n inviteFromIp,\n inviteFromLocation,\n}: InviteUserEmailProps) => {\n const previewText = `Join ${invitedByUsername} on Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Join <strong>{organizationName}</strong> on{' '}\n <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hello {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n <strong>{invitedByUsername}</strong> (\n <Link\n href={`mailto:${invitedByEmail}`}\n className=\"text-[#8a8a8a] no-underline\"\n >\n {invitedByEmail}\n </Link>\n ) has invited you to the <strong>{organizationName}</strong> team\n on <strong>Intlayer</strong>.\n </Text>\n\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={inviteLink}\n >\n Join the team\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n or copy and paste this URL into your browser:\n </Text>\n <Link\n href={inviteLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {inviteLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n This invitation was intended for{' '}\n <span className=\"text-black\">{username}</span>. This invite was\n sent from <span className=\"text-black\">{inviteFromIp}</span>{' '}\n {inviteFromLocation && (\n <>\n {' located in '}\n <span className=\"text-black\">{inviteFromLocation}</span>\n </>\n )}\n . If you were not expecting this invitation, you can ignore this\n email. If you are concerned about your account's safety, please\n reply to this email to get in touch with us.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const InviteUserEmailFR = ({\n username,\n invitedByUsername,\n invitedByEmail,\n organizationName,\n inviteLink,\n inviteFromIp,\n inviteFromLocation,\n}: InviteUserEmailProps) => {\n const previewText = `Rejoignez ${invitedByUsername} sur Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Rejoignez <strong>{organizationName}</strong> sur{' '}\n <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Bonjour {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n <strong>{invitedByUsername}</strong> (\n <Link\n href={`mailto:${invitedByEmail}`}\n className=\"text-[#8a8a8a] no-underline\"\n >\n {invitedByEmail}\n </Link>\n ) vous a invité à rejoindre l'équipe de{' '}\n <strong>{organizationName}</strong> sur <strong>Intlayer</strong>.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={inviteLink}\n >\n Rejoindre l'équipe\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n ou copiez et collez cette URL dans votre navigateur :{' '}\n </Text>\n <Link\n href={inviteLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {inviteLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Cette invitation était destinée à{' '}\n <span className=\"text-black\">{username}</span>. Cette invitation a\n été envoyée depuis{' '}\n <span className=\"text-black\">{inviteFromIp}</span>\n {inviteFromLocation && (\n <>\n {', située à '}\n <span className=\"text-black\">{inviteFromLocation}</span>\n </>\n )}\n . Si vous n'attendiez pas cette invitation, vous pouvez ignorer\n cet email. Si vous êtes préoccupé par la sécurité de votre compte,\n veuillez répondre à cet email pour nous contacter.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const InviteUserEmailES = ({\n username,\n invitedByUsername,\n invitedByEmail,\n organizationName,\n inviteLink,\n inviteFromIp,\n inviteFromLocation,\n}: InviteUserEmailProps) => {\n const previewText = `Únete a ${invitedByUsername} en Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Únete a <strong>{organizationName}</strong> en{' '}\n <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hola {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n <strong>{invitedByUsername}</strong> (\n <Link\n href={`mailto:${invitedByEmail}`}\n className=\"text-[#8a8a8a] no-underline\"\n >\n {invitedByEmail}\n </Link>\n ) te ha invitado a unirte al equipo de{' '}\n <strong>{organizationName}</strong> en <strong>Intlayer</strong>.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={inviteLink}\n >\n Unirse al equipo\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n o copia y pega esta URL en tu navegador:{' '}\n </Text>\n <Link\n href={inviteLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {inviteLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Esta invitación estaba destinada para{' '}\n <span className=\"text-black\">{username}</span>. Esta invitación\n fue enviada desde{' '}\n <span className=\"text-black\">{inviteFromIp}</span>\n {inviteFromLocation && (\n <>\n {', ubicada en '}\n <span className=\"text-black\">{inviteFromLocation}</span>\n </>\n )}\n . Si no esperabas esta invitación, puedes ignorar este correo. Si\n estás preocupado por la seguridad de tu cuenta, por favor responde\n a este correo para contactarte con nosotros.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nconst PreviewProps: InviteUserEmailProps = {\n username: 'alanturing',\n invitedByUsername: 'Alan',\n invitedByEmail: 'alan.turing@example.com',\n organizationName: 'Enigma',\n inviteLink: 'https://intlayer.org/teams/invite/foo',\n inviteFromIp: '204.13.x.x',\n inviteFromLocation: 'São Paulo, Brazil',\n};\n\nInviteUserEmailEN.PreviewProps = PreviewProps;\nInviteUserEmailFR.PreviewProps = PreviewProps;\nInviteUserEmailES.PreviewProps = PreviewProps;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InviteUserEmail.mjs","names":[],"sources":["../../../src/emails/InviteUserEmail.tsx"],"sourcesContent":["import {\n Body,\n Button,\n Container,\n Head,\n Heading,\n Hr,\n Html,\n Img,\n Link,\n Preview,\n Section,\n Tailwind,\n Text,\n} from '@react-email/components';\n\nexport type InviteUserEmailProps = {\n username: string;\n invitedByUsername: string;\n invitedByEmail: string;\n organizationName: string;\n inviteLink: string;\n inviteFromIp: string;\n inviteFromLocation: string;\n};\n\nexport const InviteUserEmailEN = ({\n username,\n invitedByUsername,\n invitedByEmail,\n organizationName,\n inviteLink,\n inviteFromIp,\n inviteFromLocation,\n}: InviteUserEmailProps) => {\n const previewText = `Join ${invitedByUsername} on Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Join <strong>{organizationName}</strong> on{' '}\n <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hello {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n <strong>{invitedByUsername}</strong> (\n <Link\n href={`mailto:${invitedByEmail}`}\n className=\"text-[#8a8a8a] no-underline\"\n >\n {invitedByEmail}\n </Link>\n ) has invited you to the <strong>{organizationName}</strong> team\n on <strong>Intlayer</strong>.\n </Text>\n\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={inviteLink}\n >\n Join the team\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n or copy and paste this URL into your browser:\n </Text>\n <Link\n href={inviteLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {inviteLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n This invitation was intended for{' '}\n <span className=\"text-black\">{username}</span>. This invite was\n sent from <span className=\"text-black\">{inviteFromIp}</span>{' '}\n {inviteFromLocation && (\n <>\n {' located in '}\n <span className=\"text-black\">{inviteFromLocation}</span>\n </>\n )}\n . If you were not expecting this invitation, you can ignore this\n email. If you are concerned about your account's safety, please\n reply to this email to get in touch with us.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const InviteUserEmailFR = ({\n username,\n invitedByUsername,\n invitedByEmail,\n organizationName,\n inviteLink,\n inviteFromIp,\n inviteFromLocation,\n}: InviteUserEmailProps) => {\n const previewText = `Rejoignez ${invitedByUsername} sur Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Rejoignez <strong>{organizationName}</strong> sur{' '}\n <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Bonjour {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n <strong>{invitedByUsername}</strong> (\n <Link\n href={`mailto:${invitedByEmail}`}\n className=\"text-[#8a8a8a] no-underline\"\n >\n {invitedByEmail}\n </Link>\n ) vous a invité à rejoindre l'équipe de{' '}\n <strong>{organizationName}</strong> sur <strong>Intlayer</strong>.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={inviteLink}\n >\n Rejoindre l'équipe\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n ou copiez et collez cette URL dans votre navigateur :{' '}\n </Text>\n <Link\n href={inviteLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {inviteLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Cette invitation était destinée à{' '}\n <span className=\"text-black\">{username}</span>. Cette invitation a\n été envoyée depuis{' '}\n <span className=\"text-black\">{inviteFromIp}</span>\n {inviteFromLocation && (\n <>\n {', située à '}\n <span className=\"text-black\">{inviteFromLocation}</span>\n </>\n )}\n . Si vous n'attendiez pas cette invitation, vous pouvez ignorer\n cet email. Si vous êtes préoccupé par la sécurité de votre compte,\n veuillez répondre à cet email pour nous contacter.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const InviteUserEmailES = ({\n username,\n invitedByUsername,\n invitedByEmail,\n organizationName,\n inviteLink,\n inviteFromIp,\n inviteFromLocation,\n}: InviteUserEmailProps) => {\n const previewText = `Únete a ${invitedByUsername} en Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Únete a <strong>{organizationName}</strong> en{' '}\n <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hola {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n <strong>{invitedByUsername}</strong> (\n <Link\n href={`mailto:${invitedByEmail}`}\n className=\"text-[#8a8a8a] no-underline\"\n >\n {invitedByEmail}\n </Link>\n ) te ha invitado a unirte al equipo de{' '}\n <strong>{organizationName}</strong> en <strong>Intlayer</strong>.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={inviteLink}\n >\n Unirse al equipo\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n o copia y pega esta URL en tu navegador:{' '}\n </Text>\n <Link\n href={inviteLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {inviteLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Esta invitación estaba destinada para{' '}\n <span className=\"text-black\">{username}</span>. Esta invitación\n fue enviada desde{' '}\n <span className=\"text-black\">{inviteFromIp}</span>\n {inviteFromLocation && (\n <>\n {', ubicada en '}\n <span className=\"text-black\">{inviteFromLocation}</span>\n </>\n )}\n . Si no esperabas esta invitación, puedes ignorar este correo. Si\n estás preocupado por la seguridad de tu cuenta, por favor responde\n a este correo para contactarte con nosotros.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nconst PreviewProps: InviteUserEmailProps = {\n username: 'alanturing',\n invitedByUsername: 'Alan',\n invitedByEmail: 'alan.turing@example.com',\n organizationName: 'Enigma',\n inviteLink: 'https://intlayer.org/teams/invite/foo',\n inviteFromIp: '204.13.x.x',\n inviteFromLocation: 'São Paulo, Brazil',\n};\n\nInviteUserEmailEN.PreviewProps = PreviewProps;\nInviteUserEmailFR.PreviewProps = PreviewProps;\nInviteUserEmailES.PreviewProps = PreviewProps;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MagicLinkEmail.mjs","names":["PreviewProps: MagicLinkEmailProps"],"sources":["../../../src/emails/MagicLinkEmail.tsx"],"sourcesContent":["import {\n Body,\n Button,\n Container,\n Head,\n Heading,\n Hr,\n Html,\n Img,\n Link,\n Preview,\n Section,\n Tailwind,\n Text,\n} from '@react-email/components';\n\nexport type MagicLinkEmailProps = {\n username: string;\n magicLink: string;\n};\n\nexport const MagicLinkEmailEN = ({\n username,\n magicLink,\n}: MagicLinkEmailProps) => {\n const previewText = `Sign in to Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Sign in to <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hello {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Click the button below to sign in to your{' '}\n <strong>Intlayer</strong> account. This link will expire in 10\n minutes.\n </Text>\n\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={magicLink}\n >\n Sign In\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n or copy and paste this URL into your browser:\n </Text>\n <Link\n href={magicLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {magicLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n This sign-in link was intended for{' '}\n <span className=\"text-black\">{username}</span>. If you did not\n request this link, you can safely ignore this email. If you are\n concerned about your account's safety, please reply to this email\n to get in touch with us.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const MagicLinkEmailFR = ({\n username,\n magicLink,\n}: MagicLinkEmailProps) => {\n const previewText = `Connectez-vous à Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Connectez-vous à <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Bonjour {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Cliquez sur le bouton ci-dessous pour vous connecter à votre\n compte <strong>Intlayer</strong>. Ce lien expirera dans 10\n minutes.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={magicLink}\n >\n Se connecter\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n ou copiez et collez cette URL dans votre navigateur :\n </Text>\n <Link\n href={magicLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {magicLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Ce lien de connexion était destiné à{' '}\n <span className=\"text-black\">{username}</span>. Si vous n'avez pas\n demandé ce lien, vous pouvez ignorer cet email en toute sécurité.\n Si vous êtes préoccupé par la sécurité de votre compte, veuillez\n répondre à cet email pour nous contacter.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const MagicLinkEmailES = ({\n username,\n magicLink,\n}: MagicLinkEmailProps) => {\n const previewText = `Inicia sesión en Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Inicia sesión en <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hola {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Haz clic en el botón de abajo para iniciar sesión en tu cuenta de{' '}\n <strong>Intlayer</strong>. Este enlace expirará en 10 minutos.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={magicLink}\n >\n Iniciar Sesión\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n o copia y pega esta URL en tu navegador:\n </Text>\n <Link\n href={magicLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {magicLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Este enlace de inicio de sesión estaba destinado a{' '}\n <span className=\"text-black\">{username}</span>. Si no solicitaste\n este enlace, puedes ignorar este correo de forma segura. Si estás\n preocupado por la seguridad de tu cuenta, por favor responde a\n este correo para ponerte en contacto con nosotros.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nconst PreviewProps: MagicLinkEmailProps = {\n username: 'alanturing',\n magicLink: 'https://intlayer.org/auth/magic-link/verify?token=foo',\n};\n\nMagicLinkEmailEN.PreviewProps = PreviewProps;\nMagicLinkEmailFR.PreviewProps = PreviewProps;\nMagicLinkEmailES.PreviewProps = PreviewProps;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MagicLinkEmail.mjs","names":[],"sources":["../../../src/emails/MagicLinkEmail.tsx"],"sourcesContent":["import {\n Body,\n Button,\n Container,\n Head,\n Heading,\n Hr,\n Html,\n Img,\n Link,\n Preview,\n Section,\n Tailwind,\n Text,\n} from '@react-email/components';\n\nexport type MagicLinkEmailProps = {\n username: string;\n magicLink: string;\n};\n\nexport const MagicLinkEmailEN = ({\n username,\n magicLink,\n}: MagicLinkEmailProps) => {\n const previewText = `Sign in to Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Sign in to <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hello {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Click the button below to sign in to your{' '}\n <strong>Intlayer</strong> account. This link will expire in 10\n minutes.\n </Text>\n\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={magicLink}\n >\n Sign In\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n or copy and paste this URL into your browser:\n </Text>\n <Link\n href={magicLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {magicLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n This sign-in link was intended for{' '}\n <span className=\"text-black\">{username}</span>. If you did not\n request this link, you can safely ignore this email. If you are\n concerned about your account's safety, please reply to this email\n to get in touch with us.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const MagicLinkEmailFR = ({\n username,\n magicLink,\n}: MagicLinkEmailProps) => {\n const previewText = `Connectez-vous à Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Connectez-vous à <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Bonjour {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Cliquez sur le bouton ci-dessous pour vous connecter à votre\n compte <strong>Intlayer</strong>. Ce lien expirera dans 10\n minutes.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={magicLink}\n >\n Se connecter\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n ou copiez et collez cette URL dans votre navigateur :\n </Text>\n <Link\n href={magicLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {magicLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Ce lien de connexion était destiné à{' '}\n <span className=\"text-black\">{username}</span>. Si vous n'avez pas\n demandé ce lien, vous pouvez ignorer cet email en toute sécurité.\n Si vous êtes préoccupé par la sécurité de votre compte, veuillez\n répondre à cet email pour nous contacter.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const MagicLinkEmailES = ({\n username,\n magicLink,\n}: MagicLinkEmailProps) => {\n const previewText = `Inicia sesión en Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Inicia sesión en <strong>Intlayer</strong>\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hola {username},\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Haz clic en el botón de abajo para iniciar sesión en tu cuenta de{' '}\n <strong>Intlayer</strong>. Este enlace expirará en 10 minutos.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={magicLink}\n >\n Iniciar Sesión\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n o copia y pega esta URL en tu navegador:\n </Text>\n <Link\n href={magicLink}\n className=\"text-[#8a8a8a] text-[10px] no-underline\"\n >\n {magicLink}\n </Link>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Este enlace de inicio de sesión estaba destinado a{' '}\n <span className=\"text-black\">{username}</span>. Si no solicitaste\n este enlace, puedes ignorar este correo de forma segura. Si estás\n preocupado por la seguridad de tu cuenta, por favor responde a\n este correo para ponerte en contacto con nosotros.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nconst PreviewProps: MagicLinkEmailProps = {\n username: 'alanturing',\n magicLink: 'https://intlayer.org/auth/magic-link/verify?token=foo',\n};\n\nMagicLinkEmailEN.PreviewProps = PreviewProps;\nMagicLinkEmailFR.PreviewProps = PreviewProps;\nMagicLinkEmailES.PreviewProps = PreviewProps;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthTokenCreatedEmail.mjs","names":["previewProps: OAuthTokenCreatedEmailProps"],"sources":["../../../src/emails/OAuthTokenCreatedEmail.tsx"],"sourcesContent":["import {\n Body,\n Button,\n Container,\n Head,\n Heading,\n Hr,\n Html,\n Img,\n Link,\n Preview,\n Section,\n Tailwind,\n Text,\n} from '@react-email/components';\n\nexport type OAuthTokenCreatedEmailProps = {\n username: string;\n applicationName: string;\n scopes: string[];\n tokenDetailsUrl: string;\n securityLogUrl: string;\n supportUrl: string;\n};\n\nexport const OAuthTokenCreatedEmailEN = ({\n username,\n applicationName,\n scopes,\n tokenDetailsUrl,\n securityLogUrl,\n supportUrl,\n}: OAuthTokenCreatedEmailProps) => {\n const previewText = `A third-party OAuth access key has been added to your Intlayer account`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n A third-party OAuth access key has been added to your account\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hey {username}!\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n A third-party OAuth access key (<strong>{applicationName}</strong>\n ) with the following permissions was recently authorized to access\n your account:\n <ul>\n {scopes.map((scope) => (\n <li key={scope}>{scope}</li>\n ))}\n </ul>\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={tokenDetailsUrl}\n >\n View Application Details\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n To see this and other security events for your account, visit{' '}\n <Link\n href={securityLogUrl}\n className=\"text-[#000000] no-underline\"\n >\n your security log\n </Link>\n .\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n If you run into problems, please contact support by visiting{' '}\n <Link href={supportUrl} className=\"text-[#000000] no-underline\">\n our support page\n </Link>\n .\n </Text>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n This email was intended for{' '}\n <span className=\"text-black\">{username}</span>. If you were not\n expecting this email, you can ignore it. If you are concerned\n about your account's safety, please reply to this email to get in\n touch with us.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const OAuthTokenCreatedEmailFR = ({\n username,\n applicationName,\n scopes,\n tokenDetailsUrl,\n securityLogUrl,\n supportUrl,\n}: OAuthTokenCreatedEmailProps) => {\n const previewText = `Une application OAuth tierce a été ajoutée à votre compte Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Une application OAuth tierce a été ajoutée à votre compte\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Salut {username} !\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Une application OAuth tierce (<strong>{applicationName}</strong>)\n avec les permissions {scopes.join(', ')} a récemment été autorisée\n à accéder à votre compte.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={tokenDetailsUrl}\n >\n Voir les détails de l'application\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Pour voir cet événement et d'autres événements de sécurité pour\n votre compte, visitez{' '}\n <Link\n href={securityLogUrl}\n className=\"text-[#000000] no-underline\"\n >\n votre journal de sécurité\n </Link>\n .\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Si vous rencontrez des problèmes, veuillez contacter le support en\n visitant{' '}\n <Link href={supportUrl} className=\"text-[#000000] no-underline\">\n notre page de support\n </Link>\n .\n </Text>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Cet e-mail était destiné à{' '}\n <span className=\"text-black\">{username}</span>. Si vous\n n'attendiez pas cet e-mail, vous pouvez l'ignorer. Si vous êtes\n préoccupé par la sécurité de votre compte, veuillez répondre à cet\n e-mail pour nous contacter.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const OAuthTokenCreatedEmailES = ({\n username,\n applicationName,\n scopes,\n tokenDetailsUrl,\n securityLogUrl,\n supportUrl,\n}: OAuthTokenCreatedEmailProps) => {\n const previewText = `Se ha añadido una aplicación OAuth de terceros a tu cuenta de Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Se ha añadido una aplicación OAuth de terceros a tu cuenta\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n ¡Hola {username}!\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Una aplicación OAuth de terceros (\n <strong>{applicationName}</strong>) con permisos{' '}\n {scopes.join(', ')} fue recientemente autorizada para acceder a tu\n cuenta.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={tokenDetailsUrl}\n >\n Ver detalles de la aplicación\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Para ver este y otros eventos de seguridad para tu cuenta, visita{' '}\n <Link\n href={securityLogUrl}\n className=\"text-[#000000] no-underline\"\n >\n tu registro de seguridad\n </Link>\n .\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Si tienes problemas, por favor contacta al soporte visitando{' '}\n <Link href={supportUrl} className=\"text-[#000000] no-underline\">\n nuestra página de soporte\n </Link>\n .\n </Text>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Este correo fue enviado a{' '}\n <span className=\"text-black\">{username}</span>. Si no esperabas\n este correo, puedes ignorarlo. Si estás preocupado por la\n seguridad de tu cuenta, por favor responde a este correo para\n contactarte con nosotros.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nconst previewProps: OAuthTokenCreatedEmailProps = {\n username: 'aymericzip',\n applicationName: 'Postman',\n scopes: ['read:user', 'user:email'],\n tokenDetailsUrl:\n 'https://intlayer.org/settings/connections/applications/Ov23li230j1cbJfa4SPW',\n securityLogUrl: 'https://intlayer.org/settings/security-log',\n supportUrl: 'https://intlayer.org/contact',\n};\n\nOAuthTokenCreatedEmailEN.PreviewProps = previewProps;\nOAuthTokenCreatedEmailFR.PreviewProps = previewProps;\nOAuthTokenCreatedEmailES.PreviewProps = previewProps;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OAuthTokenCreatedEmail.mjs","names":[],"sources":["../../../src/emails/OAuthTokenCreatedEmail.tsx"],"sourcesContent":["import {\n Body,\n Button,\n Container,\n Head,\n Heading,\n Hr,\n Html,\n Img,\n Link,\n Preview,\n Section,\n Tailwind,\n Text,\n} from '@react-email/components';\n\nexport type OAuthTokenCreatedEmailProps = {\n username: string;\n applicationName: string;\n scopes: string[];\n tokenDetailsUrl: string;\n securityLogUrl: string;\n supportUrl: string;\n};\n\nexport const OAuthTokenCreatedEmailEN = ({\n username,\n applicationName,\n scopes,\n tokenDetailsUrl,\n securityLogUrl,\n supportUrl,\n}: OAuthTokenCreatedEmailProps) => {\n const previewText = `A third-party OAuth access key has been added to your Intlayer account`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n A third-party OAuth access key has been added to your account\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Hey {username}!\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n A third-party OAuth access key (<strong>{applicationName}</strong>\n ) with the following permissions was recently authorized to access\n your account:\n <ul>\n {scopes.map((scope) => (\n <li key={scope}>{scope}</li>\n ))}\n </ul>\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={tokenDetailsUrl}\n >\n View Application Details\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n To see this and other security events for your account, visit{' '}\n <Link\n href={securityLogUrl}\n className=\"text-[#000000] no-underline\"\n >\n your security log\n </Link>\n .\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n If you run into problems, please contact support by visiting{' '}\n <Link href={supportUrl} className=\"text-[#000000] no-underline\">\n our support page\n </Link>\n .\n </Text>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n This email was intended for{' '}\n <span className=\"text-black\">{username}</span>. If you were not\n expecting this email, you can ignore it. If you are concerned\n about your account's safety, please reply to this email to get in\n touch with us.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const OAuthTokenCreatedEmailFR = ({\n username,\n applicationName,\n scopes,\n tokenDetailsUrl,\n securityLogUrl,\n supportUrl,\n}: OAuthTokenCreatedEmailProps) => {\n const previewText = `Une application OAuth tierce a été ajoutée à votre compte Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Une application OAuth tierce a été ajoutée à votre compte\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Salut {username} !\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Une application OAuth tierce (<strong>{applicationName}</strong>)\n avec les permissions {scopes.join(', ')} a récemment été autorisée\n à accéder à votre compte.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={tokenDetailsUrl}\n >\n Voir les détails de l'application\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Pour voir cet événement et d'autres événements de sécurité pour\n votre compte, visitez{' '}\n <Link\n href={securityLogUrl}\n className=\"text-[#000000] no-underline\"\n >\n votre journal de sécurité\n </Link>\n .\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Si vous rencontrez des problèmes, veuillez contacter le support en\n visitant{' '}\n <Link href={supportUrl} className=\"text-[#000000] no-underline\">\n notre page de support\n </Link>\n .\n </Text>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Cet e-mail était destiné à{' '}\n <span className=\"text-black\">{username}</span>. Si vous\n n'attendiez pas cet e-mail, vous pouvez l'ignorer. Si vous êtes\n préoccupé par la sécurité de votre compte, veuillez répondre à cet\n e-mail pour nous contacter.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nexport const OAuthTokenCreatedEmailES = ({\n username,\n applicationName,\n scopes,\n tokenDetailsUrl,\n securityLogUrl,\n supportUrl,\n}: OAuthTokenCreatedEmailProps) => {\n const previewText = `Se ha añadido una aplicación OAuth de terceros a tu cuenta de Intlayer`;\n\n return (\n <Html>\n <Head />\n <Preview>{previewText}</Preview>\n <Tailwind>\n <Body className=\"m-auto px-2 font-sans\">\n <Container className=\"mx-auto my-[40px] max-w-[465px] rounded-xl border border-[#eaeaea] border-solid bg-white p-[20px]\">\n <Section className=\"mt-[32px]\">\n <Img\n src=\"https://intlayer.org/apple-touch-icon.png\"\n width=\"40\"\n height=\"37\"\n alt=\"Intlayer\"\n className=\"mx-auto my-0\"\n />\n </Section>\n <Heading className=\"mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black\">\n Se ha añadido una aplicación OAuth de terceros a tu cuenta\n </Heading>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n ¡Hola {username}!\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Una aplicación OAuth de terceros (\n <strong>{applicationName}</strong>) con permisos{' '}\n {scopes.join(', ')} fue recientemente autorizada para acceder a tu\n cuenta.\n </Text>\n <Section className=\"my-[32px] text-center\">\n <Button\n className=\"rounded-md bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline\"\n href={tokenDetailsUrl}\n >\n Ver detalles de la aplicación\n </Button>\n </Section>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Para ver este y otros eventos de seguridad para tu cuenta, visita{' '}\n <Link\n href={securityLogUrl}\n className=\"text-[#000000] no-underline\"\n >\n tu registro de seguridad\n </Link>\n .\n </Text>\n <Text className=\"text-[14px] text-black leading-[24px]\">\n Si tienes problemas, por favor contacta al soporte visitando{' '}\n <Link href={supportUrl} className=\"text-[#000000] no-underline\">\n nuestra página de soporte\n </Link>\n .\n </Text>\n <Hr className=\"mx-0 my-[26px] w-full border border-[#eaeaea] border-solid\" />\n <Text className=\"text-[#666666] text-[12px] leading-[24px]\">\n Este correo fue enviado a{' '}\n <span className=\"text-black\">{username}</span>. Si no esperabas\n este correo, puedes ignorarlo. Si estás preocupado por la\n seguridad de tu cuenta, por favor responde a este correo para\n contactarte con nosotros.\n </Text>\n </Container>\n </Body>\n </Tailwind>\n </Html>\n );\n};\n\nconst previewProps: OAuthTokenCreatedEmailProps = {\n username: 'aymericzip',\n applicationName: 'Postman',\n scopes: ['read:user', 'user:email'],\n tokenDetailsUrl:\n 'https://intlayer.org/settings/connections/applications/Ov23li230j1cbJfa4SPW',\n securityLogUrl: 'https://intlayer.org/settings/security-log',\n supportUrl: 'https://intlayer.org/contact',\n};\n\nOAuthTokenCreatedEmailEN.PreviewProps = previewProps;\nOAuthTokenCreatedEmailFR.PreviewProps = previewProps;\nOAuthTokenCreatedEmailES.PreviewProps = previewProps;\n"],"mappings":";;;;AAyBA,MAAa,4BAA4B,EACvC,UACA,iBACA,QACA,iBACA,gBACA,iBACiC;AAGjC,QACE,qBAAC;EACC,oBAAC,SAAO;EACR,oBAAC,qBALe,2EAKgB;EAChC,oBAAC,sBACC,oBAAC;GAAK,WAAU;aACd,qBAAC;IAAU,WAAU;;KACnB,oBAAC;MAAQ,WAAU;gBACjB,oBAAC;OACC,KAAI;OACJ,OAAM;OACN,QAAO;OACP,KAAI;OACJ,WAAU;QACV;OACM;KACV,oBAAC;MAAQ,WAAU;gBAAoE;OAE7E;KACV,qBAAC;MAAK,WAAU;;OAAwC;OACjD;OAAS;;OACT;KACP,qBAAC;MAAK,WAAU;;OAAwC;OACtB,oBAAC,sBAAQ,kBAAyB;;OAGlE,oBAAC,kBACE,OAAO,KAAK,UACX,oBAAC,kBAAgB,SAAR,MAAmB,CAC5B,GACC;;OACA;KACP,oBAAC;MAAQ,WAAU;gBACjB,oBAAC;OACC,WAAU;OACV,MAAM;iBACP;QAEQ;OACD;KACV,qBAAC;MAAK,WAAU;;OAAwC;OACQ;OAC9D,oBAAC;QACC,MAAM;QACN,WAAU;kBACX;SAEM;;;OAEF;KACP,qBAAC;MAAK,WAAU;;OAAwC;OACO;OAC7D,oBAAC;QAAK,MAAM;QAAY,WAAU;kBAA8B;SAEzD;;;OAEF;KACP,oBAAC,MAAG,WAAU,+DAA+D;KAC7E,qBAAC;MAAK,WAAU;;OAA4C;OAC9B;OAC5B,oBAAC;QAAK,WAAU;kBAAc;SAAgB;;;OAIzC;;KACG;IACP,GACE;KACN;;AAIX,MAAa,4BAA4B,EACvC,UACA,iBACA,QACA,iBACA,gBACA,iBACiC;AAGjC,QACE,qBAAC;EACC,oBAAC,SAAO;EACR,oBAAC,qBALe,uEAKgB;EAChC,oBAAC,sBACC,oBAAC;GAAK,WAAU;aACd,qBAAC;IAAU,WAAU;;KACnB,oBAAC;MAAQ,WAAU;gBACjB,oBAAC;OACC,KAAI;OACJ,OAAM;OACN,QAAO;OACP,KAAI;OACJ,WAAU;QACV;OACM;KACV,oBAAC;MAAQ,WAAU;gBAAoE;OAE7E;KACV,qBAAC;MAAK,WAAU;;OAAwC;OAC/C;OAAS;;OACX;KACP,qBAAC;MAAK,WAAU;;OAAwC;OACxB,oBAAC,sBAAQ,kBAAyB;;OAC1C,OAAO,KAAK,KAAK;OAAC;;OAEnC;KACP,oBAAC;MAAQ,WAAU;gBACjB,oBAAC;OACC,WAAU;OACV,MAAM;iBACP;QAEQ;OACD;KACV,qBAAC;MAAK,WAAU;;OAAwC;OAEhC;OACtB,oBAAC;QACC,MAAM;QACN,WAAU;kBACX;SAEM;;;OAEF;KACP,qBAAC;MAAK,WAAU;;OAAwC;OAE7C;OACT,oBAAC;QAAK,MAAM;QAAY,WAAU;kBAA8B;SAEzD;;;OAEF;KACP,oBAAC,MAAG,WAAU,+DAA+D;KAC7E,qBAAC;MAAK,WAAU;;OAA4C;OAC/B;OAC3B,oBAAC;QAAK,WAAU;kBAAc;SAAgB;;;OAIzC;;KACG;IACP,GACE;KACN;;AAIX,MAAa,4BAA4B,EACvC,UACA,iBACA,QACA,iBACA,gBACA,iBACiC;AAGjC,QACE,qBAAC;EACC,oBAAC,SAAO;EACR,oBAAC,qBALe,2EAKgB;EAChC,oBAAC,sBACC,oBAAC;GAAK,WAAU;aACd,qBAAC;IAAU,WAAU;;KACnB,oBAAC;MAAQ,WAAU;gBACjB,oBAAC;OACC,KAAI;OACJ,OAAM;OACN,QAAO;OACP,KAAI;OACJ,WAAU;QACV;OACM;KACV,oBAAC;MAAQ,WAAU;gBAAoE;OAE7E;KACV,qBAAC;MAAK,WAAU;;OAAwC;OAC/C;OAAS;;OACX;KACP,qBAAC;MAAK,WAAU;;OAAwC;OAEtD,oBAAC,sBAAQ,kBAAyB;;OAAe;OAChD,OAAO,KAAK,KAAK;OAAC;;OAEd;KACP,oBAAC;MAAQ,WAAU;gBACjB,oBAAC;OACC,WAAU;OACV,MAAM;iBACP;QAEQ;OACD;KACV,qBAAC;MAAK,WAAU;;OAAwC;OACY;OAClE,oBAAC;QACC,MAAM;QACN,WAAU;kBACX;SAEM;;;OAEF;KACP,qBAAC;MAAK,WAAU;;OAAwC;OACO;OAC7D,oBAAC;QAAK,MAAM;QAAY,WAAU;kBAA8B;SAEzD;;;OAEF;KACP,oBAAC,MAAG,WAAU,+DAA+D;KAC7E,qBAAC;MAAK,WAAU;;OAA4C;OAChC;OAC1B,oBAAC;QAAK,WAAU;kBAAc;SAAgB;;;OAIzC;;KACG;IACP,GACE;KACN;;AAIX,MAAM,eAA4C;CAChD,UAAU;CACV,iBAAiB;CACjB,QAAQ,CAAC,aAAa,aAAa;CACnC,iBACE;CACF,gBAAgB;CAChB,YAAY;CACb;AAED,yBAAyB,eAAe;AACxC,yBAAyB,eAAe;AACxC,yBAAyB,eAAe"}
|