@intlayer/backend 5.7.2 → 5.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/dist/cjs/controllers/ai.controller.cjs.map +1 -1
  2. package/dist/cjs/controllers/dictionary.controller.cjs +39 -20
  3. package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
  4. package/dist/cjs/controllers/newsletter.controller.cjs +12 -13
  5. package/dist/cjs/controllers/newsletter.controller.cjs.map +1 -1
  6. package/dist/cjs/controllers/organization.controller.cjs +72 -53
  7. package/dist/cjs/controllers/organization.controller.cjs.map +1 -1
  8. package/dist/cjs/controllers/project.controller.cjs +48 -12
  9. package/dist/cjs/controllers/project.controller.cjs.map +1 -1
  10. package/dist/cjs/controllers/projectAccessKey.controller.cjs +30 -6
  11. package/dist/cjs/controllers/projectAccessKey.controller.cjs.map +1 -1
  12. package/dist/cjs/controllers/search.controller.cjs.map +1 -1
  13. package/dist/cjs/controllers/stripe.controller.cjs +15 -3
  14. package/dist/cjs/controllers/stripe.controller.cjs.map +1 -1
  15. package/dist/cjs/controllers/tag.controller.cjs +46 -17
  16. package/dist/cjs/controllers/tag.controller.cjs.map +1 -1
  17. package/dist/cjs/controllers/user.controller.cjs +19 -8
  18. package/dist/cjs/controllers/user.controller.cjs.map +1 -1
  19. package/dist/cjs/emails/OAuthTokenCreatedEmail.cjs +5 -7
  20. package/dist/cjs/emails/OAuthTokenCreatedEmail.cjs.map +1 -1
  21. package/dist/cjs/middlewares/sessionAuth.middleware.cjs.map +1 -1
  22. package/dist/cjs/routes/ai.routes.cjs.map +1 -1
  23. package/dist/cjs/routes/dictionary.routes.cjs.map +1 -1
  24. package/dist/cjs/routes/eventListener.routes.cjs.map +1 -1
  25. package/dist/cjs/routes/newsletter.routes.cjs.map +1 -1
  26. package/dist/cjs/routes/organization.routes.cjs.map +1 -1
  27. package/dist/cjs/routes/project.routes.cjs +3 -12
  28. package/dist/cjs/routes/project.routes.cjs.map +1 -1
  29. package/dist/cjs/routes/search.routes.cjs.map +1 -1
  30. package/dist/cjs/routes/stripe.routes.cjs.map +1 -1
  31. package/dist/cjs/routes/tags.routes.cjs.map +1 -1
  32. package/dist/cjs/routes/user.routes.cjs.map +1 -1
  33. package/dist/cjs/schemas/dictionary.schema.cjs +1 -1
  34. package/dist/cjs/schemas/dictionary.schema.cjs.map +1 -1
  35. package/dist/cjs/schemas/discussion.schema.cjs +1 -1
  36. package/dist/cjs/schemas/discussion.schema.cjs.map +1 -1
  37. package/dist/cjs/schemas/oAuth2.schema.cjs +1 -1
  38. package/dist/cjs/schemas/oAuth2.schema.cjs.map +1 -1
  39. package/dist/cjs/schemas/organization.schema.cjs +1 -1
  40. package/dist/cjs/schemas/organization.schema.cjs.map +1 -1
  41. package/dist/cjs/schemas/plans.schema.cjs +1 -1
  42. package/dist/cjs/schemas/plans.schema.cjs.map +1 -1
  43. package/dist/cjs/schemas/project.schema.cjs +1 -1
  44. package/dist/cjs/schemas/project.schema.cjs.map +1 -1
  45. package/dist/cjs/schemas/session.schema.cjs +1 -1
  46. package/dist/cjs/schemas/session.schema.cjs.map +1 -1
  47. package/dist/cjs/schemas/tag.schema.cjs +6 -1
  48. package/dist/cjs/schemas/tag.schema.cjs.map +1 -1
  49. package/dist/cjs/schemas/user.schema.cjs +1 -1
  50. package/dist/cjs/schemas/user.schema.cjs.map +1 -1
  51. package/dist/cjs/services/dictionary.service.cjs +0 -8
  52. package/dist/cjs/services/dictionary.service.cjs.map +1 -1
  53. package/dist/cjs/services/organization.service.cjs +0 -9
  54. package/dist/cjs/services/organization.service.cjs.map +1 -1
  55. package/dist/cjs/types/organization.types.cjs.map +1 -1
  56. package/dist/cjs/types/project.types.cjs.map +1 -1
  57. package/dist/cjs/types/tag.types.cjs.map +1 -1
  58. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs +3 -0
  59. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs.map +1 -1
  60. package/dist/cjs/utils/auth/getAuth.cjs +9 -5
  61. package/dist/cjs/utils/auth/getAuth.cjs.map +1 -1
  62. package/dist/cjs/utils/errors/errorCodes.cjs +13 -0
  63. package/dist/cjs/utils/errors/errorCodes.cjs.map +1 -1
  64. package/dist/cjs/utils/mapper/project.cjs +1 -2
  65. package/dist/cjs/utils/mapper/project.cjs.map +1 -1
  66. package/dist/cjs/utils/mapper/session.cjs +37 -0
  67. package/dist/cjs/utils/mapper/session.cjs.map +1 -0
  68. package/dist/cjs/utils/permissions.cjs +133 -38
  69. package/dist/cjs/utils/permissions.cjs.map +1 -1
  70. package/dist/cjs/utils/validation/validateArray.cjs +5 -1
  71. package/dist/cjs/utils/validation/validateArray.cjs.map +1 -1
  72. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  73. package/dist/esm/controllers/dictionary.controller.mjs +39 -20
  74. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  75. package/dist/esm/controllers/newsletter.controller.mjs +12 -13
  76. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  77. package/dist/esm/controllers/organization.controller.mjs +72 -53
  78. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  79. package/dist/esm/controllers/project.controller.mjs +48 -12
  80. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  81. package/dist/esm/controllers/projectAccessKey.controller.mjs +31 -7
  82. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  83. package/dist/esm/controllers/search.controller.mjs.map +1 -1
  84. package/dist/esm/controllers/stripe.controller.mjs +15 -3
  85. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  86. package/dist/esm/controllers/tag.controller.mjs +46 -17
  87. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  88. package/dist/esm/controllers/user.controller.mjs +19 -8
  89. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  90. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs +5 -7
  91. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
  92. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  93. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  94. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  95. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  96. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  97. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  98. package/dist/esm/routes/project.routes.mjs +3 -12
  99. package/dist/esm/routes/project.routes.mjs.map +1 -1
  100. package/dist/esm/routes/search.routes.mjs.map +1 -1
  101. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  102. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  103. package/dist/esm/routes/user.routes.mjs.map +1 -1
  104. package/dist/esm/schemas/dictionary.schema.mjs +1 -1
  105. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  106. package/dist/esm/schemas/discussion.schema.mjs +1 -1
  107. package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
  108. package/dist/esm/schemas/oAuth2.schema.mjs +1 -1
  109. package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
  110. package/dist/esm/schemas/organization.schema.mjs +1 -1
  111. package/dist/esm/schemas/organization.schema.mjs.map +1 -1
  112. package/dist/esm/schemas/plans.schema.mjs +1 -1
  113. package/dist/esm/schemas/plans.schema.mjs.map +1 -1
  114. package/dist/esm/schemas/project.schema.mjs +1 -1
  115. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  116. package/dist/esm/schemas/session.schema.mjs +1 -1
  117. package/dist/esm/schemas/session.schema.mjs.map +1 -1
  118. package/dist/esm/schemas/tag.schema.mjs +6 -1
  119. package/dist/esm/schemas/tag.schema.mjs.map +1 -1
  120. package/dist/esm/schemas/user.schema.mjs +1 -1
  121. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  122. package/dist/esm/services/dictionary.service.mjs +0 -7
  123. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  124. package/dist/esm/services/organization.service.mjs +0 -9
  125. package/dist/esm/services/organization.service.mjs.map +1 -1
  126. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs +3 -0
  127. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  128. package/dist/esm/utils/auth/getAuth.mjs +9 -5
  129. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  130. package/dist/esm/utils/errors/errorCodes.mjs +13 -0
  131. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  132. package/dist/esm/utils/mapper/project.mjs +1 -2
  133. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  134. package/dist/esm/utils/mapper/session.mjs +13 -0
  135. package/dist/esm/utils/mapper/session.mjs.map +1 -0
  136. package/dist/esm/utils/permissions.mjs +133 -38
  137. package/dist/esm/utils/permissions.mjs.map +1 -1
  138. package/dist/esm/utils/validation/validateArray.mjs +5 -1
  139. package/dist/esm/utils/validation/validateArray.mjs.map +1 -1
  140. package/dist/types/controllers/ai.controller.d.ts +10 -9
  141. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  142. package/dist/types/controllers/dictionary.controller.d.ts +9 -8
  143. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  144. package/dist/types/controllers/newsletter.controller.d.ts +5 -4
  145. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  146. package/dist/types/controllers/organization.controller.d.ts +14 -15
  147. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  148. package/dist/types/controllers/project.controller.d.ts +10 -9
  149. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  150. package/dist/types/controllers/projectAccessKey.controller.d.ts +5 -4
  151. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  152. package/dist/types/controllers/search.controller.d.ts.map +1 -1
  153. package/dist/types/controllers/stripe.controller.d.ts +9 -5
  154. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  155. package/dist/types/controllers/tag.controller.d.ts +6 -5
  156. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  157. package/dist/types/controllers/user.controller.d.ts +9 -8
  158. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  159. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
  160. package/dist/types/middlewares/sessionAuth.middleware.d.ts +2 -0
  161. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  162. package/dist/types/schemas/tag.schema.d.ts.map +1 -1
  163. package/dist/types/services/dictionary.service.d.ts +0 -1
  164. package/dist/types/services/dictionary.service.d.ts.map +1 -1
  165. package/dist/types/services/organization.service.d.ts.map +1 -1
  166. package/dist/types/types/organization.types.d.ts +1 -3
  167. package/dist/types/types/organization.types.d.ts.map +1 -1
  168. package/dist/types/types/project.types.d.ts +1 -3
  169. package/dist/types/types/project.types.d.ts.map +1 -1
  170. package/dist/types/types/tag.types.d.ts +2 -0
  171. package/dist/types/types/tag.types.d.ts.map +1 -1
  172. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
  173. package/dist/types/utils/auth/getAuth.d.ts +2 -2
  174. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  175. package/dist/types/utils/errors/errorCodes.d.ts +13 -0
  176. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  177. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  178. package/dist/types/utils/mapper/session.d.ts +4 -0
  179. package/dist/types/utils/mapper/session.d.ts.map +1 -0
  180. package/dist/types/utils/permissions.d.ts +83 -38
  181. package/dist/types/utils/permissions.d.ts.map +1 -1
  182. package/dist/types/utils/validation/validateArray.d.ts.map +1 -1
  183. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/permissions.ts"],"sourcesContent":["import { Dictionary, Organization, Project, Tag, User } from '@/export';\nimport { SessionContext } from '@/types/session.types';\n\n/**\n * @description\n * A named grouping of privileges (e.g. `\"org_admin\"`).\n * Users are *granted* one or more Roles.\n */\nexport type Roles =\n | 'admin'\n | 'org_admin'\n | 'org_user'\n | 'project_admin'\n | 'project_user'\n | 'project_reviewer';\n\n/**\n * @description\n * An atomic operation that can be performed on a resource.\n * - **read**: view or list\n * - **write**: create or update\n * - **admin**: delete or change permissions\n */\nexport type Action = 'read' | 'write' | 'admin';\n\n/**\n * @description\n * A first‑class entity in your domain model that you want to protect.\n */\nexport type Resource = {\n organization: Organization;\n project: Project;\n dictionary: Dictionary;\n tag: Tag;\n user: User;\n};\n\n/**\n * @description\n * A literal string combining a Resource and an Action, e.g. `\"project:write\"`.\n * This is the *unit* checked at runtime in your middleware.\n */\nexport type Permission = `${keyof Resource}:${Action}`;\n\ntype CheckPrivilege = (\n args: any\n) => boolean | undefined | Promise<boolean> | Promise<undefined>;\n\ntype RolePolicy = Record<Roles, Partial<Record<Permission, CheckPrivilege>>>;\n\nexport const ROLE_POLICY = {\n admin: {\n 'organization:read': () => true,\n 'organization:write': () => true,\n 'organization:admin': () => true,\n 'project:read': () => true,\n 'project:write': () => true,\n 'project:admin': () => true,\n 'dictionary:read': () => true,\n 'dictionary:write': () => true,\n 'dictionary:admin': () => true,\n 'tag:read': () => true,\n 'tag:write': () => true,\n 'tag:admin': () => true,\n 'user:read': () => true,\n 'user:write': () => true,\n 'user:admin': () => true,\n },\n org_admin: {\n 'organization:read': () => true,\n 'organization:write': () => true,\n 'organization:admin': () => true,\n\n 'project:read': () => true,\n 'project:write': () => true,\n 'project:admin': () => true,\n 'dictionary:read': () => true,\n 'dictionary:write': () => true,\n 'dictionary:admin': () => true,\n 'tag:read': () => true,\n 'tag:write': () => true,\n 'tag:admin': () => true,\n\n 'user:write': ({\n organization,\n targetUserIds,\n }: SessionContext & { targetUserIds: User['id'][] }) =>\n targetUserIds.every((targetUserId) =>\n organization?.membersIds?.map(String).includes(String(targetUserId))\n ),\n },\n org_user: {\n 'organization:read': () => true,\n\n 'user:read': ({\n organization,\n targetUserIds,\n }: SessionContext & { targetUserIds: User['id'][] }) =>\n targetUserIds.every((targetUserId) =>\n organization?.membersIds?.map(String).includes(String(targetUserId))\n ),\n },\n project_admin: {\n 'project:read': () => true,\n 'project:write': () => true,\n 'project:admin': () => true,\n 'tag:read': () => true,\n 'tag:write': () => true,\n 'tag:admin': () => true,\n\n 'user:write': ({\n project,\n targetUserIds,\n }: SessionContext & { targetUserIds: User['id'][] }) =>\n targetUserIds.every((targetUserId) =>\n project?.membersIds?.map(String).includes(String(targetUserId))\n ),\n },\n project_user: {\n 'project:read': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n\n 'dictionary:read': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n 'dictionary:write': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n\n 'tag:read': () => true,\n 'tag:write': () => true,\n 'tag:admin': ({ user, organization }: SessionContext) =>\n organization?.adminsIds?.map(String).includes(String(user?.id)),\n\n 'user:read': ({\n project,\n targetUserIds,\n }: SessionContext & { targetUserIds: User['id'][] }) =>\n targetUserIds.every((targetUserId) =>\n project?.membersIds?.map(String).includes(String(targetUserId))\n ),\n },\n project_reviewer: {\n 'dictionary:read': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n 'dictionary:write': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n\n 'tag:read': () => true,\n },\n} as const satisfies RolePolicy;\n\nexport const getSessionRoles = ({\n user,\n organization,\n project,\n}: SessionContext): Roles[] => {\n const roles: Roles[] = [];\n\n if (!user) {\n return roles;\n }\n\n const isUserAdmin = user.role === 'admin';\n\n if (isUserAdmin) {\n roles.push('admin');\n }\n\n if (!organization) {\n return roles;\n }\n\n const isOrganizationAdmin = organization.adminsIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isOrganizationAdmin) {\n roles.push('org_admin');\n }\n\n const isOrganizationMember = organization.membersIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isOrganizationMember) {\n roles.push('org_user');\n }\n\n if (!project) {\n return roles;\n }\n\n const isProjectAdmin = project.adminsIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isProjectAdmin) {\n roles.push('project_admin');\n }\n\n const isProjectMember = project?.membersIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isProjectMember) {\n roles.push('project_user');\n }\n\n // const isProjectReviewer =\n // session.project?.reviewersIds?.includes(session.user!.id);\n\n // if (isProjectReviewer) {\n // roles.push('project_reviewer');\n // }\n\n return roles;\n};\n\nexport const computeEffectivePermission = (roles: Roles[]): Permission[] =>\n Array.from(\n new Set(\n roles.flatMap((role) => Object.keys(ROLE_POLICY[role]) as Permission[])\n )\n );\n\n/**\n * Intersect two permission lists\n * @param permissionList1 - The first permission list\n * @param permissionList2 - The second permission list\n * @returns The intersection of the two permission lists (only permissions present in both)\n */\nexport const intersectPermissions = (\n permissions1: Permission[],\n permissions2: Permission[]\n): Permission[] =>\n permissions1.filter((permission) => permissions2.includes(permission));\n\ntype PermissionResult<\n R extends Roles,\n P extends Permission,\n> = (typeof ROLE_POLICY)[R] extends infer RolePerms\n ? RolePerms extends Record<string, (args: any) => any>\n ? P extends keyof RolePerms\n ? RolePerms[P] extends undefined\n ? never\n : RolePerms[P]\n : never\n : never\n : never;\n\nexport const hasPermission = <P extends Permission>(\n roles: Roles[],\n permission: P\n): PermissionResult<Roles, P> => {\n const rolesCheck: any = roles.map(\n (role) =>\n ROLE_POLICY[role]?.[\n permission as keyof (typeof ROLE_POLICY)[typeof role]\n ] ?? (() => false)\n ) as unknown as PermissionResult<Roles, P>[];\n\n return ((args: any) => rolesCheck.some((check: any) => check(args))) as any;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/utils/permissions.ts"],"sourcesContent":["import { Dictionary, DictionaryAPI } from '@/types/dictionary.types';\nimport { Organization, OrganizationAPI } from '@/types/organization.types';\nimport { Project, ProjectAPI } from '@/types/project.types';\nimport { SessionContext } from '@/types/session.types';\nimport { Tag, TagAPI } from '@/types/tag.types';\nimport { User, UserAPI } from '@/types/user.types';\n\n/**\n * A named grouping of privileges (e.g. `\"org_admin\"`).\n * Users are *granted* one or more Roles.\n */\nexport type Roles =\n | 'user'\n | 'admin'\n | 'org_admin'\n | 'org_user'\n | 'project_admin'\n | 'project_user'\n | 'project_reviewer';\n\n/**\n * An atomic operation that can be performed on a resource.\n * - **read**: view or list\n * - **write**: create or update\n * - **admin**: delete or change permissions\n */\nexport type Action = 'read' | 'write' | 'admin';\n\n/**\n * A first‑class entity in your domain model that you want to protect.\n */\nexport type Resource = {\n organization: Organization;\n project: Project;\n dictionary: Dictionary;\n tag: Tag;\n user: User;\n};\n\n/**\n * A literal string combining a Resource and an Action, e.g. `\"project:write\"`.\n * This is the *unit* checked at runtime in your middleware.\n */\nexport type Permission = `${keyof Resource}:${Action}`;\n\ntype CheckPrivilege = (\n args: any\n) => boolean | undefined | Promise<boolean> | Promise<undefined>;\n\ntype RolePolicy = Record<Roles, Partial<Record<Permission, CheckPrivilege>>>;\n\nexport const ROLE_POLICY = {\n admin: {\n 'organization:read': () => true,\n 'organization:write': () => true,\n 'organization:admin': () => true,\n 'project:read': () => true,\n 'project:write': () => true,\n 'project:admin': () => true,\n 'dictionary:read': () => true,\n 'dictionary:write': () => true,\n 'dictionary:admin': () => true,\n 'tag:read': () => true,\n 'tag:write': () => true,\n 'tag:admin': () => true,\n 'user:read': () => true,\n 'user:write': () => true,\n 'user:admin': () => true,\n },\n user: {\n 'user:read': ({\n user,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every(\n (targetUser) =>\n String(targetUser.id) === String(user?.id) &&\n targetUser.email === user?.email\n ),\n 'user:write': ({\n user,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every(\n (targetUser) =>\n String(targetUser.id) === String(user?.id) &&\n targetUser.email === user?.email\n ),\n 'user:admin': ({\n user,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every(\n (targetUser) =>\n String(targetUser.id) === String(user?.id) &&\n targetUser.email === user?.email\n ),\n 'organization:read': ({\n user,\n targetOrganizations,\n }: SessionContext & {\n targetOrganizations: (Organization | OrganizationAPI)[];\n }) =>\n targetOrganizations.every((organization) =>\n organization?.membersIds?.map(String).includes(String(user?.id))\n ),\n },\n org_admin: {\n 'organization:read': ({\n organization,\n targetOrganizations,\n }: SessionContext & {\n targetOrganizations: (Organization | OrganizationAPI)[];\n }) =>\n targetOrganizations.every(\n (targetOrg) => String(targetOrg.id) === String(organization?.id)\n ),\n 'organization:write': ({\n organization,\n targetOrganizations,\n }: SessionContext & {\n targetOrganizations: (Organization | OrganizationAPI)[];\n }) =>\n targetOrganizations.every(\n (targetOrg) => String(targetOrg.id) === String(organization?.id)\n ),\n 'organization:admin': ({\n organization,\n targetOrganizations,\n }: SessionContext & {\n targetOrganizations: (Organization | OrganizationAPI)[];\n }) =>\n targetOrganizations.every(\n (targetOrg) => String(targetOrg.id) === String(organization?.id)\n ),\n\n 'project:read': ({ organization, project }: SessionContext) =>\n String(project?.organizationId) === String(organization?.id),\n 'project:write': ({ organization, project }: SessionContext) =>\n String(project?.organizationId) === String(organization?.id),\n 'project:admin': ({ organization, project }: SessionContext) =>\n String(project?.organizationId) === String(organization?.id),\n\n 'tag:read': ({ organization, project }: SessionContext) =>\n String(project?.organizationId) === String(organization?.id),\n 'tag:write': ({ organization, project }: SessionContext) =>\n String(project?.organizationId) === String(organization?.id),\n 'tag:admin': ({ organization, project }: SessionContext) =>\n String(project?.organizationId) === String(organization?.id),\n\n 'user:write': ({\n organization,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every((targetUser) =>\n organization?.membersIds?.map(String).includes(String(targetUser?.id))\n ),\n },\n org_user: {\n 'organization:read': ({\n organization,\n targetOrganizations,\n }: SessionContext & {\n targetOrganizations: (Organization | OrganizationAPI)[];\n }) =>\n targetOrganizations.every(\n (targetOrg) => String(targetOrg.id) === String(organization?.id)\n ),\n\n 'project:read': ({\n user,\n targetProjects,\n }: SessionContext & {\n targetProjects: (Project | ProjectAPI)[];\n }) =>\n targetProjects?.every((project) =>\n project?.membersIds?.map(String).includes(String(user?.id))\n ),\n\n 'user:read': ({\n organization,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every((targetUser) =>\n organization?.membersIds?.map(String).includes(String(targetUser?.id))\n ),\n },\n project_admin: {\n 'project:read': ({\n project,\n targetProjectIds,\n }: SessionContext & {\n targetProjectIds?: (Project | ProjectAPI)['id'][];\n }) =>\n targetProjectIds?.every(\n (projectId) => String(project?.id) === String(projectId)\n ),\n 'project:write': ({\n project,\n targetProjectIds,\n }: SessionContext & {\n targetProjectIds?: (Project | ProjectAPI)['id'][];\n }) =>\n targetProjectIds?.every(\n (projectId) => String(project?.id) === String(projectId)\n ),\n 'project:admin': ({\n project,\n targetProjectIds,\n }: SessionContext & {\n targetProjectIds?: (Project | ProjectAPI)['id'][];\n }) =>\n targetProjectIds?.every(\n (projectId) => String(project?.id) === String(projectId)\n ),\n\n 'tag:read': ({\n project,\n targetTags,\n }: SessionContext & { targetTags: (Tag | TagAPI)[] }) =>\n targetTags.every((tag) => String(project?.id) === String(tag?.projectId)),\n 'tag:write': ({\n project,\n targetTags,\n }: SessionContext & { targetTags: (Tag | TagAPI)[] }) =>\n targetTags.every((tag) => String(project?.id) === String(tag?.projectId)),\n 'tag:admin': ({\n project,\n targetTags,\n }: SessionContext & { targetTags: (Tag | TagAPI)[] }) =>\n targetTags.every((tag) => String(project?.id) === String(tag?.projectId)),\n\n 'user:write': ({\n project,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every((targetUser) =>\n project?.membersIds?.map(String).includes(String(targetUser?.id))\n ),\n\n 'dictionary:read': ({ project, user }: SessionContext) =>\n project?.adminsIds?.map(String).includes(String(user?.id)),\n 'dictionary:write': ({ project, user }: SessionContext) =>\n project?.adminsIds?.map(String).includes(String(user?.id)),\n 'dictionary:admin': ({ project, user }: SessionContext) =>\n project?.adminsIds?.map(String).includes(String(user?.id)),\n },\n project_user: {\n 'project:read': ({ user, organization, project }: SessionContext) =>\n String(organization?.id) === String(project?.organizationId) &&\n organization?.membersIds?.map(String).includes(String(user?.id)) &&\n project?.membersIds?.map(String).includes(String(user?.id)),\n\n 'dictionary:read': ({\n user,\n project,\n targetDictionaries,\n }: SessionContext & {\n targetDictionaries: (Dictionary | DictionaryAPI)[];\n }) =>\n project?.membersIds?.map(String).includes(String(user?.id)) &&\n targetDictionaries.every((dictionary) =>\n dictionary?.projectIds?.map(String).includes(String(project?.id))\n ),\n 'dictionary:write': ({ user, project }: SessionContext) =>\n project?.adminsIds?.map(String).includes(String(user?.id)),\n\n 'tag:read': ({\n project,\n targetTags,\n user,\n }: SessionContext & {\n targetTags: (Tag | TagAPI)[];\n }) =>\n project?.membersIds?.map(String).includes(String(user?.id)) &&\n targetTags.every((tag) => String(project?.id) === String(tag?.projectId)),\n 'tag:write': ({\n project,\n targetTags,\n user,\n }: SessionContext & {\n targetTags: (Tag | TagAPI)[];\n }) =>\n project?.membersIds?.map(String).includes(String(user?.id)) &&\n targetTags.every((tag) => String(project?.id) === String(tag?.projectId)),\n 'tag:admin': ({\n user,\n project,\n targetTags,\n }: SessionContext & {\n targetTags: (Tag | TagAPI)[];\n }) =>\n project?.adminsIds?.map(String).includes(String(user?.id)) &&\n targetTags.every((tag) => String(project?.id) === String(tag?.projectId)),\n\n 'user:read': ({\n project,\n targetUsers,\n }: SessionContext & { targetUsers: (User | UserAPI)[] }) =>\n targetUsers.every((targetUser) =>\n project?.membersIds?.map(String).includes(String(targetUser?.id))\n ),\n },\n project_reviewer: {\n 'dictionary:read': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n 'dictionary:write': ({ user, project }: SessionContext) =>\n project?.membersIds?.map(String).includes(String(user?.id)),\n\n 'tag:read': () => true,\n },\n} as const satisfies RolePolicy;\n\nexport const getSessionRoles = ({\n user,\n organization,\n project,\n}: SessionContext): Roles[] => {\n const roles: Roles[] = [];\n\n if (!user) {\n return roles;\n }\n\n roles.push('user');\n\n const isUserAdmin = user.role === 'admin';\n\n if (isUserAdmin) {\n roles.push('admin');\n }\n\n if (!organization) {\n return roles;\n }\n\n const isOrganizationAdmin = organization.adminsIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isOrganizationAdmin) {\n roles.push('org_admin');\n }\n\n const isOrganizationMember = organization.membersIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isOrganizationMember) {\n roles.push('org_user');\n }\n\n if (!project) {\n return roles;\n }\n\n const isProjectAdmin = project.adminsIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isProjectAdmin) {\n roles.push('project_admin');\n }\n\n const isProjectMember = project?.membersIds\n ?.map(String)\n .includes(String(user!.id));\n\n if (isProjectMember) {\n roles.push('project_user');\n }\n\n // const isProjectReviewer =\n // session.project?.reviewersIds?.includes(session.user!.id);\n\n // if (isProjectReviewer) {\n // roles.push('project_reviewer');\n // }\n\n return roles;\n};\n\nexport const computeEffectivePermission = (roles: Roles[]): Permission[] =>\n Array.from(\n new Set(\n roles.flatMap((role) => Object.keys(ROLE_POLICY[role]) as Permission[])\n )\n );\n\n/**\n * Intersect two permission lists\n * @param permissionList1 - The first permission list\n * @param permissionList2 - The second permission list\n * @returns The intersection of the two permission lists (only permissions present in both)\n */\nexport const intersectPermissions = (\n permissions1: Permission[],\n permissions2: Permission[]\n): Permission[] =>\n permissions1.filter((permission) => permissions2.includes(permission));\n\ntype PermissionResult<\n R extends Roles,\n P extends Permission,\n> = (typeof ROLE_POLICY)[R] extends infer RolePerms\n ? RolePerms extends Record<string, (args: any) => any>\n ? P extends keyof RolePerms\n ? RolePerms[P] extends undefined\n ? never\n : RolePerms[P]\n : never\n : never\n : never;\n\nexport const hasPermission = <P extends Permission>(\n roles: Roles[],\n permission: P\n): PermissionResult<Roles, P> => {\n const rolesCheck: any = roles.map(\n (role) =>\n ROLE_POLICY[role]?.[\n permission as keyof (typeof ROLE_POLICY)[typeof role]\n ] ?? (() => false)\n ) as unknown as PermissionResult<Roles, P>[];\n\n return ((args: any) => rolesCheck.some((check: any) => check(args))) as any;\n};\n"],"mappings":"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","names":[]}
@@ -30,7 +30,11 @@ const validateArray = (value, entityName = "Array", elementType, elementValidato
30
30
  } else if (maxLength && value.length > maxLength) {
31
31
  errors.push(`${entityName} must be at most ${maxLength} items long.`);
32
32
  } else if (elementType) {
33
- const isValid = value.some((element) => typeof element !== elementType);
33
+ const isValid = value.every((element) => {
34
+ console.log("element", element, typeof element, elementType);
35
+ return typeof element === elementType;
36
+ });
37
+ console.log("isValid", isValid);
34
38
  if (!isValid) {
35
39
  errors.push(`${entityName} must contain only ${elementType} elements.`);
36
40
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/utils/validation/validateArray.ts"],"sourcesContent":["/* eslint-disable sonarjs/cognitive-complexity */\ntype ElementType = 'string' | 'number' | 'boolean' | 'object' | 'array';\ntype ElementValidator<T> = (value: T) => boolean | string[];\n\n/**\n * Validates a email field.\n * @param value The value to validate.\n * @param entityName The name of the entity being validated.\n * @param minLength The minimum length of the string.\n * @param maxLength The maximum length of the string.\n * @returns An array of validation errors.\n */\nexport const validateArray = <T = unknown>(\n value: T[],\n entityName = 'Array',\n elementType?: ElementType,\n elementValidator?: ElementValidator<T>,\n minLength = 0,\n maxLength = undefined\n): string[] => {\n const errors: string[] = [];\n\n if (!Array.isArray(value)) {\n errors.push(`${entityName} must be an array.`);\n } else if (minLength && value.length < minLength) {\n errors.push(`${entityName} must be at least ${minLength} items long.`);\n } else if (maxLength && value.length > maxLength) {\n errors.push(`${entityName} must be at most ${maxLength} items long.`);\n } else if (elementType) {\n const isValid = value.some((element) => typeof element !== elementType);\n\n if (!isValid) {\n errors.push(`${entityName} must contain only ${elementType} elements.`);\n }\n } else if (elementValidator) {\n const isValidOrErrorList = value.every(elementValidator);\n\n if (Array.isArray(isValidOrErrorList)) {\n const isValid = isValidOrErrorList.length > 0;\n\n if (!isValid) {\n errors.push(...isValidOrErrorList);\n }\n } else {\n const isValid = isValidOrErrorList;\n\n if (!isValid) {\n errors.push(`${entityName} must contain valid elements.`);\n }\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,MAAM,gBAAgB,CAC3B,OACA,aAAa,SACb,aACA,kBACA,YAAY,GACZ,YAAY,WACC;AACb,QAAM,SAAmB,CAAC;AAE1B,MAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,WAAO,KAAK,GAAG,UAAU,oBAAoB;AAAA,EAC/C,WAAW,aAAa,MAAM,SAAS,WAAW;AAChD,WAAO,KAAK,GAAG,UAAU,qBAAqB,SAAS,cAAc;AAAA,EACvE,WAAW,aAAa,MAAM,SAAS,WAAW;AAChD,WAAO,KAAK,GAAG,UAAU,oBAAoB,SAAS,cAAc;AAAA,EACtE,WAAW,aAAa;AACtB,UAAM,UAAU,MAAM,KAAK,CAAC,YAAY,OAAO,YAAY,WAAW;AAEtE,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,GAAG,UAAU,sBAAsB,WAAW,YAAY;AAAA,IACxE;AAAA,EACF,WAAW,kBAAkB;AAC3B,UAAM,qBAAqB,MAAM,MAAM,gBAAgB;AAEvD,QAAI,MAAM,QAAQ,kBAAkB,GAAG;AACrC,YAAM,UAAU,mBAAmB,SAAS;AAE5C,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,GAAG,kBAAkB;AAAA,MACnC;AAAA,IACF,OAAO;AACL,YAAM,UAAU;AAEhB,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,GAAG,UAAU,+BAA+B;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../src/utils/validation/validateArray.ts"],"sourcesContent":["/* eslint-disable sonarjs/cognitive-complexity */\ntype ElementType = 'string' | 'number' | 'boolean' | 'object' | 'array';\ntype ElementValidator<T> = (value: T) => boolean | string[];\n\n/**\n * Validates a email field.\n * @param value The value to validate.\n * @param entityName The name of the entity being validated.\n * @param minLength The minimum length of the string.\n * @param maxLength The maximum length of the string.\n * @returns An array of validation errors.\n */\nexport const validateArray = <T = unknown>(\n value: T[],\n entityName = 'Array',\n elementType?: ElementType,\n elementValidator?: ElementValidator<T>,\n minLength = 0,\n maxLength = undefined\n): string[] => {\n const errors: string[] = [];\n\n if (!Array.isArray(value)) {\n errors.push(`${entityName} must be an array.`);\n } else if (minLength && value.length < minLength) {\n errors.push(`${entityName} must be at least ${minLength} items long.`);\n } else if (maxLength && value.length > maxLength) {\n errors.push(`${entityName} must be at most ${maxLength} items long.`);\n } else if (elementType) {\n const isValid = value.every((element) => {\n console.log('element', element, typeof element, elementType);\n return typeof element === elementType;\n });\n\n console.log('isValid', isValid);\n\n if (!isValid) {\n errors.push(`${entityName} must contain only ${elementType} elements.`);\n }\n } else if (elementValidator) {\n const isValidOrErrorList = value.every(elementValidator);\n\n if (Array.isArray(isValidOrErrorList)) {\n const isValid = isValidOrErrorList.length > 0;\n\n if (!isValid) {\n errors.push(...isValidOrErrorList);\n }\n } else {\n const isValid = isValidOrErrorList;\n\n if (!isValid) {\n errors.push(`${entityName} must contain valid elements.`);\n }\n }\n }\n\n return errors;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,MAAM,gBAAgB,CAC3B,OACA,aAAa,SACb,aACA,kBACA,YAAY,GACZ,YAAY,WACC;AACb,QAAM,SAAmB,CAAC;AAE1B,MAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,WAAO,KAAK,GAAG,UAAU,oBAAoB;AAAA,EAC/C,WAAW,aAAa,MAAM,SAAS,WAAW;AAChD,WAAO,KAAK,GAAG,UAAU,qBAAqB,SAAS,cAAc;AAAA,EACvE,WAAW,aAAa,MAAM,SAAS,WAAW;AAChD,WAAO,KAAK,GAAG,UAAU,oBAAoB,SAAS,cAAc;AAAA,EACtE,WAAW,aAAa;AACtB,UAAM,UAAU,MAAM,MAAM,CAAC,YAAY;AACvC,cAAQ,IAAI,WAAW,SAAS,OAAO,SAAS,WAAW;AAC3D,aAAO,OAAO,YAAY;AAAA,IAC5B,CAAC;AAED,YAAQ,IAAI,WAAW,OAAO;AAE9B,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,GAAG,UAAU,sBAAsB,WAAW,YAAY;AAAA,IACxE;AAAA,EACF,WAAW,kBAAkB;AAC3B,UAAM,qBAAqB,MAAM,MAAM,gBAAgB;AAEvD,QAAI,MAAM,QAAQ,kBAAkB,GAAG;AACrC,YAAM,UAAU,mBAAmB,SAAS;AAE5C,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,GAAG,kBAAkB;AAAA,MACnC;AAAA,IACF,OAAO;AACL,YAAM,UAAU;AAEhB,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,GAAG,UAAU,+BAA+B;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { Tag } from '@/types/tag.types';\nimport { type KeyPath } from '@intlayer/core';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport {\n AIConfig,\n getAIConfig,\n type AIOptions,\n type ChatCompletionRequestMessage,\n} from '@utils/AI/aiSdk';\nimport * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport * as auditContentDeclarationUtil from '@utils/AI/auditDictionary';\nimport * as auditContentDeclarationFieldUtil from '@utils/AI/auditDictionaryField';\nimport * as auditContentDeclarationMetadataUtil from '@utils/AI/auditDictionaryMetadata';\nimport * as auditTagUtil from '@utils/AI/auditTag';\nimport * as autocompleteUtil from '@utils/AI/autocomplete';\nimport * as customQueryUtil from '@utils/AI/customQuery';\nimport * as translateJSONUtil from '@utils/AI/translateJSON';\nimport { ErrorHandler, type AppError } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { NextFunction, Request, Response } from 'express';\nimport type { Locales } from 'intlayer';\n\ntype ReplaceAIConfigByOptions<T> = Omit<T, 'aiConfig'> & {\n aiOptions?: AIOptions;\n};\n\nexport type CustomQueryBody =\n ReplaceAIConfigByOptions<customQueryUtil.CustomQueryOptions>;\nexport type CustomQueryResult =\n ResponseData<customQueryUtil.CustomQueryResultData>;\n\nexport const customQuery = async (\n req: Request<AuditContentDeclarationBody>,\n res: Response<CustomQueryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: customQueryUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n const auditResponse = await customQueryUtil.customQuery({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'QUERY_FAILED');\n return;\n }\n\n const responseData = formatResponse<customQueryUtil.CustomQueryResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type TranslateJSONBody = Omit<\n ReplaceAIConfigByOptions<translateJSONUtil.TranslateJSONOptions>,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult =\n ResponseData<translateJSONUtil.TranslateJSONResultData>;\n\nexport const translateJSON = async (\n req: Request<AuditContentDeclarationBody>,\n res: Response<TranslateJSONResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: translateJSONUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await translateJSONUtil.translateJSON({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<translateJSONUtil.TranslateJSONResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locales[];\n defaultLocale: Locales;\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n};\nexport type AuditContentDeclarationResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclaration = async (\n req: Request<AuditContentDeclarationBody>,\n res: Response<AuditContentDeclarationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await auditContentDeclarationUtil.auditDictionary({\n fileContent,\n filePath,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n defaultLocale,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationFieldBody = {\n aiOptions?: AIOptions;\n locales: Locales[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n req: Request<AuditContentDeclarationFieldBody>,\n res: Response<AuditContentDeclarationFieldResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationFieldUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse =\n await auditContentDeclarationFieldUtil.auditDictionaryField({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n tags,\n keyPath,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationMetadata = async (\n req: Request<AuditContentDeclarationMetadataBody>,\n res: Response<AuditContentDeclarationMetadataResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization } = res.locals;\n const { fileContent, aiOptions } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationMetadataUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n const tags: Tag[] = await tagService.findTags(\n {\n organizationId: organization?.id,\n },\n 0,\n 1000\n );\n\n const auditResponse =\n await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditTagBody = {\n aiOptions?: AIOptions;\n tag: Tag;\n};\nexport type AuditTagResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditTag = async (\n req: Request<undefined, undefined, AuditTagBody>,\n res: Response<AuditTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { aiOptions, tag } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditTagUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let dictionaries: Dictionary[] = [];\n if (project?.organizationId) {\n dictionaries = await getDictionariesByTags([tag.key], project.id);\n }\n\n const auditResponse = await auditTagUtil.auditTag({\n aiConfig,\n dictionaries,\n tag,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AskDocQuestionBody = {\n messages: ChatCompletionRequestMessage[];\n discutionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n req: Request<undefined, undefined, AskDocQuestionBody>,\n res: Response<AskDocQuestionResult>\n) => {\n const { messages, discutionId } = req.body;\n const { user, project, organization } = res.locals;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: {},\n defaultOptions: askDocQuestionUtil.aiDefaultOptions,\n accessType: ['public'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n // 1. Prepare SSE headers and flush them NOW\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering\n res.flushHeaders?.();\n res.write(': connected\\n\\n'); // initial comment keeps some browsers happy\n res.flush?.();\n\n // 2. Kick off the upstream stream WITHOUT awaiting it\n askDocQuestionUtil\n .askDocQuestion(messages, aiConfig, {\n onMessage: (chunk) => {\n res.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n res.flush?.();\n },\n })\n .then(async (fullResponse) => {\n const lastUserMessageContent = messages.findLast(\n (msg) => msg.role === 'user'\n )?.content;\n const lastUserMessageNbWords = lastUserMessageContent\n ? lastUserMessageContent.split(' ').length\n : 0;\n if (lastUserMessageNbWords > 2) {\n // If the last user message is less than 3 words, don't persist the discussion\n // Example: \"Hello\", \"Hi\", \"Hey\", \"test\", etc.\n\n // 3. Persist discussion while the client already has all chunks\n await DiscussionModel.findOneAndUpdate(\n { discutionId },\n {\n $set: {\n discutionId,\n userId: user?.id,\n projectId: project?.id,\n organizationId: organization?.id,\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp,\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n },\n },\n { upsert: true, new: true }\n );\n }\n\n // 4. Tell the client we're done and close the stream\n res.write(\n `data: ${JSON.stringify({ done: true, response: fullResponse })}\\n\\n`\n );\n res.end();\n })\n .catch((err) => {\n // propagate error as an SSE event so the client knows why it closed\n res.write(\n `event: error\\ndata: ${JSON.stringify({ message: err.message })}\\n\\n`\n );\n res.end();\n });\n};\n\nexport type AutocompleteBody = {\n text: string;\n aiOptions?: AIOptions;\n contextBefore?: string;\n currentLine?: string;\n contextAfter?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n req: Request<undefined, undefined, AutocompleteBody>,\n res: Response<AutocompleteResponse>\n) => {\n try {\n const { text, aiOptions, contextBefore, currentLine, contextAfter } =\n req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: autocompleteUtil.aiDefaultOptions,\n accessType: ['public'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n contextBefore,\n currentLine,\n contextAfter,\n })) ?? {\n autocompletion: '',\n tokenUsed: 0,\n };\n\n const responseData =\n formatResponse<autocompleteUtil.AutocompleteFileResultData>({\n data: response,\n });\n\n res.json(responseData);\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { Tag } from '@/types/tag.types';\nimport { type KeyPath } from '@intlayer/core';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport {\n AIConfig,\n getAIConfig,\n type AIOptions,\n type ChatCompletionRequestMessage,\n} from '@utils/AI/aiSdk';\nimport * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport * as auditContentDeclarationUtil from '@utils/AI/auditDictionary';\nimport * as auditContentDeclarationFieldUtil from '@utils/AI/auditDictionaryField';\nimport * as auditContentDeclarationMetadataUtil from '@utils/AI/auditDictionaryMetadata';\nimport * as auditTagUtil from '@utils/AI/auditTag';\nimport * as autocompleteUtil from '@utils/AI/autocomplete';\nimport * as customQueryUtil from '@utils/AI/customQuery';\nimport * as translateJSONUtil from '@utils/AI/translateJSON';\nimport { ErrorHandler, type AppError } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport type { Locales } from 'intlayer';\n\ntype ReplaceAIConfigByOptions<T> = Omit<T, 'aiConfig'> & {\n aiOptions?: AIOptions;\n};\n\nexport type CustomQueryBody =\n ReplaceAIConfigByOptions<customQueryUtil.CustomQueryOptions>;\nexport type CustomQueryResult =\n ResponseData<customQueryUtil.CustomQueryResultData>;\n\nexport const customQuery = async (\n req: Request<AuditContentDeclarationBody>,\n res: ResponseWithSession<CustomQueryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: customQueryUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n const auditResponse = await customQueryUtil.customQuery({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'QUERY_FAILED');\n return;\n }\n\n const responseData = formatResponse<customQueryUtil.CustomQueryResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type TranslateJSONBody = Omit<\n ReplaceAIConfigByOptions<translateJSONUtil.TranslateJSONOptions>,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult =\n ResponseData<translateJSONUtil.TranslateJSONResultData>;\n\nexport const translateJSON = async (\n req: Request<AuditContentDeclarationBody>,\n res: ResponseWithSession<TranslateJSONResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: translateJSONUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await translateJSONUtil.translateJSON({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<translateJSONUtil.TranslateJSONResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locales[];\n defaultLocale: Locales;\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n};\nexport type AuditContentDeclarationResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclaration = async (\n req: Request<AuditContentDeclarationBody>,\n res: ResponseWithSession<AuditContentDeclarationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await auditContentDeclarationUtil.auditDictionary({\n fileContent,\n filePath,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n defaultLocale,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationFieldBody = {\n aiOptions?: AIOptions;\n locales: Locales[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n req: Request<AuditContentDeclarationFieldBody>,\n res: ResponseWithSession<AuditContentDeclarationFieldResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationFieldUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse =\n await auditContentDeclarationFieldUtil.auditDictionaryField({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n tags,\n keyPath,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationMetadata = async (\n req: Request<AuditContentDeclarationMetadataBody>,\n res: ResponseWithSession<AuditContentDeclarationMetadataResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization } = res.locals;\n const { fileContent, aiOptions } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationMetadataUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n const tags: Tag[] = await tagService.findTags(\n {\n organizationId: organization?.id,\n },\n 0,\n 1000\n );\n\n const auditResponse =\n await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditTagBody = {\n aiOptions?: AIOptions;\n tag: Tag;\n};\nexport type AuditTagResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditTag = async (\n req: Request<undefined, undefined, AuditTagBody>,\n res: ResponseWithSession<AuditTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { aiOptions, tag } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: auditTagUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let dictionaries: Dictionary[] = [];\n if (project?.organizationId) {\n dictionaries = await getDictionariesByTags([tag.key], project.id);\n }\n\n const auditResponse = await auditTagUtil.auditTag({\n aiConfig,\n dictionaries,\n tag,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AskDocQuestionBody = {\n messages: ChatCompletionRequestMessage[];\n discutionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n req: Request<undefined, undefined, AskDocQuestionBody>,\n res: ResponseWithSession<AskDocQuestionResult>\n) => {\n const { messages, discutionId } = req.body;\n const { user, project, organization } = res.locals;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: {},\n defaultOptions: askDocQuestionUtil.aiDefaultOptions,\n accessType: ['public'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n // 1. Prepare SSE headers and flush them NOW\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering\n res.flushHeaders?.();\n res.write(': connected\\n\\n'); // initial comment keeps some browsers happy\n res.flush?.();\n\n // 2. Kick off the upstream stream WITHOUT awaiting it\n askDocQuestionUtil\n .askDocQuestion(messages, aiConfig, {\n onMessage: (chunk) => {\n res.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n res.flush?.();\n },\n })\n .then(async (fullResponse) => {\n const lastUserMessageContent = messages.findLast(\n (msg) => msg.role === 'user'\n )?.content;\n const lastUserMessageNbWords = lastUserMessageContent\n ? lastUserMessageContent.split(' ').length\n : 0;\n if (lastUserMessageNbWords > 2) {\n // If the last user message is less than 3 words, don't persist the discussion\n // Example: \"Hello\", \"Hi\", \"Hey\", \"test\", etc.\n\n // 3. Persist discussion while the client already has all chunks\n await DiscussionModel.findOneAndUpdate(\n { discutionId },\n {\n $set: {\n discutionId,\n userId: user?.id,\n projectId: project?.id,\n organizationId: organization?.id,\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp,\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n },\n },\n { upsert: true, new: true }\n );\n }\n\n // 4. Tell the client we're done and close the stream\n res.write(\n `data: ${JSON.stringify({ done: true, response: fullResponse })}\\n\\n`\n );\n res.end();\n })\n .catch((err) => {\n // propagate error as an SSE event so the client knows why it closed\n res.write(\n `event: error\\ndata: ${JSON.stringify({ message: err.message })}\\n\\n`\n );\n res.end();\n });\n};\n\nexport type AutocompleteBody = {\n text: string;\n aiOptions?: AIOptions;\n contextBefore?: string;\n currentLine?: string;\n contextAfter?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n req: Request<undefined, undefined, AutocompleteBody>,\n res: ResponseWithSession<AutocompleteResponse>\n) => {\n try {\n const { text, aiOptions, contextBefore, currentLine, contextAfter } =\n req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(res, {\n userOptions: aiOptions,\n defaultOptions: autocompleteUtil.aiDefaultOptions,\n accessType: ['public'],\n });\n } catch (error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n contextBefore,\n currentLine,\n contextAfter,\n })) ?? {\n autocompletion: '',\n tokenUsed: 0,\n };\n\n const responseData =\n formatResponse<autocompleteUtil.AutocompleteFileResultData>({\n data: response,\n });\n\n res.json(responseData);\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}
@@ -24,10 +24,6 @@ const getDictionaries = async (req, res, _next) => {
24
24
  ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
25
25
  return;
26
26
  }
27
- if (!hasPermission(roles, "dictionary:read")(res.locals)) {
28
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
29
- return;
30
- }
31
27
  try {
32
28
  const dictionaries = await dictionaryService.findDictionaries(
33
29
  {
@@ -37,6 +33,16 @@ const getDictionaries = async (req, res, _next) => {
37
33
  skip,
38
34
  pageSize
39
35
  );
36
+ if (!hasPermission(
37
+ roles,
38
+ "dictionary:read"
39
+ )({
40
+ ...res.locals,
41
+ targetDictionaries: dictionaries
42
+ })) {
43
+ ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
44
+ return;
45
+ }
40
46
  const totalItems = await dictionaryService.countDictionaries(filters);
41
47
  const dictionariesAPI = dictionaries.map(
42
48
  (el) => mapDictionaryToAPI(el, project.id)
@@ -61,14 +67,21 @@ const getDictionariesKeys = async (_req, res, _next) => {
61
67
  ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
62
68
  return;
63
69
  }
64
- if (!hasPermission(roles, "dictionary:read")(res.locals)) {
65
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
66
- return;
67
- }
68
70
  try {
69
- const dictionariesKeys = await dictionaryService.getDictionariesKeys(
70
- project.id
71
- );
71
+ const dictionaries = await dictionaryService.findDictionaries({
72
+ projectIds: project.id
73
+ });
74
+ if (!hasPermission(
75
+ roles,
76
+ "dictionary:read"
77
+ )({
78
+ ...res.locals,
79
+ targetDictionaries: dictionaries
80
+ })) {
81
+ ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
82
+ return;
83
+ }
84
+ const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);
72
85
  const responseData = formatResponse({
73
86
  data: dictionariesKeys
74
87
  });
@@ -91,15 +104,21 @@ const getDictionaryByKey = async (req, res, _next) => {
91
104
  ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
92
105
  return;
93
106
  }
94
- if (!hasPermission(roles, "dictionary:read")(res.locals)) {
95
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
96
- return;
97
- }
98
107
  try {
99
108
  const dictionary = await dictionaryService.getDictionaryByKey(
100
109
  dictionaryKey,
101
110
  project.id
102
111
  );
112
+ if (!hasPermission(
113
+ roles,
114
+ "dictionary:read"
115
+ )({
116
+ ...res.locals,
117
+ targetDictionaries: [dictionary]
118
+ })) {
119
+ ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
120
+ return;
121
+ }
103
122
  if (!dictionary.projectIds.map(String).includes(String(project.id))) {
104
123
  ErrorHandler.handleGenericErrorResponse(
105
124
  res,
@@ -137,10 +156,6 @@ const addDictionary = async (req, res, _next) => {
137
156
  ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_PROJECT_MISMATCH");
138
157
  return;
139
158
  }
140
- if (!hasPermission(roles, "dictionary:write")(res.locals)) {
141
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
142
- return;
143
- }
144
159
  const dictionary = {
145
160
  key: dictionaryData.key,
146
161
  title: dictionaryData.title,
@@ -154,6 +169,10 @@ const addDictionary = async (req, res, _next) => {
154
169
  },
155
170
  projectIds: dictionaryData.projectIds ?? [String(project.id)]
156
171
  };
172
+ if (!hasPermission(roles, "dictionary:write")(res.locals)) {
173
+ ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
174
+ return;
175
+ }
157
176
  try {
158
177
  const newDictionary = await dictionaryService.createDictionary(dictionary);
159
178
  const apiResult = mapDictionaryToAPI(newDictionary, project.id);
@@ -401,7 +420,7 @@ const deleteDictionary = async (req, res, _next) => {
401
420
  ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
402
421
  return;
403
422
  }
404
- if (!hasPermission(roles, "dictionary:admin")()) {
423
+ if (!hasPermission(roles, "dictionary:admin")(res.locals)) {
405
424
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
406
425
  return;
407
426
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n Dictionary as LocalDictionary,\n} from '@intlayer/core';\nimport { logger } from '@logger';\nimport * as dictionaryService from '@services/dictionary.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request, Response } from 'express';\nimport { t } from 'express-intlayer';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n req: Request<GetDictionariesParams>,\n res: Response<GetDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(req);\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:read')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize\n );\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) =>\n mapDictionaryToAPI(el, project.id)\n );\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _req: Request,\n res: Response<GetDictionariesKeysResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:read')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionariesKeys = await dictionaryService.getDictionariesKeys(\n project.id\n );\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n req: Request<GetDictionaryParams, any, any, GetDictionaryQuery>,\n res: Response<GetDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const { dictionaryKey } = req.params;\n const version = req.query.version;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:read')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, project.id, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n req: Request<any, any, AddDictionaryBody>,\n res: Response<AddDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionary;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n ['v1', { content: dictionaryData.content ?? ({} as ContentNode) }],\n ]),\n creatorId: user.id,\n filePath: {\n [String(project.id)]: dictionaryData.filePath ?? '',\n },\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary, project.id),\n status: 'ADDED',\n },\n ]);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: string[];\n updatedDictionaries: string[];\n error: { dictionaryId: string; message: string }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response containing the created dictionary.\n */\nexport const pushDictionaries = async (\n req: Request<any, any, PushDictionariesBody>,\n res: Response<PushDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionaries;\n const dictionariesKeys = dictionaryData.map((dictionary) => dictionary.key);\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARIES_NOT_PROVIDED');\n return;\n } else if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const { existingDictionariesKey, newDictionariesKey } =\n await dictionaryService.getExistingDictionaryKey(\n dictionariesKeys,\n project.id\n );\n\n const existingDictionaries = dictionaryData.filter((dictionary) =>\n existingDictionariesKey.includes(dictionary.key)\n );\n const newDictionaries = dictionaryData.filter((dictionary) =>\n newDictionariesKey.includes(dictionary.key)\n );\n\n const newDictionariesResult: DictionaryAPI[] = [];\n const updatedDictionariesResult: DictionaryAPI[] = [];\n const errorResult: PushDictionariesResultData['error'] = [];\n\n for (const dictionaryDataEl of newDictionaries) {\n const dictionary: DictionaryData = {\n title: dictionaryDataEl.title,\n description: dictionaryDataEl.description,\n projectIds: [String(project.id)],\n creatorId: user.id,\n content: new Map([\n ['v1', { content: dictionaryDataEl.content ?? ({} as ContentNode) }],\n ]),\n filePath: {\n [String(project.id)]: dictionaryDataEl.filePath ?? '',\n },\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push(\n mapDictionaryToAPI(newDictionary, project.id)\n );\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n\n if (existingDictionariesKey.length >= 0) {\n const existingDictionariesDB =\n await dictionaryService.getDictionariesByKeys(\n existingDictionariesKey,\n project.id\n );\n\n for (const dictionaryDataEl of existingDictionaries) {\n const existingDictionaryDB = existingDictionariesDB.find(\n (dictionaryDB) => dictionaryDB.key === dictionaryDataEl.key\n )!;\n\n const versionList = [...(existingDictionaryDB.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (existingDictionaryDB.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent =\n JSON.stringify(lastContent) ===\n JSON.stringify(dictionaryDataEl.content);\n\n let newContent: VersionedContent = existingDictionaryDB.content;\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(existingDictionaryDB);\n\n existingDictionaryDB.content.set(newContentVersion, {\n content: dictionaryDataEl.content ?? ({} as ContentNode),\n });\n\n newContent = existingDictionaryDB.content;\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(existingDictionaryDB),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n filePath: {\n [String(project.id)]: dictionaryDataEl.filePath ?? '',\n },\n key: dictionaryDataEl.key,\n };\n\n try {\n const updatedDictionary =\n await dictionaryService.updateDictionaryByKey(\n dictionaryDataEl.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push(\n mapDictionaryToAPI(updatedDictionary, project.id)\n );\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n updatedDictionaries: updatedDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n req: Request<UpdateDictionaryParam, any, UpdateDictionaryBody>,\n res: Response<UpdateDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { dictionaryId } = req.params;\n const { project, roles } = res.locals;\n const dictionaryData = req.body;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n if (typeof dictionaryId === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n req: Request<DeleteDictionaryParam>,\n res: Response<DeleteDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, roles } = res.locals;\n const { dictionaryId } = req.params as Partial<DeleteDictionaryParam>;\n\n if (!dictionaryId) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:admin')()) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_NOT_FOUND', {\n dictionaryId,\n });\n return;\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/dictionary.controller.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryAPI,\n DictionaryCreationData,\n DictionaryData,\n VersionedContent,\n} from '@/types/dictionary.types';\nimport * as eventListener from '@controllers/eventListener.controller';\nimport type {\n ContentNode,\n Dictionary as LocalDictionary,\n} from '@intlayer/core';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as dictionaryService from '@services/dictionary.service';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DictionaryFiltersParams,\n getDictionaryFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody';\nimport { mapDictionaryToAPI } from '@utils/mapper/dictionary';\nimport { hasPermission } from '@utils/permissions';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\n\nexport type GetDictionariesParams =\n FiltersAndPagination<DictionaryFiltersParams>;\nexport type GetDictionariesResult = PaginatedResponse<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaries = async (\n req: Request<GetDictionariesParams>,\n res: ResponseWithSession<GetDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, roles } = res.locals;\n const { filters, pageSize, skip, page, getNumberOfPages } =\n getDictionaryFiltersAndPagination(req);\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries(\n {\n ...filters,\n projectIds: project.id,\n },\n skip,\n pageSize\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await dictionaryService.countDictionaries(filters);\n\n const dictionariesAPI = dictionaries.map((el) =>\n mapDictionaryToAPI(el, project.id)\n );\n\n const responseData = formatPaginatedResponse<DictionaryAPI>({\n data: dictionariesAPI,\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionariesKeysResult = ResponseData<string[]>;\n\n/**\n * Retrieves a list of dictionaries keys based on filters and pagination.\n */\nexport const getDictionariesKeys = async (\n _req: Request,\n res: ResponseWithSession<GetDictionariesKeysResult>,\n _next: NextFunction\n) => {\n const { project, roles } = res.locals;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionaries = await dictionaryService.findDictionaries({\n projectIds: project.id,\n });\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: dictionaries,\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const dictionariesKeys = dictionaries.map((dictionary) => dictionary.key);\n\n const responseData = formatResponse<string[]>({\n data: dictionariesKeys,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDictionaryParams = { dictionaryKey: string };\nexport type GetDictionaryQuery = { version?: string };\nexport type GetDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const getDictionaryByKey = async (\n req: Request<GetDictionaryParams, any, any, GetDictionaryQuery>,\n res: ResponseWithSession<GetDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const { dictionaryKey } = req.params;\n const version = req.query.version;\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const dictionary = await dictionaryService.getDictionaryByKey(\n dictionaryKey,\n project.id\n );\n\n if (\n !hasPermission(\n roles,\n 'dictionary:read'\n )({\n ...res.locals,\n targetDictionaries: [dictionary],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n if (!dictionary.projectIds.map(String).includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const apiResult = mapDictionaryToAPI(dictionary, project.id, version);\n\n const responseData = formatResponse<DictionaryAPI>({\n data: apiResult,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AddDictionaryBody = { dictionary: DictionaryCreationData };\nexport type AddDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Adds a new dictionary to the database.\n */\nexport const addDictionary = async (\n req: Request<any, any, AddDictionaryBody>,\n res: ResponseWithSession<AddDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionary;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n const dictionary: DictionaryData = {\n key: dictionaryData.key,\n title: dictionaryData.title,\n description: dictionaryData.description,\n content: new Map([\n ['v1', { content: dictionaryData.content ?? ({} as ContentNode) }],\n ]),\n creatorId: user.id,\n filePath: {\n [String(project.id)]: dictionaryData.filePath ?? '',\n },\n projectIds: dictionaryData.projectIds ?? [String(project.id)],\n };\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const newDictionary = await dictionaryService.createDictionary(dictionary);\n\n const apiResult = mapDictionaryToAPI(newDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary created successfully',\n fr: 'Dictionnaire créé avec succès',\n es: 'Diccionario creado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been created successfully',\n fr: 'Votre dictionnaire a été créé avec succès',\n es: 'Su diccionario ha sido creado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: mapDictionaryToAPI(newDictionary, project.id),\n status: 'ADDED',\n },\n ]);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type PushDictionariesBody = {\n dictionaries: LocalDictionary[];\n};\ntype PushDictionariesResultData = {\n newDictionaries: string[];\n updatedDictionaries: string[];\n error: { dictionaryId: string; message: string }[];\n};\nexport type PushDictionariesResult = ResponseData<PushDictionariesResultData>;\n\n/**\n * Check each dictionaries, add the new ones and update the existing ones.\n * @param req - Express request object.\n * @param res - Express response object.\n * @returns Response containing the created dictionary.\n */\nexport const pushDictionaries = async (\n req: Request<any, any, PushDictionariesBody>,\n res: ResponseWithSession<PushDictionariesResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, user, roles } = res.locals;\n const dictionaryData = req.body.dictionaries;\n const dictionariesKeys = dictionaryData.map((dictionary) => dictionary.key);\n\n if (\n typeof dictionaryData === 'object' &&\n Array.isArray(dictionaryData) &&\n dictionaryData.length === 0\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARIES_NOT_PROVIDED');\n return;\n } else if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const { existingDictionariesKey, newDictionariesKey } =\n await dictionaryService.getExistingDictionaryKey(\n dictionariesKeys,\n project.id\n );\n\n const existingDictionaries = dictionaryData.filter((dictionary) =>\n existingDictionariesKey.includes(dictionary.key)\n );\n const newDictionaries = dictionaryData.filter((dictionary) =>\n newDictionariesKey.includes(dictionary.key)\n );\n\n const newDictionariesResult: DictionaryAPI[] = [];\n const updatedDictionariesResult: DictionaryAPI[] = [];\n const errorResult: PushDictionariesResultData['error'] = [];\n\n for (const dictionaryDataEl of newDictionaries) {\n const dictionary: DictionaryData = {\n title: dictionaryDataEl.title,\n description: dictionaryDataEl.description,\n projectIds: [String(project.id)],\n creatorId: user.id,\n content: new Map([\n ['v1', { content: dictionaryDataEl.content ?? ({} as ContentNode) }],\n ]),\n filePath: {\n [String(project.id)]: dictionaryDataEl.filePath ?? '',\n },\n key: dictionaryDataEl.key,\n };\n\n try {\n const newDictionary =\n await dictionaryService.createDictionary(dictionary);\n newDictionariesResult.push(\n mapDictionaryToAPI(newDictionary, project.id)\n );\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n\n if (existingDictionariesKey.length >= 0) {\n const existingDictionariesDB =\n await dictionaryService.getDictionariesByKeys(\n existingDictionariesKey,\n project.id\n );\n\n for (const dictionaryDataEl of existingDictionaries) {\n const existingDictionaryDB = existingDictionariesDB.find(\n (dictionaryDB) => dictionaryDB.key === dictionaryDataEl.key\n )!;\n\n const versionList = [...(existingDictionaryDB.content.keys() ?? [])];\n const lastVersion = versionList[versionList.length - 1];\n\n const lastContent =\n (existingDictionaryDB.content.get(lastVersion)\n ?.content as DictionaryAPI['content']) ?? null;\n\n const isSameContent =\n JSON.stringify(lastContent) ===\n JSON.stringify(dictionaryDataEl.content);\n\n let newContent: VersionedContent = existingDictionaryDB.content;\n\n if (!isSameContent) {\n const newContentVersion =\n dictionaryService.incrementVersion(existingDictionaryDB);\n\n existingDictionaryDB.content.set(newContentVersion, {\n content: dictionaryDataEl.content ?? ({} as ContentNode),\n });\n\n newContent = existingDictionaryDB.content;\n }\n\n const dictionary: DictionaryData = {\n ...ensureMongoDocumentToObject(existingDictionaryDB),\n ...dictionaryDataEl,\n content: newContent,\n projectIds: [String(project.id)],\n creatorId: user.id,\n filePath: {\n [String(project.id)]: dictionaryDataEl.filePath ?? '',\n },\n key: dictionaryDataEl.key,\n };\n\n try {\n const updatedDictionary =\n await dictionaryService.updateDictionaryByKey(\n dictionaryDataEl.key,\n dictionary,\n project.id\n );\n updatedDictionariesResult.push(\n mapDictionaryToAPI(updatedDictionary, project.id)\n );\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n }\n }\n\n const result: PushDictionariesResultData = {\n newDictionaries: newDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n updatedDictionaries: updatedDictionariesResult.map(\n (dictionary) => dictionary.key\n ),\n error: errorResult,\n };\n\n const responseData = formatResponse<PushDictionariesResultData>({\n message: t({\n en: 'Dictionaries updated successfully',\n fr: 'Dictionnaires mis à jour avec succès',\n es: 'Diccionarios actualizados con éxito',\n }),\n description: t({\n en: 'Your dictionaries have been updated successfully',\n fr: 'Vos dictionnaires ont été mis à jour avec succès',\n es: 'Sus diccionarios han sido actualizados con éxito',\n }),\n data: result,\n });\n\n eventListener.sendDictionaryUpdate([\n ...newDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'ADDED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ...updatedDictionariesResult.map(\n (dictionary) =>\n ({\n dictionary,\n status: 'UPDATED',\n }) as eventListener.SendDictionaryUpdateArg\n ),\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type UpdateDictionaryParam = { dictionaryId: string };\nexport type UpdateDictionaryBody = Partial<Dictionary>;\nexport type UpdateDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Updates an existing dictionary in the database.\n */\nexport const updateDictionary = async (\n req: Request<UpdateDictionaryParam, any, UpdateDictionaryBody>,\n res: ResponseWithSession<UpdateDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { dictionaryId } = req.params;\n const { project, roles } = res.locals;\n const dictionaryData = req.body;\n\n if (!dictionaryData) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_DATA_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!dictionaryData.projectIds?.includes(String(project.id))) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_PROJECT_MISMATCH');\n return;\n }\n\n if (typeof dictionaryId === 'undefined') {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:write')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const updatedDictionary = await dictionaryService.updateDictionaryById(\n dictionaryId,\n dictionaryData\n );\n\n const apiResult = mapDictionaryToAPI(updatedDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary updated successfully',\n fr: 'Dictionnaire mis à jour avec succès',\n es: 'Diccionario actualizado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been updated successfully',\n fr: 'Votre dictionnaire a été mis à jour avec succès',\n es: 'Su diccionario ha sido actualizado con éxito',\n }),\n data: apiResult,\n });\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'UPDATED',\n },\n ]);\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type DeleteDictionaryParam = { dictionaryId: string };\nexport type DeleteDictionaryResult = ResponseData<DictionaryAPI>;\n\n/**\n * Deletes a dictionary from the database by its ID.\n */\nexport const deleteDictionary = async (\n req: Request<DeleteDictionaryParam>,\n res: ResponseWithSession<DeleteDictionaryResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project, roles } = res.locals;\n const { dictionaryId } = req.params as Partial<DeleteDictionaryParam>;\n\n if (!dictionaryId) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_ID_NOT_FOUND');\n return;\n }\n\n if (!project) {\n ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED');\n return;\n }\n\n if (!hasPermission(roles, 'dictionary:admin')(res.locals)) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n const dictionaryToDelete =\n await dictionaryService.getDictionaryById(dictionaryId);\n\n if (!dictionaryToDelete.projectIds.includes(project.id)) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'DICTIONARY_PROJECT_MISMATCH'\n );\n return;\n }\n\n const deletedDictionary =\n await dictionaryService.deleteDictionaryById(dictionaryId);\n\n if (!deletedDictionary) {\n ErrorHandler.handleGenericErrorResponse(res, 'DICTIONARY_NOT_FOUND', {\n dictionaryId,\n });\n return;\n }\n\n logger.info(`Dictionary deleted: ${String(deletedDictionary.id)}`);\n\n const apiResult = mapDictionaryToAPI(deletedDictionary, project.id);\n\n const responseData = formatResponse<DictionaryAPI>({\n message: t({\n en: 'Dictionary deleted successfully',\n fr: 'Dictionnaire supprimé avec succès',\n es: 'Diccionario eliminado con éxito',\n }),\n description: t({\n en: 'Your dictionary has been deleted successfully',\n fr: 'Votre dictionnaire a été supprimé avec succès',\n es: 'Su diccionario ha sido eliminado con éxito',\n }),\n data: apiResult,\n });\n\n res.json(responseData);\n\n eventListener.sendDictionaryUpdate([\n {\n dictionary: apiResult,\n status: 'DELETED',\n },\n ]);\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}
@@ -5,7 +5,6 @@ import { mapUserToAPI } from "./../utils/mapper/user.mjs";
5
5
  import { hasPermission } from "./../utils/permissions.mjs";
6
6
  import { formatResponse } from "./../utils/responseData.mjs";
7
7
  import { t } from "express-intlayer";
8
- import { ObjectId } from "mongodb";
9
8
  const subscribeToNewsletter = async (req, res, _next) => {
10
9
  const { roles } = res.locals;
11
10
  const { email, emailList } = req.body;
@@ -31,7 +30,7 @@ const subscribeToNewsletter = async (req, res, _next) => {
31
30
  "user:write"
32
31
  )({
33
32
  ...res.locals,
34
- targetUserIds: [user.id]
33
+ targetUsers: [user]
35
34
  })) {
36
35
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
37
36
  return;
@@ -69,22 +68,22 @@ const unsubscribeFromNewsletter = async (req, res, _next) => {
69
68
  ErrorHandler.handleGenericErrorResponse(res, "USER_DATA_NOT_FOUND");
70
69
  return;
71
70
  }
72
- if (!hasPermission(
73
- roles,
74
- "user:write"
75
- )({
76
- ...res.locals,
77
- targetUserIds: [new ObjectId(userId)]
78
- })) {
79
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
80
- return;
81
- }
82
71
  try {
83
72
  const user = await userService.getUserById(userId);
84
73
  if (!user) {
85
74
  ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
86
75
  return;
87
76
  }
77
+ if (!hasPermission(
78
+ roles,
79
+ "user:write"
80
+ )({
81
+ ...res.locals,
82
+ targetUsers: [user]
83
+ })) {
84
+ ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
85
+ return;
86
+ }
88
87
  const emailLists = Array.isArray(emailList) ? emailList : [emailList];
89
88
  const emailsListObject = Object.fromEntries(
90
89
  emailLists.map((list) => [list, false])
@@ -132,7 +131,7 @@ const getNewsletterStatus = async (_req, res, _next) => {
132
131
  "user:read"
133
132
  )({
134
133
  ...res.locals,
135
- targetUserIds: [user.id]
134
+ targetUsers: [user]
136
135
  })) {
137
136
  ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
138
137
  return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/newsletter.controller.ts"],"sourcesContent":["import type { EmailsList, UserAPI } from '@/types/user.types';\nimport { logger } from '@logger';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { hasPermission } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { NextFunction, Request, Response } from 'express';\nimport { t } from 'express-intlayer';\nimport { ObjectId } from 'mongodb';\n\nexport type NewsletterSubscriptionBody = {\n email: string;\n emailList: EmailsList | EmailsList[];\n};\nexport type NewsletterSubscriptionResult = ResponseData<UserAPI>;\n\n/**\n * Subscribes a user to the newsletter.\n * If the user doesn't exist, creates a new user.\n * If the user exists, updates their newsletter subscription to true.\n */\nexport const subscribeToNewsletter = async (\n req: Request<any, any, NewsletterSubscriptionBody>,\n res: Response<NewsletterSubscriptionResult>,\n _next: NextFunction\n): Promise<void> => {\n const { roles } = res.locals;\n const { email, emailList } = req.body;\n\n if (!email) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_DATA_NOT_FOUND');\n return;\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, true])\n ) as Record<EmailsList, boolean>;\n\n try {\n // Check if user exists\n let user = await userService.getUserByEmail(email);\n\n if (!user) {\n user = await userService.createUser({\n email,\n emailsList: emailsListObject,\n });\n\n logger.info(`New user created and subscribed to newsletter: ${email}`);\n } else {\n if (\n !hasPermission(\n roles,\n 'user:write'\n )({\n ...res.locals,\n targetUserIds: [user.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n // Update existing user's newsletter subscription\n user = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User subscribed to newsletter: ${email}`);\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully subscribed to newsletter',\n fr: 'Abonnement à la newsletter réussi',\n es: 'Suscripción al boletín exitosa',\n }),\n description: t({\n en: 'You have been successfully subscribed to our newsletter',\n fr: 'Vous avez été abonné avec succès à notre newsletter',\n es: 'Te has suscrito exitosamente a nuestro boletín',\n }),\n data: formattedUser,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type NewsletterUnsubscriptionBody = {\n userId: string;\n emailList: EmailsList | EmailsList[];\n};\n\n/**\n * Unsubscribes a user from the newsletter.\n * Only works if the user exists.\n */\nexport const unsubscribeFromNewsletter = async (\n req: Request<any, any, NewsletterUnsubscriptionBody>,\n res: Response<NewsletterSubscriptionResult>,\n _next: NextFunction\n): Promise<void> => {\n const { userId, emailList } = req.body;\n const { roles } = res.locals;\n\n if (!userId) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_DATA_NOT_FOUND');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'user:write'\n )({\n ...res.locals,\n targetUserIds: [new ObjectId(userId)],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n try {\n // Check if user exists\n const user = await userService.getUserById(userId);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, false])\n ) as Record<EmailsList, boolean>;\n\n // Update user's newsletter subscription to false\n const updatedUser = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User unsubscribed from newsletter: ${updatedUser.email}`);\n\n const formattedUser = mapUserToAPI(updatedUser);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully unsubscribed from newsletter',\n fr: 'Désabonnement de la newsletter réussi',\n es: 'Cancelación de suscripción al boletín exitosa',\n }),\n description: t({\n en: 'You have been successfully unsubscribed from our newsletter',\n fr: 'Vous avez été désabonné avec succès de notre newsletter',\n es: 'Te has desuscrito exitosamente de nuestro boletín',\n }),\n data: formattedUser,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\n/**\n * Gets the newsletter subscription status for a user.\n */\nexport const getNewsletterStatus = async (\n _req: Request<{ email: string }>,\n res: Response<NewsletterSubscriptionResult>,\n _next: NextFunction\n): Promise<void> => {\n const email = res.locals.user?.email;\n const { roles } = res.locals;\n\n if (!email) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_DATA_NOT_FOUND');\n return;\n }\n\n try {\n const user = await userService.getUserByEmail(email);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'user:read'\n )({\n ...res.locals,\n targetUserIds: [user.id],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Newsletter subscription status retrieved',\n fr: \"Statut d'abonnement à la newsletter récupéré\",\n es: 'Estado de suscripción al boletín obtenido',\n }),\n data: formattedUser,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"AACA,SAAS,cAAc;AACvB,YAAY,iBAAiB;AAC7B,SAAwB,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,sBAAyC;AAElD,SAAS,SAAS;AAClB,SAAS,gBAAgB;AAalB,MAAM,wBAAwB,OACnC,KACA,KACA,UACkB;AAClB,QAAM,EAAE,MAAM,IAAI,IAAI;AACtB,QAAM,EAAE,OAAO,UAAU,IAAI,IAAI;AAEjC,MAAI,CAAC,OAAO;AACV,iBAAa,2BAA2B,KAAK,qBAAqB;AAClE;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS;AAGpE,QAAM,mBAAmB,OAAO;AAAA,IAC9B,WAAW,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC;AAAA,EACvC;AAEA,MAAI;AAEF,QAAI,OAAO,MAAM,YAAY,eAAe,KAAK;AAEjD,QAAI,CAAC,MAAM;AACT,aAAO,MAAM,YAAY,WAAW;AAAA,QAClC;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAED,aAAO,KAAK,kDAAkD,KAAK,EAAE;AAAA,IACvE,OAAO;AACL,UACE,CAAC;AAAA,QACC;AAAA,QACA;AAAA,MACF,EAAE;AAAA,QACA,GAAG,IAAI;AAAA,QACP,eAAe,CAAC,KAAK,EAAE;AAAA,MACzB,CAAC,GACD;AACA,qBAAa,2BAA2B,KAAK,mBAAmB;AAChE;AAAA,MACF;AAGA,aAAO,MAAM,YAAY,eAAe,KAAK,IAAI;AAAA,QAC/C,YAAY,EAAE,GAAG,KAAK,YAAY,GAAG,iBAAiB;AAAA,MACxD,CAAC;AAED,aAAO,KAAK,kCAAkC,KAAK,EAAE;AAAA,IACvD;AAEA,UAAM,gBAAgB,aAAa,IAAI;AAEvC,UAAM,eAAe,eAAwB;AAAA,MAC3C,SAAS,EAAE;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AAAA,MACD,aAAa,EAAE;AAAA,QACb,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AAAA,MACD,MAAM;AAAA,IACR,CAAC;AAED,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AAWO,MAAM,4BAA4B,OACvC,KACA,KACA,UACkB;AAClB,QAAM,EAAE,QAAQ,UAAU,IAAI,IAAI;AAClC,QAAM,EAAE,MAAM,IAAI,IAAI;AAEtB,MAAI,CAAC,QAAQ;AACX,iBAAa,2BAA2B,KAAK,qBAAqB;AAClE;AAAA,EACF;AAEA,MACE,CAAC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EAAE;AAAA,IACA,GAAG,IAAI;AAAA,IACP,eAAe,CAAC,IAAI,SAAS,MAAM,CAAC;AAAA,EACtC,CAAC,GACD;AACA,iBAAa,2BAA2B,KAAK,mBAAmB;AAChE;AAAA,EACF;AAEA,MAAI;AAEF,UAAM,OAAO,MAAM,YAAY,YAAY,MAAM;AAEjD,QAAI,CAAC,MAAM;AACT,mBAAa,2BAA2B,KAAK,gBAAgB;AAC7D;AAAA,IACF;AAEA,UAAM,aAAa,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS;AAGpE,UAAM,mBAAmB,OAAO;AAAA,MAC9B,WAAW,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;AAAA,IACxC;AAGA,UAAM,cAAc,MAAM,YAAY,eAAe,KAAK,IAAI;AAAA,MAC5D,YAAY,EAAE,GAAG,KAAK,YAAY,GAAG,iBAAiB;AAAA,IACxD,CAAC;AAED,WAAO,KAAK,sCAAsC,YAAY,KAAK,EAAE;AAErE,UAAM,gBAAgB,aAAa,WAAW;AAE9C,UAAM,eAAe,eAAwB;AAAA,MAC3C,SAAS,EAAE;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AAAA,MACD,aAAa,EAAE;AAAA,QACb,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AAAA,MACD,MAAM;AAAA,IACR,CAAC;AAED,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AAKO,MAAM,sBAAsB,OACjC,MACA,KACA,UACkB;AAClB,QAAM,QAAQ,IAAI,OAAO,MAAM;AAC/B,QAAM,EAAE,MAAM,IAAI,IAAI;AAEtB,MAAI,CAAC,OAAO;AACV,iBAAa,2BAA2B,KAAK,qBAAqB;AAClE;AAAA,EACF;AAEA,MAAI;AACF,UAAM,OAAO,MAAM,YAAY,eAAe,KAAK;AAEnD,QAAI,CAAC,MAAM;AACT,mBAAa,2BAA2B,KAAK,gBAAgB;AAC7D;AAAA,IACF;AAEA,QACE,CAAC;AAAA,MACC;AAAA,MACA;AAAA,IACF,EAAE;AAAA,MACA,GAAG,IAAI;AAAA,MACP,eAAe,CAAC,KAAK,EAAE;AAAA,IACzB,CAAC,GACD;AACA,mBAAa,2BAA2B,KAAK,mBAAmB;AAChE;AAAA,IACF;AAEA,UAAM,gBAAgB,aAAa,IAAI;AAEvC,UAAM,eAAe,eAAwB;AAAA,MAC3C,SAAS,EAAE;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AAAA,MACD,MAAM;AAAA,IACR,CAAC;AAED,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/newsletter.controller.ts"],"sourcesContent":["import type { EmailsList, UserAPI } from '@/types/user.types';\nimport { logger } from '@logger';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport * as userService from '@services/user.service';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { hasPermission } from '@utils/permissions';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { t } from 'express-intlayer';\n\nexport type NewsletterSubscriptionBody = {\n email: string;\n emailList: EmailsList | EmailsList[];\n};\nexport type NewsletterSubscriptionResult = ResponseData<UserAPI>;\n\n/**\n * Subscribes a user to the newsletter.\n * If the user doesn't exist, creates a new user.\n * If the user exists, updates their newsletter subscription to true.\n */\nexport const subscribeToNewsletter = async (\n req: Request<any, any, NewsletterSubscriptionBody>,\n res: ResponseWithSession<NewsletterSubscriptionResult>,\n _next: NextFunction\n): Promise<void> => {\n const { roles } = res.locals;\n const { email, emailList } = req.body;\n\n if (!email) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_DATA_NOT_FOUND');\n return;\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, true])\n ) as Record<EmailsList, boolean>;\n\n try {\n // Check if user exists\n let user = await userService.getUserByEmail(email);\n\n if (!user) {\n user = await userService.createUser({\n email,\n emailsList: emailsListObject,\n });\n\n logger.info(`New user created and subscribed to newsletter: ${email}`);\n } else {\n if (\n !hasPermission(\n roles,\n 'user:write'\n )({\n ...res.locals,\n targetUsers: [user],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n // Update existing user's newsletter subscription\n user = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User subscribed to newsletter: ${email}`);\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully subscribed to newsletter',\n fr: 'Abonnement à la newsletter réussi',\n es: 'Suscripción al boletín exitosa',\n }),\n description: t({\n en: 'You have been successfully subscribed to our newsletter',\n fr: 'Vous avez été abonné avec succès à notre newsletter',\n es: 'Te has suscrito exitosamente a nuestro boletín',\n }),\n data: formattedUser,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type NewsletterUnsubscriptionBody = {\n userId: string;\n emailList: EmailsList | EmailsList[];\n};\n\n/**\n * Unsubscribes a user from the newsletter.\n * Only works if the user exists.\n */\nexport const unsubscribeFromNewsletter = async (\n req: Request<any, any, NewsletterUnsubscriptionBody>,\n res: ResponseWithSession<NewsletterSubscriptionResult>,\n _next: NextFunction\n): Promise<void> => {\n const { userId, emailList } = req.body;\n const { roles } = res.locals;\n\n if (!userId) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_DATA_NOT_FOUND');\n return;\n }\n\n try {\n // Check if user exists\n const user = await userService.getUserById(userId);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'user:write'\n )({\n ...res.locals,\n targetUsers: [user],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const emailLists = Array.isArray(emailList) ? emailList : [emailList];\n\n // Create new user with newsletter subscription enabled\n const emailsListObject = Object.fromEntries(\n emailLists.map((list) => [list, false])\n ) as Record<EmailsList, boolean>;\n\n // Update user's newsletter subscription to false\n const updatedUser = await userService.updateUserById(user.id, {\n emailsList: { ...user.emailsList, ...emailsListObject },\n });\n\n logger.info(`User unsubscribed from newsletter: ${updatedUser.email}`);\n\n const formattedUser = mapUserToAPI(updatedUser);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Successfully unsubscribed from newsletter',\n fr: 'Désabonnement de la newsletter réussi',\n es: 'Cancelación de suscripción al boletín exitosa',\n }),\n description: t({\n en: 'You have been successfully unsubscribed from our newsletter',\n fr: 'Vous avez été désabonné avec succès de notre newsletter',\n es: 'Te has desuscrito exitosamente de nuestro boletín',\n }),\n data: formattedUser,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\n/**\n * Gets the newsletter subscription status for a user.\n */\nexport const getNewsletterStatus = async (\n _req: Request<{ email: string }>,\n res: ResponseWithSession<NewsletterSubscriptionResult>,\n _next: NextFunction\n): Promise<void> => {\n const email = res.locals.user?.email;\n const { roles } = res.locals;\n\n if (!email) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_DATA_NOT_FOUND');\n return;\n }\n\n try {\n const user = await userService.getUserByEmail(email);\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n if (\n !hasPermission(\n roles,\n 'user:read'\n )({\n ...res.locals,\n targetUsers: [user],\n })\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const formattedUser = mapUserToAPI(user);\n\n const responseData = formatResponse<UserAPI>({\n message: t({\n en: 'Newsletter subscription status retrieved',\n fr: \"Statut d'abonnement à la newsletter récupéré\",\n es: 'Estado de suscripción al boletín obtenido',\n }),\n data: formattedUser,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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","names":[]}