@intlayer/backend 8.10.0 → 8.10.1

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 (179) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10948 -8936
  2. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/interest_of_intlayer.json +1 -14957
  3. package/dist/esm/controllers/ai.controller.mjs +10 -9
  4. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  5. package/dist/esm/controllers/audit.controller.mjs +1 -1
  6. package/dist/esm/controllers/audit.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/organization.controller.mjs +1 -1
  8. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/project.controller.mjs +2 -2
  10. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/showcaseProject.controller.mjs +0 -2
  12. package/dist/esm/controllers/showcaseProject.controller.mjs.map +1 -1
  13. package/dist/esm/controllers/translation.controller.mjs +0 -1
  14. package/dist/esm/controllers/translation.controller.mjs.map +1 -1
  15. package/dist/esm/controllers/user.controller.mjs +0 -6
  16. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  17. package/dist/esm/schemas/account.schema.mjs +3 -2
  18. package/dist/esm/schemas/account.schema.mjs.map +1 -1
  19. package/dist/esm/{models/audit.model.mjs → schemas/audit.schema.mjs} +3 -3
  20. package/dist/esm/schemas/audit.schema.mjs.map +1 -0
  21. package/dist/esm/{models/auditJob.model.mjs → schemas/auditJob.schema.mjs} +3 -3
  22. package/dist/esm/schemas/auditJob.schema.mjs.map +1 -0
  23. package/dist/esm/{models/auditPage.model.mjs → schemas/auditPage.schema.mjs} +3 -3
  24. package/dist/esm/schemas/auditPage.schema.mjs.map +1 -0
  25. package/dist/esm/schemas/cliSessionToken.schema.mjs +3 -2
  26. package/dist/esm/schemas/cliSessionToken.schema.mjs.map +1 -1
  27. package/dist/esm/schemas/dictionary.schema.mjs +3 -2
  28. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  29. package/dist/esm/schemas/discussion.schema.mjs +3 -2
  30. package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
  31. package/dist/esm/schemas/oAuth2.schema.mjs +3 -2
  32. package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
  33. package/dist/esm/schemas/organization.schema.mjs +3 -2
  34. package/dist/esm/schemas/organization.schema.mjs.map +1 -1
  35. package/dist/esm/schemas/project.schema.mjs +3 -2
  36. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  37. package/dist/esm/schemas/session.schema.mjs +3 -2
  38. package/dist/esm/schemas/session.schema.mjs.map +1 -1
  39. package/dist/esm/schemas/showcaseProject.schema.mjs +3 -2
  40. package/dist/esm/schemas/showcaseProject.schema.mjs.map +1 -1
  41. package/dist/esm/schemas/tag.schema.mjs +3 -2
  42. package/dist/esm/schemas/tag.schema.mjs.map +1 -1
  43. package/dist/esm/schemas/user.schema.mjs +3 -2
  44. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  45. package/dist/esm/services/audit/recursiveAudit.service.mjs +2 -2
  46. package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
  47. package/dist/esm/services/bitbucket.service.mjs +1 -1
  48. package/dist/esm/services/bitbucket.service.mjs.map +1 -1
  49. package/dist/esm/services/cliSessionToken.service.mjs +1 -1
  50. package/dist/esm/services/cliSessionToken.service.mjs.map +1 -1
  51. package/dist/esm/services/dictionary.service.mjs +1 -1
  52. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  53. package/dist/esm/services/github.service.mjs +1 -1
  54. package/dist/esm/services/github.service.mjs.map +1 -1
  55. package/dist/esm/services/gitlab.service.mjs +1 -1
  56. package/dist/esm/services/gitlab.service.mjs.map +1 -1
  57. package/dist/esm/services/oAuth2.service.mjs +2 -2
  58. package/dist/esm/services/oAuth2.service.mjs.map +1 -1
  59. package/dist/esm/services/organization.service.mjs +1 -1
  60. package/dist/esm/services/organization.service.mjs.map +1 -1
  61. package/dist/esm/services/project.service.mjs +1 -1
  62. package/dist/esm/services/project.service.mjs.map +1 -1
  63. package/dist/esm/services/projectAccessKey.service.mjs +1 -1
  64. package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
  65. package/dist/esm/services/showcase/showcaseProject.service.mjs +1 -1
  66. package/dist/esm/services/showcase/showcaseProject.service.mjs.map +1 -1
  67. package/dist/esm/services/tag.service.mjs +1 -1
  68. package/dist/esm/services/tag.service.mjs.map +1 -1
  69. package/dist/esm/services/user.service.mjs +1 -1
  70. package/dist/esm/services/user.service.mjs.map +1 -1
  71. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/dictionary/markdown.json +10948 -8936
  72. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/interest_of_intlayer.json +1 -14957
  73. package/dist/esm/utils/AI/auditDictionaryField/index.mjs +9 -0
  74. package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
  75. package/dist/esm/utils/AI/getProjectAIOptions.mjs +20 -0
  76. package/dist/esm/utils/AI/getProjectAIOptions.mjs.map +1 -0
  77. package/dist/esm/utils/errors/ErrorHandler.mjs +40 -8
  78. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  79. package/dist/esm/utils/mapper/project.mjs +7 -1
  80. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  81. package/dist/esm/utils/mongoDB/connectDB.mjs +12 -12
  82. package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
  83. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  84. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  85. package/dist/types/controllers/showcaseProject.controller.d.ts.map +1 -1
  86. package/dist/types/controllers/translation.controller.d.ts.map +1 -1
  87. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  88. package/dist/types/schemas/account.schema.d.ts +3 -2
  89. package/dist/types/schemas/account.schema.d.ts.map +1 -1
  90. package/dist/types/schemas/audit.schema.d.ts +64 -0
  91. package/dist/types/schemas/audit.schema.d.ts.map +1 -0
  92. package/dist/types/schemas/auditJob.schema.d.ts +122 -0
  93. package/dist/types/schemas/auditJob.schema.d.ts.map +1 -0
  94. package/dist/types/schemas/auditPage.schema.d.ts +120 -0
  95. package/dist/types/schemas/auditPage.schema.d.ts.map +1 -0
  96. package/dist/types/schemas/cliSessionToken.schema.d.ts +10 -3
  97. package/dist/types/schemas/cliSessionToken.schema.d.ts.map +1 -1
  98. package/dist/types/schemas/dictionary.schema.d.ts +22 -13
  99. package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
  100. package/dist/types/schemas/discussion.schema.d.ts +19 -14
  101. package/dist/types/schemas/discussion.schema.d.ts.map +1 -1
  102. package/dist/types/schemas/oAuth2.schema.d.ts +13 -3
  103. package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
  104. package/dist/types/schemas/organization.schema.d.ts +7 -6
  105. package/dist/types/schemas/organization.schema.d.ts.map +1 -1
  106. package/dist/types/schemas/plans.schema.d.ts +7 -7
  107. package/dist/types/schemas/project.schema.d.ts +7 -6
  108. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  109. package/dist/types/schemas/session.schema.d.ts +11 -10
  110. package/dist/types/schemas/session.schema.d.ts.map +1 -1
  111. package/dist/types/schemas/showcaseProject.schema.d.ts +16 -15
  112. package/dist/types/schemas/showcaseProject.schema.d.ts.map +1 -1
  113. package/dist/types/schemas/tag.schema.d.ts +9 -8
  114. package/dist/types/schemas/tag.schema.d.ts.map +1 -1
  115. package/dist/types/schemas/user.schema.d.ts +8 -7
  116. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  117. package/dist/types/services/audit/recursiveAudit.service.d.ts +2 -2
  118. package/dist/types/types/project.types.d.ts +2 -1
  119. package/dist/types/types/project.types.d.ts.map +1 -1
  120. package/dist/types/utils/AI/getProjectAIOptions.d.ts +15 -0
  121. package/dist/types/utils/AI/getProjectAIOptions.d.ts.map +1 -0
  122. package/dist/types/utils/errors/ErrorHandler.d.ts +4 -4
  123. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  124. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +3 -3
  125. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  126. package/package.json +5 -5
  127. package/dist/esm/models/account.model.mjs +0 -9
  128. package/dist/esm/models/account.model.mjs.map +0 -1
  129. package/dist/esm/models/audit.model.mjs.map +0 -1
  130. package/dist/esm/models/auditJob.model.mjs.map +0 -1
  131. package/dist/esm/models/auditPage.model.mjs.map +0 -1
  132. package/dist/esm/models/cliSessionToken.model.mjs +0 -9
  133. package/dist/esm/models/cliSessionToken.model.mjs.map +0 -1
  134. package/dist/esm/models/dictionary.model.mjs +0 -9
  135. package/dist/esm/models/dictionary.model.mjs.map +0 -1
  136. package/dist/esm/models/discussion.model.mjs +0 -9
  137. package/dist/esm/models/discussion.model.mjs.map +0 -1
  138. package/dist/esm/models/oAuth2.model.mjs +0 -9
  139. package/dist/esm/models/oAuth2.model.mjs.map +0 -1
  140. package/dist/esm/models/organization.model.mjs +0 -9
  141. package/dist/esm/models/organization.model.mjs.map +0 -1
  142. package/dist/esm/models/project.model.mjs +0 -9
  143. package/dist/esm/models/project.model.mjs.map +0 -1
  144. package/dist/esm/models/session.model.mjs +0 -9
  145. package/dist/esm/models/session.model.mjs.map +0 -1
  146. package/dist/esm/models/showcaseProject.model.mjs +0 -9
  147. package/dist/esm/models/showcaseProject.model.mjs.map +0 -1
  148. package/dist/esm/models/tag.model.mjs +0 -9
  149. package/dist/esm/models/tag.model.mjs.map +0 -1
  150. package/dist/esm/models/user.model.mjs +0 -9
  151. package/dist/esm/models/user.model.mjs.map +0 -1
  152. package/dist/types/models/account.model.d.ts +0 -7
  153. package/dist/types/models/account.model.d.ts.map +0 -1
  154. package/dist/types/models/audit.model.d.ts +0 -18
  155. package/dist/types/models/audit.model.d.ts.map +0 -1
  156. package/dist/types/models/auditJob.model.d.ts +0 -31
  157. package/dist/types/models/auditJob.model.d.ts.map +0 -1
  158. package/dist/types/models/auditPage.model.d.ts +0 -29
  159. package/dist/types/models/auditPage.model.d.ts.map +0 -1
  160. package/dist/types/models/cliSessionToken.model.d.ts +0 -14
  161. package/dist/types/models/cliSessionToken.model.d.ts.map +0 -1
  162. package/dist/types/models/dictionary.model.d.ts +0 -16
  163. package/dist/types/models/dictionary.model.d.ts.map +0 -1
  164. package/dist/types/models/discussion.model.d.ts +0 -12
  165. package/dist/types/models/discussion.model.d.ts.map +0 -1
  166. package/dist/types/models/oAuth2.model.d.ts +0 -18
  167. package/dist/types/models/oAuth2.model.d.ts.map +0 -1
  168. package/dist/types/models/organization.model.d.ts +0 -7
  169. package/dist/types/models/organization.model.d.ts.map +0 -1
  170. package/dist/types/models/project.model.d.ts +0 -7
  171. package/dist/types/models/project.model.d.ts.map +0 -1
  172. package/dist/types/models/session.model.d.ts +0 -7
  173. package/dist/types/models/session.model.d.ts.map +0 -1
  174. package/dist/types/models/showcaseProject.model.d.ts +0 -7
  175. package/dist/types/models/showcaseProject.model.d.ts.map +0 -1
  176. package/dist/types/models/tag.model.d.ts +0 -7
  177. package/dist/types/models/tag.model.d.ts.map +0 -1
  178. package/dist/types/models/user.model.d.ts +0 -7
  179. package/dist/types/models/user.model.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"oAuth2.service.mjs","names":[],"sources":["../../../src/services/oAuth2.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { OAuth2AccessTokenModel } from '@models/oAuth2.model';\nimport { ProjectModel } from '@models/project.model';\nimport type { Callback, Client } from '@node-oauth/oauth2-server';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { getTokenExpireAt, shouldExtendOAuth2Token } from '@utils/oAuth2';\nimport type { Types } from 'mongoose';\nimport type { OAuth2Token } from '@/types/oAuth2.types';\nimport type { Organization } from '@/types/organization.types';\nimport type {\n OAuth2Access,\n OAuth2AccessContext,\n Project,\n ProjectDocument,\n} from '@/types/project.types';\nimport type { User, UserAPI, UserDocument } from '@/types/user.types';\nimport type { Token } from '../schemas/oAuth2.schema';\nimport { getOrganizationById } from './organization.service';\nimport { getUserById } from './user.service';\n\n/**\n * Function to generate client credentials\n *\n * @returns The client id and client secret\n */\nexport const generateClientCredentials = (): {\n clientId: string;\n clientSecret: string;\n} => {\n const clientId = randomBytes(16).toString('hex'); // Generate a 16 character hexadecimal string\n const clientSecret = randomBytes(32).toString('hex'); // Generate a 32 character hexadecimal string\n\n return { clientId, clientSecret };\n};\n\n/**\n * Method to get the client and the project\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The an object containing the client, the rights and the project or false if not found\n */\nexport const getClientAndProjectByClientId = async (\n clientId: string\n): Promise<\n | {\n client: Client;\n oAuth2Access: OAuth2Access;\n project: ProjectDocument;\n grants: Token['grants'];\n }\n | false\n> => {\n const project = await ProjectModel.findOne({\n 'oAuth2Access.clientId': String(clientId),\n });\n\n if (!project) {\n return false;\n }\n\n const oAuth2Access = project.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!oAuth2Access) {\n return false;\n }\n\n const formattedClient: Client = {\n id: oAuth2Access.clientId,\n clientId,\n clientSecret: oAuth2Access.clientSecret,\n grants: ['client_credentials'],\n };\n\n return {\n client: formattedClient,\n oAuth2Access,\n grants: oAuth2Access.grants,\n project,\n };\n};\n\n/**\n * Get the client and verify that the client secret is correct\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The client or false if not found\n */\nexport const getClient = async (\n clientId: string,\n clientSecret: string\n): Promise<Client | false> => {\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client } = result;\n\n if (!client || client.clientSecret !== clientSecret) {\n return false;\n }\n\n return client;\n};\n\n/**\n * Format an OAuth2Token\n *\n * @param token - The token to format\n * @param client - The client\n * @param user - The user\n * @param project - The project\n * @param organization - The organization\n * @param grants - The grants\n * @returns The formatted token\n */\nexport const formatOAuth2Token = (\n token: Token,\n client: Client,\n user: UserAPI,\n project: Project,\n organization: Organization,\n grants: Token['grants']\n): OAuth2Token => {\n const { clientId, userId, ...restToken } = token;\n\n if (String(userId) !== String(user.id)) {\n throw new GenericError('USER_ID_MISMATCH');\n }\n\n const formattedToken: OAuth2Token = {\n ...restToken,\n client,\n user: mapUserToAPI(user),\n organization: mapOrganizationToAPI(organization),\n project: mapProjectToAPI(project),\n accessToken: token.accessToken,\n accessTokenExpiresAt: token.accessTokenExpiresAt ?? new Date('999-99-99'),\n grants,\n };\n\n return formattedToken;\n};\n\n/**\n * Format a auth token for the database\n *\n * @param token - The oAuth2 token to format\n * @param clientId - The client ID\n * @param userId - The user ID\n * @returns\n */\nexport const formatDBToken = (\n token: OAuth2Token,\n clientId: Client['id'],\n userId: User['id'] | string\n): Token => {\n const formattedToken: Token = {\n id: token.id,\n clientId: clientId,\n userId: userId as Types.ObjectId,\n accessToken: token.accessToken,\n expiresIn: token.accessTokenExpiresAt ?? getTokenExpireAt(),\n };\n\n return formattedToken;\n};\n\n/**\n * Method to save the token\n *\n * @param token - The token\n * @param client - The client\n * @param user - The user\n * @returns The saved token or false if not saved\n */\nexport const saveToken = async (\n token: OAuth2Token,\n client: Client,\n user: UserAPI\n): Promise<OAuth2Token | false> => {\n const formattedAccessToken: Token = formatDBToken(token, client.id, user.id);\n\n const result = await OAuth2AccessTokenModel.create(formattedAccessToken);\n\n if (!result) {\n return false;\n }\n\n const result2 = await getClientAndProjectByClientId(result.clientId);\n\n if (!result2) {\n return false;\n }\n\n const { project } = result2;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedResult = formatOAuth2Token(\n formattedAccessToken,\n client,\n user,\n project,\n organization,\n token.rights\n );\n return formattedResult;\n};\n\n/**\n * Sliding-refresh: push the token's expiry forward when it has been used\n * within the refresh threshold. Idempotent and cheap when no extension is due.\n */\nexport const extendOAuth2AccessToken = async (\n accessToken: string\n): Promise<Date> => {\n const nextExpiresAt = getTokenExpireAt();\n await OAuth2AccessTokenModel.updateOne(\n { accessToken: String(accessToken) },\n { $set: { accessTokenExpiresAt: nextExpiresAt, expiresIn: nextExpiresAt } }\n );\n return nextExpiresAt;\n};\n\n/**\n * Method to get the access token\n *\n * @param accessToken - The access token\n * @returns The access token or false if not found\n */\nexport const getAccessToken = async (\n accessToken: string\n): Promise<OAuth2Token | false> => {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken: String(accessToken),\n });\n\n if (!token) {\n return false;\n }\n\n // Slide the expiry forward when this active token is approaching its\n // deadline so a long-lived integration doesn't have to re-authenticate.\n const currentExpiresAt = token.accessTokenExpiresAt ?? token.expiresIn;\n if (currentExpiresAt && shouldExtendOAuth2Token(currentExpiresAt)) {\n const nextExpiresAt = await extendOAuth2AccessToken(accessToken);\n token.accessTokenExpiresAt = nextExpiresAt;\n token.expiresIn = nextExpiresAt;\n }\n\n const { userId, clientId } = token;\n\n const user = await getUserById(userId);\n\n if (!user) {\n return false;\n }\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client, project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedAccessToken = formatOAuth2Token(\n token,\n client,\n user,\n project,\n organization,\n grants\n );\n\n return formattedAccessToken;\n};\n\n/**\n * Method to get the user from the client\n *\n * @param client - The client\n * @returns The user or false if not found\n */\nexport const getUserFromClient = async (\n client: Client\n): Promise<UserDocument | false> => {\n const response = await getClientAndProjectByClientId(client.id);\n\n if (!response) {\n return false;\n }\n\n const { userId } = response.oAuth2Access;\n\n if (!userId) {\n return false;\n }\n\n const user = await getUserById(userId);\n\n return user ?? false;\n};\n\n/**\n * Method to verify the permissions (grants)\n *\n * @param token - The token\n * @param scope - The scope\n * @returns True if the token has the required scope, false otherwise\n */\nexport const verifyScope = async (\n _token: OAuth2Token,\n _scope: string,\n _callback?: Callback<boolean> | undefined\n): Promise<boolean> => {\n // Implement the verification of scopes if necessary\n return true;\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const validateOAuth2AccessToken = async (\n accessToken: string\n): Promise<Token> => {\n try {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken,\n });\n\n if (!token) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n // Check if token is expired\n if (new Date() > new Date(token.expiresIn)) {\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n return ensureMongoDocumentToObject(token);\n } catch (_error) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const getOAuth2AccessTokenContext = async (\n token: Token\n): Promise<OAuth2AccessContext> => {\n const { userId, clientId } = token;\n\n const user = await getUserById(String(userId));\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const { project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n return {\n accessToken: token.accessToken,\n user: user ? mapUserToAPI(user) : undefined,\n project: project ? mapProjectToAPI(project) : undefined,\n organization: organization ? mapOrganizationToAPI(organization) : undefined,\n grants,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"oAuth2.service.mjs","names":[],"sources":["../../../src/services/oAuth2.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { Callback, Client } from '@node-oauth/oauth2-server';\nimport { OAuth2AccessTokenModel } from '@schemas/oAuth2.schema';\nimport { ProjectModel } from '@schemas/project.schema';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport { getTokenExpireAt, shouldExtendOAuth2Token } from '@utils/oAuth2';\nimport type { Types } from 'mongoose';\nimport type { OAuth2Token } from '@/types/oAuth2.types';\nimport type { Organization } from '@/types/organization.types';\nimport type {\n OAuth2Access,\n OAuth2AccessContext,\n Project,\n ProjectDocument,\n} from '@/types/project.types';\nimport type { User, UserAPI, UserDocument } from '@/types/user.types';\nimport type { Token } from '../schemas/oAuth2.schema';\nimport { getOrganizationById } from './organization.service';\nimport { getUserById } from './user.service';\n\n/**\n * Function to generate client credentials\n *\n * @returns The client id and client secret\n */\nexport const generateClientCredentials = (): {\n clientId: string;\n clientSecret: string;\n} => {\n const clientId = randomBytes(16).toString('hex'); // Generate a 16 character hexadecimal string\n const clientSecret = randomBytes(32).toString('hex'); // Generate a 32 character hexadecimal string\n\n return { clientId, clientSecret };\n};\n\n/**\n * Method to get the client and the project\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The an object containing the client, the rights and the project or false if not found\n */\nexport const getClientAndProjectByClientId = async (\n clientId: string\n): Promise<\n | {\n client: Client;\n oAuth2Access: OAuth2Access;\n project: ProjectDocument;\n grants: Token['grants'];\n }\n | false\n> => {\n const project = await ProjectModel.findOne({\n 'oAuth2Access.clientId': String(clientId),\n });\n\n if (!project) {\n return false;\n }\n\n const oAuth2Access = project.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!oAuth2Access) {\n return false;\n }\n\n const formattedClient: Client = {\n id: oAuth2Access.clientId,\n clientId,\n clientSecret: oAuth2Access.clientSecret,\n grants: ['client_credentials'],\n };\n\n return {\n client: formattedClient,\n oAuth2Access,\n grants: oAuth2Access.grants,\n project,\n };\n};\n\n/**\n * Get the client and verify that the client secret is correct\n *\n * @param clientId - The client id\n * @param clientSecret - The client secret\n * @returns The client or false if not found\n */\nexport const getClient = async (\n clientId: string,\n clientSecret: string\n): Promise<Client | false> => {\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client } = result;\n\n if (!client || client.clientSecret !== clientSecret) {\n return false;\n }\n\n return client;\n};\n\n/**\n * Format an OAuth2Token\n *\n * @param token - The token to format\n * @param client - The client\n * @param user - The user\n * @param project - The project\n * @param organization - The organization\n * @param grants - The grants\n * @returns The formatted token\n */\nexport const formatOAuth2Token = (\n token: Token,\n client: Client,\n user: UserAPI,\n project: Project,\n organization: Organization,\n grants: Token['grants']\n): OAuth2Token => {\n const { clientId, userId, ...restToken } = token;\n\n if (String(userId) !== String(user.id)) {\n throw new GenericError('USER_ID_MISMATCH');\n }\n\n const formattedToken: OAuth2Token = {\n ...restToken,\n client,\n user: mapUserToAPI(user),\n organization: mapOrganizationToAPI(organization),\n project: mapProjectToAPI(project),\n accessToken: token.accessToken,\n accessTokenExpiresAt: token.accessTokenExpiresAt ?? new Date('999-99-99'),\n grants,\n };\n\n return formattedToken;\n};\n\n/**\n * Format a auth token for the database\n *\n * @param token - The oAuth2 token to format\n * @param clientId - The client ID\n * @param userId - The user ID\n * @returns\n */\nexport const formatDBToken = (\n token: OAuth2Token,\n clientId: Client['id'],\n userId: User['id'] | string\n): Token => {\n const formattedToken: Token = {\n id: token.id,\n clientId: clientId,\n userId: userId as Types.ObjectId,\n accessToken: token.accessToken,\n expiresIn: token.accessTokenExpiresAt ?? getTokenExpireAt(),\n };\n\n return formattedToken;\n};\n\n/**\n * Method to save the token\n *\n * @param token - The token\n * @param client - The client\n * @param user - The user\n * @returns The saved token or false if not saved\n */\nexport const saveToken = async (\n token: OAuth2Token,\n client: Client,\n user: UserAPI\n): Promise<OAuth2Token | false> => {\n const formattedAccessToken: Token = formatDBToken(token, client.id, user.id);\n\n const result = await OAuth2AccessTokenModel.create(formattedAccessToken);\n\n if (!result) {\n return false;\n }\n\n const result2 = await getClientAndProjectByClientId(result.clientId);\n\n if (!result2) {\n return false;\n }\n\n const { project } = result2;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedResult = formatOAuth2Token(\n formattedAccessToken,\n client,\n user,\n project,\n organization,\n token.rights\n );\n return formattedResult;\n};\n\n/**\n * Sliding-refresh: push the token's expiry forward when it has been used\n * within the refresh threshold. Idempotent and cheap when no extension is due.\n */\nexport const extendOAuth2AccessToken = async (\n accessToken: string\n): Promise<Date> => {\n const nextExpiresAt = getTokenExpireAt();\n await OAuth2AccessTokenModel.updateOne(\n { accessToken: String(accessToken) },\n { $set: { accessTokenExpiresAt: nextExpiresAt, expiresIn: nextExpiresAt } }\n );\n return nextExpiresAt;\n};\n\n/**\n * Method to get the access token\n *\n * @param accessToken - The access token\n * @returns The access token or false if not found\n */\nexport const getAccessToken = async (\n accessToken: string\n): Promise<OAuth2Token | false> => {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken: String(accessToken),\n });\n\n if (!token) {\n return false;\n }\n\n // Slide the expiry forward when this active token is approaching its\n // deadline so a long-lived integration doesn't have to re-authenticate.\n const currentExpiresAt = token.accessTokenExpiresAt ?? token.expiresIn;\n if (currentExpiresAt && shouldExtendOAuth2Token(currentExpiresAt)) {\n const nextExpiresAt = await extendOAuth2AccessToken(accessToken);\n token.accessTokenExpiresAt = nextExpiresAt;\n token.expiresIn = nextExpiresAt;\n }\n\n const { userId, clientId } = token;\n\n const user = await getUserById(userId);\n\n if (!user) {\n return false;\n }\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n return false;\n }\n\n const { client, project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n if (!organization) {\n return false;\n }\n\n const formattedAccessToken = formatOAuth2Token(\n token,\n client,\n user,\n project,\n organization,\n grants\n );\n\n return formattedAccessToken;\n};\n\n/**\n * Method to get the user from the client\n *\n * @param client - The client\n * @returns The user or false if not found\n */\nexport const getUserFromClient = async (\n client: Client\n): Promise<UserDocument | false> => {\n const response = await getClientAndProjectByClientId(client.id);\n\n if (!response) {\n return false;\n }\n\n const { userId } = response.oAuth2Access;\n\n if (!userId) {\n return false;\n }\n\n const user = await getUserById(userId);\n\n return user ?? false;\n};\n\n/**\n * Method to verify the permissions (grants)\n *\n * @param token - The token\n * @param scope - The scope\n * @returns True if the token has the required scope, false otherwise\n */\nexport const verifyScope = async (\n _token: OAuth2Token,\n _scope: string,\n _callback?: Callback<boolean> | undefined\n): Promise<boolean> => {\n // Implement the verification of scopes if necessary\n return true;\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const validateOAuth2AccessToken = async (\n accessToken: string\n): Promise<Token> => {\n try {\n const token = await OAuth2AccessTokenModel.findOne({\n accessToken,\n });\n\n if (!token) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n // Check if token is expired\n if (new Date() > new Date(token.expiresIn)) {\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n return ensureMongoDocumentToObject(token);\n } catch (_error) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n};\n\n/**\n * Validate OAuth2 access token and return user context\n */\nexport const getOAuth2AccessTokenContext = async (\n token: Token\n): Promise<OAuth2AccessContext> => {\n const { userId, clientId } = token;\n\n const user = await getUserById(String(userId));\n\n const result = await getClientAndProjectByClientId(clientId);\n\n if (!result) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const { project, grants } = result;\n\n const organization = await getOrganizationById(project.organizationId);\n\n return {\n accessToken: token.accessToken,\n user: user ? mapUserToAPI(user) : undefined,\n project: project ? mapProjectToAPI(project) : undefined,\n organization: organization ? mapOrganizationToAPI(organization) : undefined,\n grants,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,MAAa,kCAGR;CAIH,OAAO;EAAE,UAHQ,YAAY,EAAE,EAAE,SAAS,KAG1B;EAAG,cAFE,YAAY,EAAE,EAAE,SAAS,KAEhB;CAAE;AAClC;;;;;;;;AASA,MAAa,gCAAgC,OAC3C,aASG;CACH,MAAM,UAAU,MAAM,aAAa,QAAQ,EACzC,yBAAyB,OAAO,QAAQ,EAC1C,CAAC;CAED,IAAI,CAAC,SACH,OAAO;CAGT,MAAM,eAAe,QAAQ,aAAa,MACvC,WAAW,OAAO,aAAa,QAClC;CAEA,IAAI,CAAC,cACH,OAAO;CAUT,OAAO;EACL,QAAQ;GAPR,IAAI,aAAa;GACjB;GACA,cAAc,aAAa;GAC3B,QAAQ,CAAC,oBAAoB;EAIP;EACtB;EACA,QAAQ,aAAa;EACrB;CACF;AACF;;;;;;;;AASA,MAAa,YAAY,OACvB,UACA,iBAC4B;CAC5B,MAAM,SAAS,MAAM,8BAA8B,QAAQ;CAE3D,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,EAAE,WAAW;CAEnB,IAAI,CAAC,UAAU,OAAO,iBAAiB,cACrC,OAAO;CAGT,OAAO;AACT;;;;;;;;;;;;AAaA,MAAa,qBACX,OACA,QACA,MACA,SACA,cACA,WACgB;CAChB,MAAM,EAAE,UAAU,QAAQ,GAAG,cAAc;CAE3C,IAAI,OAAO,MAAM,MAAM,OAAO,KAAK,EAAE,GACnC,MAAM,IAAI,aAAa,kBAAkB;CAc3C,OAAO;EAVL,GAAG;EACH;EACA,MAAM,aAAa,IAAI;EACvB,cAAc,qBAAqB,YAAY;EAC/C,SAAS,gBAAgB,OAAO;EAChC,aAAa,MAAM;EACnB,sBAAsB,MAAM,wCAAwB,IAAI,KAAK,WAAW;EACxE;CAGkB;AACtB;;;;;;;;;AAUA,MAAa,iBACX,OACA,UACA,WACU;CASV,OAAO;EAPL,IAAI,MAAM;EACA;EACF;EACR,aAAa,MAAM;EACnB,WAAW,MAAM,wBAAwB,iBAAiB;CAGxC;AACtB;;;;;;;;;AAUA,MAAa,YAAY,OACvB,OACA,QACA,SACiC;CACjC,MAAM,uBAA8B,cAAc,OAAO,OAAO,IAAI,KAAK,EAAE;CAE3E,MAAM,SAAS,MAAM,uBAAuB,OAAO,oBAAoB;CAEvE,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,UAAU,MAAM,8BAA8B,OAAO,QAAQ;CAEnE,IAAI,CAAC,SACH,OAAO;CAGT,MAAM,EAAE,YAAY;CAEpB,MAAM,eAAe,MAAM,oBAAoB,QAAQ,cAAc;CAErE,IAAI,CAAC,cACH,OAAO;CAWT,OARwB,kBACtB,sBACA,QACA,MACA,SACA,cACA,MAAM,MAEa;AACvB;;;;;AAMA,MAAa,0BAA0B,OACrC,gBACkB;CAClB,MAAM,gBAAgB,iBAAiB;CACvC,MAAM,uBAAuB,UAC3B,EAAE,aAAa,OAAO,WAAW,EAAE,GACnC,EAAE,MAAM;EAAE,sBAAsB;EAAe,WAAW;CAAc,EAAE,CAC5E;CACA,OAAO;AACT;;;;;;;AAQA,MAAa,iBAAiB,OAC5B,gBACiC;CACjC,MAAM,QAAQ,MAAM,uBAAuB,QAAQ,EACjD,aAAa,OAAO,WAAW,EACjC,CAAC;CAED,IAAI,CAAC,OACH,OAAO;CAKT,MAAM,mBAAmB,MAAM,wBAAwB,MAAM;CAC7D,IAAI,oBAAoB,wBAAwB,gBAAgB,GAAG;EACjE,MAAM,gBAAgB,MAAM,wBAAwB,WAAW;EAC/D,MAAM,uBAAuB;EAC7B,MAAM,YAAY;CACpB;CAEA,MAAM,EAAE,QAAQ,aAAa;CAE7B,MAAM,OAAO,MAAM,YAAY,MAAM;CAErC,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,SAAS,MAAM,8BAA8B,QAAQ;CAE3D,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,EAAE,QAAQ,SAAS,WAAW;CAEpC,MAAM,eAAe,MAAM,oBAAoB,QAAQ,cAAc;CAErE,IAAI,CAAC,cACH,OAAO;CAYT,OAT6B,kBAC3B,OACA,QACA,MACA,SACA,cACA,MAGwB;AAC5B;;;;;;;AAQA,MAAa,oBAAoB,OAC/B,WACkC;CAClC,MAAM,WAAW,MAAM,8BAA8B,OAAO,EAAE;CAE9D,IAAI,CAAC,UACH,OAAO;CAGT,MAAM,EAAE,WAAW,SAAS;CAE5B,IAAI,CAAC,QACH,OAAO;CAKT,OAAO,MAFY,YAAY,MAAM,KAEtB;AACjB;;;;;;;;AASA,MAAa,cAAc,OACzB,QACA,QACA,cACqB;CAErB,OAAO;AACT;;;;AAKA,MAAa,4BAA4B,OACvC,gBACmB;CACnB,IAAI;EACF,MAAM,QAAQ,MAAM,uBAAuB,QAAQ,EACjD,YACF,CAAC;EAED,IAAI,CAAC,OACH,MAAM,IAAI,aAAa,sBAAsB;EAI/C,oBAAI,IAAI,KAAK,IAAI,IAAI,KAAK,MAAM,SAAS,GACvC,MAAM,IAAI,aAAa,sBAAsB;EAG/C,OAAO,4BAA4B,KAAK;CAC1C,SAAS,QAAQ;EACf,MAAM,IAAI,aAAa,sBAAsB;CAC/C;AACF;;;;AAKA,MAAa,8BAA8B,OACzC,UACiC;CACjC,MAAM,EAAE,QAAQ,aAAa;CAE7B,MAAM,OAAO,MAAM,YAAY,OAAO,MAAM,CAAC;CAE7C,MAAM,SAAS,MAAM,8BAA8B,QAAQ;CAE3D,IAAI,CAAC,QACH,MAAM,IAAI,aAAa,sBAAsB;CAG/C,MAAM,EAAE,SAAS,WAAW;CAE5B,MAAM,eAAe,MAAM,oBAAoB,QAAQ,cAAc;CAErE,OAAO;EACL,aAAa,MAAM;EACnB,MAAM,OAAO,aAAa,IAAI,IAAI;EAClC,SAAS,UAAU,gBAAgB,OAAO,IAAI;EAC9C,cAAc,eAAe,qBAAqB,YAAY,IAAI;EAClE;CACF;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { GenericError } from "../utils/errors/ErrorsClass.mjs";
2
2
  import { validateOrganization } from "../utils/validation/validateOrganization.mjs";
3
- import { OrganizationModel } from "../models/organization.model.mjs";
3
+ import { OrganizationModel } from "../schemas/organization.schema.mjs";
4
4
 
5
5
  //#region src/services/organization.service.ts
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"organization.service.mjs","names":[],"sources":["../../../src/services/organization.service.ts"],"sourcesContent":["import { OrganizationModel } from '@models/organization.model';\nimport { GenericError } from '@utils/errors';\nimport type { OrganizationFilters } from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n type OrganizationFields,\n validateOrganization,\n} from '@utils/validation/validateOrganization';\nimport type { Types } from 'mongoose';\nimport type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n OrganizationDocument,\n} from '@/types/organization.types';\nimport type { Plan, PlanDocument } from '@/types/plan.types';\n\n/**\n * Finds organizations based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of organizations matching the filters.\n */\nexport const findOrganizations = async (\n filters: OrganizationFilters,\n skip: number,\n limit: number,\n sortOptions?: Record<string, 1 | -1>\n): Promise<OrganizationDocument[]> => {\n let query = OrganizationModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Finds an organization by its ID.\n * @param organizationId - The ID of the organization to find.\n * @returns The organization matching the ID.\n */\nexport const getOrganizationById = async (\n organizationId: string | Types.ObjectId\n): Promise<OrganizationDocument> => {\n const organization = await OrganizationModel.findById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Counts the total number of organizations that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of organizations.\n */\nexport const countOrganizations = async (\n filters: OrganizationFilters\n): Promise<number> => {\n const result = await OrganizationModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('ORGANIZATION_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new organization in the database.\n * @param organization - The organization data to create.\n * @returns The created organization.\n */\nexport const createOrganization = async (\n organization: OrganizationCreationData,\n userId: string | Types.ObjectId\n): Promise<OrganizationDocument> => {\n const errors = validateOrganization(organization, ['name']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', { errors });\n }\n\n try {\n const result = await OrganizationModel.create({\n creatorId: userId,\n membersIds: [userId],\n adminsIds: [userId],\n ...organization,\n });\n\n return result;\n } catch (error) {\n throw new GenericError('ORGANIZATION_CREATION_FAILED', {\n error: (error as Error).message,\n });\n }\n};\n\n/**\n * Updates an existing organization in the database by its ID.\n * @param organizationId - The ID of the organization to update.\n * @param organization - The updated organization data.\n * @returns The updated organization.\n */\nexport const updateOrganizationById = async (\n organizationId: string | Types.ObjectId,\n organization: Partial<Organization | OrganizationAPI>\n): Promise<OrganizationDocument> => {\n const updatedKeys = Object.keys(organization) as OrganizationFields;\n const errors = validateOrganization(organization, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', {\n organizationId,\n errors,\n });\n }\n\n const result = await OrganizationModel.updateOne(\n { _id: organizationId },\n organization\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', { organizationId });\n }\n\n return await getOrganizationById(organizationId);\n};\n\n/**\n * Deletes an organization from the database by its ID.\n * @param organizationId - The ID of the organization to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteOrganizationById = async (\n organizationId: string | Types.ObjectId\n): Promise<OrganizationDocument> => {\n const organization =\n await OrganizationModel.findByIdAndDelete(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Updates an existing plan in the database by its ID.\n * @param planId - The ID of the plan to update.\n * @param plan - The updated plan data.\n * @returns The updated plan.\n */\nexport const updatePlan = async (\n organization: Organization | OrganizationDocument,\n plan: Partial<Plan>\n): Promise<OrganizationDocument | null> => {\n let prevPlan = organization.plan ?? {};\n\n if (typeof (prevPlan as PlanDocument)?.toObject === 'function') {\n prevPlan = (prevPlan as PlanDocument).toObject();\n }\n\n const updateOrganizationResult = await OrganizationModel.updateOne(\n { _id: organization.id },\n { $set: { plan: { ...prevPlan, ...plan } } }\n );\n\n if (updateOrganizationResult.matchedCount === 0) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await getOrganizationById(organization.id);\n\n return updatedOrganization;\n};\n"],"mappings":";;;;;;;;;;;;AAuBA,MAAa,oBAAoB,OAC/B,SACA,MACA,OACA,gBACoC;CACpC,IAAI,QAAQ,kBAAkB,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAElE,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,sBAAsB,OACjC,mBACkC;CAClC,MAAM,eAAe,MAAM,kBAAkB,SAAS,cAAc;CAEpE,IAAI,CAAC,cACH,MAAM,IAAI,aAAa,0BAA0B,EAAE,eAAe,CAAC;CAGrE,OAAO;AACT;;;;;;AAOA,MAAa,qBAAqB,OAChC,YACoB;CACpB,MAAM,SAAS,MAAM,kBAAkB,eAAe,OAAO;CAE7D,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,aAAa,6BAA6B,EAAE,QAAQ,CAAC;CAGjE,OAAO;AACT;;;;;;AAOA,MAAa,qBAAqB,OAChC,cACA,WACkC;CAClC,MAAM,SAAS,qBAAqB,cAAc,CAAC,MAAM,CAAC;CAE1D,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,+BAA+B,EAAE,OAAO,CAAC;CAGlE,IAAI;EAQF,OAAO,MAPc,kBAAkB,OAAO;GAC5C,WAAW;GACX,YAAY,CAAC,MAAM;GACnB,WAAW,CAAC,MAAM;GAClB,GAAG;EACL,CAAC;CAGH,SAAS,OAAO;EACd,MAAM,IAAI,aAAa,gCAAgC,EACrD,OAAQ,MAAgB,QAC1B,CAAC;CACH;AACF;;;;;;;AAQA,MAAa,yBAAyB,OACpC,gBACA,iBACkC;CAElC,MAAM,SAAS,qBAAqB,cADhB,OAAO,KAAK,YAC4B,CAAC;CAE7D,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,+BAA+B;EACpD;EACA;CACF,CAAC;CAQH,KAAI,MALiB,kBAAkB,UACrC,EAAE,KAAK,eAAe,GACtB,YACF,GAEW,iBAAiB,GAC1B,MAAM,IAAI,aAAa,8BAA8B,EAAE,eAAe,CAAC;CAGzE,OAAO,MAAM,oBAAoB,cAAc;AACjD;;;;;;AAOA,MAAa,yBAAyB,OACpC,mBACkC;CAClC,MAAM,eACJ,MAAM,kBAAkB,kBAAkB,cAAc;CAE1D,IAAI,CAAC,cACH,MAAM,IAAI,aAAa,0BAA0B,EAAE,eAAe,CAAC;CAGrE,OAAO;AACT;;;;;;;AAQA,MAAa,aAAa,OACxB,cACA,SACyC;CACzC,IAAI,WAAW,aAAa,QAAQ,CAAC;CAErC,IAAI,OAAQ,UAA2B,aAAa,YAClD,WAAY,SAA0B,SAAS;CAQjD,KAAI,MALmC,kBAAkB,UACvD,EAAE,KAAK,aAAa,GAAG,GACvB,EAAE,MAAM,EAAE,MAAM;EAAE,GAAG;EAAU,GAAG;CAAK,EAAE,EAAE,CAC7C,GAE6B,iBAAiB,GAC5C,MAAM,IAAI,aAAa,8BAA8B,EACnD,gBAAgB,aAAa,GAC/B,CAAC;CAKH,OAAO,MAF2B,oBAAoB,aAAa,EAAE;AAGvE"}
1
+ {"version":3,"file":"organization.service.mjs","names":[],"sources":["../../../src/services/organization.service.ts"],"sourcesContent":["import { OrganizationModel } from '@schemas/organization.schema';\nimport { GenericError } from '@utils/errors';\nimport type { OrganizationFilters } from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n type OrganizationFields,\n validateOrganization,\n} from '@utils/validation/validateOrganization';\nimport type { Types } from 'mongoose';\nimport type {\n Organization,\n OrganizationAPI,\n OrganizationCreationData,\n OrganizationDocument,\n} from '@/types/organization.types';\nimport type { Plan, PlanDocument } from '@/types/plan.types';\n\n/**\n * Finds organizations based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of organizations matching the filters.\n */\nexport const findOrganizations = async (\n filters: OrganizationFilters,\n skip: number,\n limit: number,\n sortOptions?: Record<string, 1 | -1>\n): Promise<OrganizationDocument[]> => {\n let query = OrganizationModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Finds an organization by its ID.\n * @param organizationId - The ID of the organization to find.\n * @returns The organization matching the ID.\n */\nexport const getOrganizationById = async (\n organizationId: string | Types.ObjectId\n): Promise<OrganizationDocument> => {\n const organization = await OrganizationModel.findById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Counts the total number of organizations that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of organizations.\n */\nexport const countOrganizations = async (\n filters: OrganizationFilters\n): Promise<number> => {\n const result = await OrganizationModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('ORGANIZATION_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new organization in the database.\n * @param organization - The organization data to create.\n * @returns The created organization.\n */\nexport const createOrganization = async (\n organization: OrganizationCreationData,\n userId: string | Types.ObjectId\n): Promise<OrganizationDocument> => {\n const errors = validateOrganization(organization, ['name']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', { errors });\n }\n\n try {\n const result = await OrganizationModel.create({\n creatorId: userId,\n membersIds: [userId],\n adminsIds: [userId],\n ...organization,\n });\n\n return result;\n } catch (error) {\n throw new GenericError('ORGANIZATION_CREATION_FAILED', {\n error: (error as Error).message,\n });\n }\n};\n\n/**\n * Updates an existing organization in the database by its ID.\n * @param organizationId - The ID of the organization to update.\n * @param organization - The updated organization data.\n * @returns The updated organization.\n */\nexport const updateOrganizationById = async (\n organizationId: string | Types.ObjectId,\n organization: Partial<Organization | OrganizationAPI>\n): Promise<OrganizationDocument> => {\n const updatedKeys = Object.keys(organization) as OrganizationFields;\n const errors = validateOrganization(organization, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', {\n organizationId,\n errors,\n });\n }\n\n const result = await OrganizationModel.updateOne(\n { _id: organizationId },\n organization\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', { organizationId });\n }\n\n return await getOrganizationById(organizationId);\n};\n\n/**\n * Deletes an organization from the database by its ID.\n * @param organizationId - The ID of the organization to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteOrganizationById = async (\n organizationId: string | Types.ObjectId\n): Promise<OrganizationDocument> => {\n const organization =\n await OrganizationModel.findByIdAndDelete(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Updates an existing plan in the database by its ID.\n * @param planId - The ID of the plan to update.\n * @param plan - The updated plan data.\n * @returns The updated plan.\n */\nexport const updatePlan = async (\n organization: Organization | OrganizationDocument,\n plan: Partial<Plan>\n): Promise<OrganizationDocument | null> => {\n let prevPlan = organization.plan ?? {};\n\n if (typeof (prevPlan as PlanDocument)?.toObject === 'function') {\n prevPlan = (prevPlan as PlanDocument).toObject();\n }\n\n const updateOrganizationResult = await OrganizationModel.updateOne(\n { _id: organization.id },\n { $set: { plan: { ...prevPlan, ...plan } } }\n );\n\n if (updateOrganizationResult.matchedCount === 0) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization.id,\n });\n }\n\n const updatedOrganization = await getOrganizationById(organization.id);\n\n return updatedOrganization;\n};\n"],"mappings":";;;;;;;;;;;;AAuBA,MAAa,oBAAoB,OAC/B,SACA,MACA,OACA,gBACoC;CACpC,IAAI,QAAQ,kBAAkB,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAElE,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,sBAAsB,OACjC,mBACkC;CAClC,MAAM,eAAe,MAAM,kBAAkB,SAAS,cAAc;CAEpE,IAAI,CAAC,cACH,MAAM,IAAI,aAAa,0BAA0B,EAAE,eAAe,CAAC;CAGrE,OAAO;AACT;;;;;;AAOA,MAAa,qBAAqB,OAChC,YACoB;CACpB,MAAM,SAAS,MAAM,kBAAkB,eAAe,OAAO;CAE7D,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,aAAa,6BAA6B,EAAE,QAAQ,CAAC;CAGjE,OAAO;AACT;;;;;;AAOA,MAAa,qBAAqB,OAChC,cACA,WACkC;CAClC,MAAM,SAAS,qBAAqB,cAAc,CAAC,MAAM,CAAC;CAE1D,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,+BAA+B,EAAE,OAAO,CAAC;CAGlE,IAAI;EAQF,OAAO,MAPc,kBAAkB,OAAO;GAC5C,WAAW;GACX,YAAY,CAAC,MAAM;GACnB,WAAW,CAAC,MAAM;GAClB,GAAG;EACL,CAAC;CAGH,SAAS,OAAO;EACd,MAAM,IAAI,aAAa,gCAAgC,EACrD,OAAQ,MAAgB,QAC1B,CAAC;CACH;AACF;;;;;;;AAQA,MAAa,yBAAyB,OACpC,gBACA,iBACkC;CAElC,MAAM,SAAS,qBAAqB,cADhB,OAAO,KAAK,YAC4B,CAAC;CAE7D,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,+BAA+B;EACpD;EACA;CACF,CAAC;CAQH,KAAI,MALiB,kBAAkB,UACrC,EAAE,KAAK,eAAe,GACtB,YACF,GAEW,iBAAiB,GAC1B,MAAM,IAAI,aAAa,8BAA8B,EAAE,eAAe,CAAC;CAGzE,OAAO,MAAM,oBAAoB,cAAc;AACjD;;;;;;AAOA,MAAa,yBAAyB,OACpC,mBACkC;CAClC,MAAM,eACJ,MAAM,kBAAkB,kBAAkB,cAAc;CAE1D,IAAI,CAAC,cACH,MAAM,IAAI,aAAa,0BAA0B,EAAE,eAAe,CAAC;CAGrE,OAAO;AACT;;;;;;;AAQA,MAAa,aAAa,OACxB,cACA,SACyC;CACzC,IAAI,WAAW,aAAa,QAAQ,CAAC;CAErC,IAAI,OAAQ,UAA2B,aAAa,YAClD,WAAY,SAA0B,SAAS;CAQjD,KAAI,MALmC,kBAAkB,UACvD,EAAE,KAAK,aAAa,GAAG,GACvB,EAAE,MAAM,EAAE,MAAM;EAAE,GAAG;EAAU,GAAG;CAAK,EAAE,EAAE,CAC7C,GAE6B,iBAAiB,GAC5C,MAAM,IAAI,aAAa,8BAA8B,EACnD,gBAAgB,aAAa,GAC/B,CAAC;CAKH,OAAO,MAF2B,oBAAoB,aAAa,EAAE;AAGvE"}
@@ -2,7 +2,7 @@ import { ensureMongoDocumentToObject } from "../utils/ensureMongoDocumentToObjec
2
2
  import { GenericError } from "../utils/errors/ErrorsClass.mjs";
3
3
  import { removeObjectKeys } from "../utils/removeObjectKeys.mjs";
4
4
  import { validateProject } from "../utils/validation/validateProject.mjs";
5
- import { ProjectModel } from "../models/project.model.mjs";
5
+ import { ProjectModel } from "../schemas/project.schema.mjs";
6
6
 
7
7
  //#region src/services/project.service.ts
8
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"project.service.mjs","names":[],"sources":["../../../src/services/project.service.ts"],"sourcesContent":["import { ProjectModel } from '@models/project.model';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { ProjectFilters } from '@utils/filtersAndPagination/getProjectFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type ProjectFields,\n validateProject,\n} from '@utils/validation/validateProject';\nimport type { Types } from 'mongoose';\nimport type {\n Project,\n ProjectAPI,\n ProjectData,\n ProjectDocument,\n} from '@/types/project.types';\n\n/**\n * Finds projects based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of projects matching the filters.\n */\nexport const findProjects = async (\n filters: ProjectFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>\n): Promise<ProjectDocument[]> => {\n let query = ProjectModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Finds a project by its ID.\n * @param projectId - The ID of the project to find.\n * @returns The project matching the ID.\n */\nexport const getProjectById = async (\n projectId: string | Types.ObjectId\n): Promise<ProjectDocument> => {\n const project = await ProjectModel.findById(projectId);\n\n if (!project) {\n throw new GenericError('PROJECT_NOT_DEFINED', { projectId });\n }\n\n return project;\n};\n\n/**\n * Counts the total number of projects that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of projects.\n */\nexport const countProjects = async (\n filters: ProjectFilters\n): Promise<number> => {\n const result = await ProjectModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('PROJECT_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new project in the database.\n * @param project - The project data to create.\n * @returns The created project.\n */\nexport const createProject = async (\n project: ProjectData\n): Promise<ProjectDocument> => {\n if ((project as Partial<Project>).oAuth2Access) {\n (project as Partial<Project>).oAuth2Access = undefined;\n }\n\n const errors = await validateProject(project, ['name']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('PROJECT_INVALID_FIELDS', { errors });\n }\n\n return await ProjectModel.create(project);\n};\n\n/**\n * Updates an existing project in the database by its ID.\n * @param projectId - The ID of the project to update.\n * @param project - The updated project data.\n * @returns The updated project.\n */\nexport const updateProjectById = async (\n projectId: string | Types.ObjectId,\n project: Partial<Project | ProjectAPI>\n): Promise<ProjectDocument> => {\n const projectObject = ensureMongoDocumentToObject(project);\n const projectToUpdate = removeObjectKeys(projectObject, [\n 'id',\n 'oAuth2Access',\n 'organizationId',\n ]);\n\n const updatedKeys = Object.keys(projectToUpdate) as ProjectFields;\n\n const errors = await validateProject(project, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('PROJECT_INVALID_FIELDS', {\n projectId,\n errors,\n });\n }\n\n const result = await ProjectModel.updateOne(\n { _id: projectId },\n projectToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('PROJECT_UPDATE_FAILED', { projectId });\n }\n\n return await getProjectById(projectId);\n};\n\n/**\n * Deletes a project from the database by its ID.\n * @param projectId - The ID of the project to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteProjectById = async (\n projectId: string | Types.ObjectId\n): Promise<ProjectDocument> => {\n const project = await ProjectModel.findByIdAndDelete(projectId);\n\n if (!project) {\n throw new GenericError('PROJECT_NOT_DEFINED', { projectId });\n }\n\n return project;\n};\n"],"mappings":";;;;;;;;;;;;;;AAwBA,MAAa,eAAe,OAC1B,SACA,OAAO,GACP,QAAQ,KACR,gBAC+B;CAC/B,IAAI,QAAQ,aAAa,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAE7D,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,iBAAiB,OAC5B,cAC6B;CAC7B,MAAM,UAAU,MAAM,aAAa,SAAS,SAAS;CAErD,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,uBAAuB,EAAE,UAAU,CAAC;CAG7D,OAAO;AACT;;;;;;AAOA,MAAa,gBAAgB,OAC3B,YACoB;CACpB,MAAM,SAAS,MAAM,aAAa,eAAe,OAAO;CAExD,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,aAAa,wBAAwB,EAAE,QAAQ,CAAC;CAG5D,OAAO;AACT;;;;;;AAOA,MAAa,gBAAgB,OAC3B,YAC6B;CAC7B,IAAK,QAA6B,cAChC,AAAC,QAA6B,eAAe;CAG/C,MAAM,SAAS,MAAM,gBAAgB,SAAS,CAAC,MAAM,CAAC;CAEtD,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,0BAA0B,EAAE,OAAO,CAAC;CAG7D,OAAO,MAAM,aAAa,OAAO,OAAO;AAC1C;;;;;;;AAQA,MAAa,oBAAoB,OAC/B,WACA,YAC6B;CAE7B,MAAM,kBAAkB,iBADF,4BAA4B,OACG,GAAG;EACtD;EACA;EACA;CACF,CAAC;CAID,MAAM,SAAS,MAAM,gBAAgB,SAFjB,OAAO,KAAK,eAEwB,CAAC;CAEzD,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,0BAA0B;EAC/C;EACA;CACF,CAAC;CAQH,KAAI,MALiB,aAAa,UAChC,EAAE,KAAK,UAAU,GACjB,eACF,GAEW,iBAAiB,GAC1B,MAAM,IAAI,aAAa,yBAAyB,EAAE,UAAU,CAAC;CAG/D,OAAO,MAAM,eAAe,SAAS;AACvC;;;;;;AAOA,MAAa,oBAAoB,OAC/B,cAC6B;CAC7B,MAAM,UAAU,MAAM,aAAa,kBAAkB,SAAS;CAE9D,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,uBAAuB,EAAE,UAAU,CAAC;CAG7D,OAAO;AACT"}
1
+ {"version":3,"file":"project.service.mjs","names":[],"sources":["../../../src/services/project.service.ts"],"sourcesContent":["import { ProjectModel } from '@schemas/project.schema';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { ProjectFilters } from '@utils/filtersAndPagination/getProjectFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type ProjectFields,\n validateProject,\n} from '@utils/validation/validateProject';\nimport type { Types } from 'mongoose';\nimport type {\n Project,\n ProjectAPI,\n ProjectData,\n ProjectDocument,\n} from '@/types/project.types';\n\n/**\n * Finds projects based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of projects matching the filters.\n */\nexport const findProjects = async (\n filters: ProjectFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>\n): Promise<ProjectDocument[]> => {\n let query = ProjectModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Finds a project by its ID.\n * @param projectId - The ID of the project to find.\n * @returns The project matching the ID.\n */\nexport const getProjectById = async (\n projectId: string | Types.ObjectId\n): Promise<ProjectDocument> => {\n const project = await ProjectModel.findById(projectId);\n\n if (!project) {\n throw new GenericError('PROJECT_NOT_DEFINED', { projectId });\n }\n\n return project;\n};\n\n/**\n * Counts the total number of projects that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of projects.\n */\nexport const countProjects = async (\n filters: ProjectFilters\n): Promise<number> => {\n const result = await ProjectModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('PROJECT_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new project in the database.\n * @param project - The project data to create.\n * @returns The created project.\n */\nexport const createProject = async (\n project: ProjectData\n): Promise<ProjectDocument> => {\n if ((project as Partial<Project>).oAuth2Access) {\n (project as Partial<Project>).oAuth2Access = undefined;\n }\n\n const errors = await validateProject(project, ['name']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('PROJECT_INVALID_FIELDS', { errors });\n }\n\n return await ProjectModel.create(project);\n};\n\n/**\n * Updates an existing project in the database by its ID.\n * @param projectId - The ID of the project to update.\n * @param project - The updated project data.\n * @returns The updated project.\n */\nexport const updateProjectById = async (\n projectId: string | Types.ObjectId,\n project: Partial<Project | ProjectAPI>\n): Promise<ProjectDocument> => {\n const projectObject = ensureMongoDocumentToObject(project);\n const projectToUpdate = removeObjectKeys(projectObject, [\n 'id',\n 'oAuth2Access',\n 'organizationId',\n ]);\n\n const updatedKeys = Object.keys(projectToUpdate) as ProjectFields;\n\n const errors = await validateProject(project, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('PROJECT_INVALID_FIELDS', {\n projectId,\n errors,\n });\n }\n\n const result = await ProjectModel.updateOne(\n { _id: projectId },\n projectToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('PROJECT_UPDATE_FAILED', { projectId });\n }\n\n return await getProjectById(projectId);\n};\n\n/**\n * Deletes a project from the database by its ID.\n * @param projectId - The ID of the project to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteProjectById = async (\n projectId: string | Types.ObjectId\n): Promise<ProjectDocument> => {\n const project = await ProjectModel.findByIdAndDelete(projectId);\n\n if (!project) {\n throw new GenericError('PROJECT_NOT_DEFINED', { projectId });\n }\n\n return project;\n};\n"],"mappings":";;;;;;;;;;;;;;AAwBA,MAAa,eAAe,OAC1B,SACA,OAAO,GACP,QAAQ,KACR,gBAC+B;CAC/B,IAAI,QAAQ,aAAa,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAE7D,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,iBAAiB,OAC5B,cAC6B;CAC7B,MAAM,UAAU,MAAM,aAAa,SAAS,SAAS;CAErD,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,uBAAuB,EAAE,UAAU,CAAC;CAG7D,OAAO;AACT;;;;;;AAOA,MAAa,gBAAgB,OAC3B,YACoB;CACpB,MAAM,SAAS,MAAM,aAAa,eAAe,OAAO;CAExD,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,aAAa,wBAAwB,EAAE,QAAQ,CAAC;CAG5D,OAAO;AACT;;;;;;AAOA,MAAa,gBAAgB,OAC3B,YAC6B;CAC7B,IAAK,QAA6B,cAChC,AAAC,QAA6B,eAAe;CAG/C,MAAM,SAAS,MAAM,gBAAgB,SAAS,CAAC,MAAM,CAAC;CAEtD,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,0BAA0B,EAAE,OAAO,CAAC;CAG7D,OAAO,MAAM,aAAa,OAAO,OAAO;AAC1C;;;;;;;AAQA,MAAa,oBAAoB,OAC/B,WACA,YAC6B;CAE7B,MAAM,kBAAkB,iBADF,4BAA4B,OACG,GAAG;EACtD;EACA;EACA;CACF,CAAC;CAID,MAAM,SAAS,MAAM,gBAAgB,SAFjB,OAAO,KAAK,eAEwB,CAAC;CAEzD,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,0BAA0B;EAC/C;EACA;CACF,CAAC;CAQH,KAAI,MALiB,aAAa,UAChC,EAAE,KAAK,UAAU,GACjB,eACF,GAEW,iBAAiB,GAC1B,MAAM,IAAI,aAAa,yBAAyB,EAAE,UAAU,CAAC;CAG/D,OAAO,MAAM,eAAe,SAAS;AACvC;;;;;;AAOA,MAAa,oBAAoB,OAC/B,cAC6B;CAC7B,MAAM,UAAU,MAAM,aAAa,kBAAkB,SAAS;CAE9D,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,uBAAuB,EAAE,UAAU,CAAC;CAG7D,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  import { GenericError } from "../utils/errors/ErrorsClass.mjs";
2
- import { ProjectModel } from "../models/project.model.mjs";
2
+ import { ProjectModel } from "../schemas/project.schema.mjs";
3
3
  import { getProjectById } from "./project.service.mjs";
4
4
  import { randomBytes } from "node:crypto";
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"projectAccessKey.service.mjs","names":[],"sources":["../../../src/services/projectAccessKey.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { ProjectModel } from '@models/project.model';\nimport { GenericError } from '@utils/errors';\nimport type { Types } from 'mongoose';\nimport type {\n AccessKeyData,\n OAuth2Access,\n OAuth2AccessData,\n Project,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\nimport { getProjectById } from './project.service';\n\n/**\n * Generates cryptographically secure OAuth2 client credentials\n *\n * @returns Object containing clientId and clientSecret\n *\n * Security improvements:\n * - clientId: 32 characters (128 bits of entropy)\n * - clientSecret: 64 characters (256 bits of entropy)\n * - Uses crypto.randomBytes for cryptographically secure random generation\n * - Follows OAuth2 best practices for credential strength\n */\nconst generateClientCredentials = () => ({\n clientId: randomBytes(16).toString('hex'), // 32 character hexadecimal string\n clientSecret: randomBytes(32).toString('hex'), // 64 character hexadecimal string\n});\n\n/**\n * Adds a new access key to a project.\n *\n * @param accessKeyData - The access key data.\n * @param projectId - The ID of the project to add the access key to.\n * @param user - The user adding the access key.\n * @returns The new access key.\n *\n */\nexport const addNewAccessKey = async (\n accessKeyData: AccessKeyData,\n projectId: string | Types.ObjectId,\n user: User\n): Promise<OAuth2Access> => {\n const { clientId, clientSecret } = generateClientCredentials();\n\n const newAccessKey: OAuth2AccessData = {\n ...accessKeyData,\n clientId,\n clientSecret,\n userId: user.id,\n accessToken: [],\n grants: accessKeyData.grants,\n };\n\n const result = await ProjectModel.updateOne(\n { _id: projectId },\n { $push: { oAuth2Access: newAccessKey } }\n );\n\n if (result.modifiedCount === 0) {\n throw new GenericError('ACCESS_KEY_CREATION_FAILED', {\n accessKeyData,\n projectId,\n userId: user.id,\n });\n }\n\n const updatedProject = await getProjectById(projectId);\n\n const newAccessKeyId = updatedProject.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!newAccessKeyId) {\n throw new GenericError('ACCESS_KEY_CREATION_FAILED', {\n accessKeyData,\n projectId,\n userId: user.id,\n });\n }\n\n return newAccessKeyId;\n};\n\nexport const deleteAccessKey = async (\n clientId: string | Types.ObjectId,\n project: Project,\n userId: string | Types.ObjectId\n) => {\n const projectAccess = project.oAuth2Access.find(\n (access) =>\n access.clientId === clientId && String(access.userId) === String(userId)\n );\n\n if (!projectAccess) {\n throw new GenericError('ACCESS_KEY_NOT_FOUND', {\n clientId,\n projectId: project.id,\n });\n }\n\n const result = await ProjectModel.updateOne(\n {\n 'oAuth2Access.clientId': clientId,\n 'oAuth2Access.userId': String(userId),\n },\n { $pull: { oAuth2Access: { clientId } } }\n );\n\n if (result.modifiedCount === 0) {\n throw new GenericError('ACCESS_KEY_DELETION_FAILED', {\n clientId,\n projectId: project.id,\n });\n }\n\n return projectAccess;\n};\n\nexport const refreshAccessKey = async (\n clientId: string | Types.ObjectId,\n projectId: string | Types.ObjectId,\n userId: string | Types.ObjectId\n): Promise<OAuth2Access> => {\n const project = await ProjectModel.findOne({\n _id: projectId,\n 'oAuth2Access.clientId': String(clientId),\n 'oAuth2Access.userId': String(userId),\n });\n\n if (!project) {\n throw new GenericError('PROJECT_NOT_FOUND', {\n clientId,\n projectId,\n userId,\n });\n }\n\n const projectAccess = project.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!projectAccess) {\n throw new GenericError('ACCESS_KEY_NOT_FOUND', {\n clientId,\n projectId: project.id,\n });\n }\n\n const { clientSecret } = generateClientCredentials();\n\n const result = await ProjectModel.updateOne(\n {\n 'oAuth2Access.clientId': String(clientId),\n 'oAuth2Access.userId': String(userId),\n },\n {\n $set: {\n 'oAuth2Access.$.clientId': projectAccess.clientId,\n 'oAuth2Access.$.clientSecret': clientSecret,\n },\n }\n );\n\n if (result.modifiedCount === 0) {\n throw new GenericError('ACCESS_KEY_UPDATE_FAILED', {\n clientId,\n projectId,\n });\n }\n\n const updatedProject = await getProjectById(projectId);\n\n const newAccessKeyId = updatedProject.oAuth2Access.find(\n (access) => access.clientId === projectAccess.clientId\n );\n\n if (!newAccessKeyId) {\n throw new GenericError('ACCESS_KEY_CREATION_FAILED', {\n accessKeyData: updatedProject.oAuth2Access,\n projectId,\n userId,\n });\n }\n\n return newAccessKeyId;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAM,mCAAmC;CACvC,UAAU,YAAY,EAAE,EAAE,SAAS,KAAK;CACxC,cAAc,YAAY,EAAE,EAAE,SAAS,KAAK;AAC9C;;;;;;;;;;AAWA,MAAa,kBAAkB,OAC7B,eACA,WACA,SAC0B;CAC1B,MAAM,EAAE,UAAU,iBAAiB,0BAA0B;CAE7D,MAAM,eAAiC;EACrC,GAAG;EACH;EACA;EACA,QAAQ,KAAK;EACb,aAAa,CAAC;EACd,QAAQ,cAAc;CACxB;CAOA,KAAI,MALiB,aAAa,UAChC,EAAE,KAAK,UAAU,GACjB,EAAE,OAAO,EAAE,cAAc,aAAa,EAAE,CAC1C,GAEW,kBAAkB,GAC3B,MAAM,IAAI,aAAa,8BAA8B;EACnD;EACA;EACA,QAAQ,KAAK;CACf,CAAC;CAKH,MAAM,kBAAiB,MAFM,eAAe,SAAS,GAEf,aAAa,MAChD,WAAW,OAAO,aAAa,QAClC;CAEA,IAAI,CAAC,gBACH,MAAM,IAAI,aAAa,8BAA8B;EACnD;EACA;EACA,QAAQ,KAAK;CACf,CAAC;CAGH,OAAO;AACT;AAEA,MAAa,kBAAkB,OAC7B,UACA,SACA,WACG;CACH,MAAM,gBAAgB,QAAQ,aAAa,MACxC,WACC,OAAO,aAAa,YAAY,OAAO,OAAO,MAAM,MAAM,OAAO,MAAM,CAC3E;CAEA,IAAI,CAAC,eACH,MAAM,IAAI,aAAa,wBAAwB;EAC7C;EACA,WAAW,QAAQ;CACrB,CAAC;CAWH,KAAI,MARiB,aAAa,UAChC;EACE,yBAAyB;EACzB,uBAAuB,OAAO,MAAM;CACtC,GACA,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,EAAE,CAC1C,GAEW,kBAAkB,GAC3B,MAAM,IAAI,aAAa,8BAA8B;EACnD;EACA,WAAW,QAAQ;CACrB,CAAC;CAGH,OAAO;AACT;AAEA,MAAa,mBAAmB,OAC9B,UACA,WACA,WAC0B;CAC1B,MAAM,UAAU,MAAM,aAAa,QAAQ;EACzC,KAAK;EACL,yBAAyB,OAAO,QAAQ;EACxC,uBAAuB,OAAO,MAAM;CACtC,CAAC;CAED,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,qBAAqB;EAC1C;EACA;EACA;CACF,CAAC;CAGH,MAAM,gBAAgB,QAAQ,aAAa,MACxC,WAAW,OAAO,aAAa,QAClC;CAEA,IAAI,CAAC,eACH,MAAM,IAAI,aAAa,wBAAwB;EAC7C;EACA,WAAW,QAAQ;CACrB,CAAC;CAGH,MAAM,EAAE,iBAAiB,0BAA0B;CAenD,KAAI,MAbiB,aAAa,UAChC;EACE,yBAAyB,OAAO,QAAQ;EACxC,uBAAuB,OAAO,MAAM;CACtC,GACA,EACE,MAAM;EACJ,2BAA2B,cAAc;EACzC,+BAA+B;CACjC,EACF,CACF,GAEW,kBAAkB,GAC3B,MAAM,IAAI,aAAa,4BAA4B;EACjD;EACA;CACF,CAAC;CAGH,MAAM,iBAAiB,MAAM,eAAe,SAAS;CAErD,MAAM,iBAAiB,eAAe,aAAa,MAChD,WAAW,OAAO,aAAa,cAAc,QAChD;CAEA,IAAI,CAAC,gBACH,MAAM,IAAI,aAAa,8BAA8B;EACnD,eAAe,eAAe;EAC9B;EACA;CACF,CAAC;CAGH,OAAO;AACT"}
1
+ {"version":3,"file":"projectAccessKey.service.mjs","names":[],"sources":["../../../src/services/projectAccessKey.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { ProjectModel } from '@schemas/project.schema';\nimport { GenericError } from '@utils/errors';\nimport type { Types } from 'mongoose';\nimport type {\n AccessKeyData,\n OAuth2Access,\n OAuth2AccessData,\n Project,\n} from '@/types/project.types';\nimport type { User } from '@/types/user.types';\nimport { getProjectById } from './project.service';\n\n/**\n * Generates cryptographically secure OAuth2 client credentials\n *\n * @returns Object containing clientId and clientSecret\n *\n * Security improvements:\n * - clientId: 32 characters (128 bits of entropy)\n * - clientSecret: 64 characters (256 bits of entropy)\n * - Uses crypto.randomBytes for cryptographically secure random generation\n * - Follows OAuth2 best practices for credential strength\n */\nconst generateClientCredentials = () => ({\n clientId: randomBytes(16).toString('hex'), // 32 character hexadecimal string\n clientSecret: randomBytes(32).toString('hex'), // 64 character hexadecimal string\n});\n\n/**\n * Adds a new access key to a project.\n *\n * @param accessKeyData - The access key data.\n * @param projectId - The ID of the project to add the access key to.\n * @param user - The user adding the access key.\n * @returns The new access key.\n *\n */\nexport const addNewAccessKey = async (\n accessKeyData: AccessKeyData,\n projectId: string | Types.ObjectId,\n user: User\n): Promise<OAuth2Access> => {\n const { clientId, clientSecret } = generateClientCredentials();\n\n const newAccessKey: OAuth2AccessData = {\n ...accessKeyData,\n clientId,\n clientSecret,\n userId: user.id,\n accessToken: [],\n grants: accessKeyData.grants,\n };\n\n const result = await ProjectModel.updateOne(\n { _id: projectId },\n { $push: { oAuth2Access: newAccessKey } }\n );\n\n if (result.modifiedCount === 0) {\n throw new GenericError('ACCESS_KEY_CREATION_FAILED', {\n accessKeyData,\n projectId,\n userId: user.id,\n });\n }\n\n const updatedProject = await getProjectById(projectId);\n\n const newAccessKeyId = updatedProject.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!newAccessKeyId) {\n throw new GenericError('ACCESS_KEY_CREATION_FAILED', {\n accessKeyData,\n projectId,\n userId: user.id,\n });\n }\n\n return newAccessKeyId;\n};\n\nexport const deleteAccessKey = async (\n clientId: string | Types.ObjectId,\n project: Project,\n userId: string | Types.ObjectId\n) => {\n const projectAccess = project.oAuth2Access.find(\n (access) =>\n access.clientId === clientId && String(access.userId) === String(userId)\n );\n\n if (!projectAccess) {\n throw new GenericError('ACCESS_KEY_NOT_FOUND', {\n clientId,\n projectId: project.id,\n });\n }\n\n const result = await ProjectModel.updateOne(\n {\n 'oAuth2Access.clientId': clientId,\n 'oAuth2Access.userId': String(userId),\n },\n { $pull: { oAuth2Access: { clientId } } }\n );\n\n if (result.modifiedCount === 0) {\n throw new GenericError('ACCESS_KEY_DELETION_FAILED', {\n clientId,\n projectId: project.id,\n });\n }\n\n return projectAccess;\n};\n\nexport const refreshAccessKey = async (\n clientId: string | Types.ObjectId,\n projectId: string | Types.ObjectId,\n userId: string | Types.ObjectId\n): Promise<OAuth2Access> => {\n const project = await ProjectModel.findOne({\n _id: projectId,\n 'oAuth2Access.clientId': String(clientId),\n 'oAuth2Access.userId': String(userId),\n });\n\n if (!project) {\n throw new GenericError('PROJECT_NOT_FOUND', {\n clientId,\n projectId,\n userId,\n });\n }\n\n const projectAccess = project.oAuth2Access.find(\n (access) => access.clientId === clientId\n );\n\n if (!projectAccess) {\n throw new GenericError('ACCESS_KEY_NOT_FOUND', {\n clientId,\n projectId: project.id,\n });\n }\n\n const { clientSecret } = generateClientCredentials();\n\n const result = await ProjectModel.updateOne(\n {\n 'oAuth2Access.clientId': String(clientId),\n 'oAuth2Access.userId': String(userId),\n },\n {\n $set: {\n 'oAuth2Access.$.clientId': projectAccess.clientId,\n 'oAuth2Access.$.clientSecret': clientSecret,\n },\n }\n );\n\n if (result.modifiedCount === 0) {\n throw new GenericError('ACCESS_KEY_UPDATE_FAILED', {\n clientId,\n projectId,\n });\n }\n\n const updatedProject = await getProjectById(projectId);\n\n const newAccessKeyId = updatedProject.oAuth2Access.find(\n (access) => access.clientId === projectAccess.clientId\n );\n\n if (!newAccessKeyId) {\n throw new GenericError('ACCESS_KEY_CREATION_FAILED', {\n accessKeyData: updatedProject.oAuth2Access,\n projectId,\n userId,\n });\n }\n\n return newAccessKeyId;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAM,mCAAmC;CACvC,UAAU,YAAY,EAAE,EAAE,SAAS,KAAK;CACxC,cAAc,YAAY,EAAE,EAAE,SAAS,KAAK;AAC9C;;;;;;;;;;AAWA,MAAa,kBAAkB,OAC7B,eACA,WACA,SAC0B;CAC1B,MAAM,EAAE,UAAU,iBAAiB,0BAA0B;CAE7D,MAAM,eAAiC;EACrC,GAAG;EACH;EACA;EACA,QAAQ,KAAK;EACb,aAAa,CAAC;EACd,QAAQ,cAAc;CACxB;CAOA,KAAI,MALiB,aAAa,UAChC,EAAE,KAAK,UAAU,GACjB,EAAE,OAAO,EAAE,cAAc,aAAa,EAAE,CAC1C,GAEW,kBAAkB,GAC3B,MAAM,IAAI,aAAa,8BAA8B;EACnD;EACA;EACA,QAAQ,KAAK;CACf,CAAC;CAKH,MAAM,kBAAiB,MAFM,eAAe,SAAS,GAEf,aAAa,MAChD,WAAW,OAAO,aAAa,QAClC;CAEA,IAAI,CAAC,gBACH,MAAM,IAAI,aAAa,8BAA8B;EACnD;EACA;EACA,QAAQ,KAAK;CACf,CAAC;CAGH,OAAO;AACT;AAEA,MAAa,kBAAkB,OAC7B,UACA,SACA,WACG;CACH,MAAM,gBAAgB,QAAQ,aAAa,MACxC,WACC,OAAO,aAAa,YAAY,OAAO,OAAO,MAAM,MAAM,OAAO,MAAM,CAC3E;CAEA,IAAI,CAAC,eACH,MAAM,IAAI,aAAa,wBAAwB;EAC7C;EACA,WAAW,QAAQ;CACrB,CAAC;CAWH,KAAI,MARiB,aAAa,UAChC;EACE,yBAAyB;EACzB,uBAAuB,OAAO,MAAM;CACtC,GACA,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,EAAE,CAC1C,GAEW,kBAAkB,GAC3B,MAAM,IAAI,aAAa,8BAA8B;EACnD;EACA,WAAW,QAAQ;CACrB,CAAC;CAGH,OAAO;AACT;AAEA,MAAa,mBAAmB,OAC9B,UACA,WACA,WAC0B;CAC1B,MAAM,UAAU,MAAM,aAAa,QAAQ;EACzC,KAAK;EACL,yBAAyB,OAAO,QAAQ;EACxC,uBAAuB,OAAO,MAAM;CACtC,CAAC;CAED,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,qBAAqB;EAC1C;EACA;EACA;CACF,CAAC;CAGH,MAAM,gBAAgB,QAAQ,aAAa,MACxC,WAAW,OAAO,aAAa,QAClC;CAEA,IAAI,CAAC,eACH,MAAM,IAAI,aAAa,wBAAwB;EAC7C;EACA,WAAW,QAAQ;CACrB,CAAC;CAGH,MAAM,EAAE,iBAAiB,0BAA0B;CAenD,KAAI,MAbiB,aAAa,UAChC;EACE,yBAAyB,OAAO,QAAQ;EACxC,uBAAuB,OAAO,MAAM;CACtC,GACA,EACE,MAAM;EACJ,2BAA2B,cAAc;EACzC,+BAA+B;CACjC,EACF,CACF,GAEW,kBAAkB,GAC3B,MAAM,IAAI,aAAa,4BAA4B;EACjD;EACA;CACF,CAAC;CAGH,MAAM,iBAAiB,MAAM,eAAe,SAAS;CAErD,MAAM,iBAAiB,eAAe,aAAa,MAChD,WAAW,OAAO,aAAa,cAAc,QAChD;CAEA,IAAI,CAAC,gBACH,MAAM,IAAI,aAAa,8BAA8B;EACnD,eAAe,eAAe;EAC9B;EACA;CACF,CAAC;CAGH,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  import { GenericError } from "../../utils/errors/ErrorsClass.mjs";
2
- import { ShowcaseProjectModel } from "../../models/showcaseProject.model.mjs";
2
+ import { ShowcaseProjectModel } from "../../schemas/showcaseProject.schema.mjs";
3
3
 
4
4
  //#region src/services/showcase/showcaseProject.service.ts
5
5
  const findShowcaseProjects = async (filters) => {
@@ -1 +1 @@
1
- {"version":3,"file":"showcaseProject.service.mjs","names":[],"sources":["../../../../src/services/showcase/showcaseProject.service.ts"],"sourcesContent":["import { ShowcaseProjectModel } from '@models/showcaseProject.model';\nimport { GenericError } from '@utils/errors';\nimport type {\n ShowcaseProjectData,\n ShowcaseProjectDocument,\n ShowcaseProjectStatus,\n} from '@/types/showcaseProject.types';\n\nexport const findShowcaseProjects = async (filters: {\n search?: string;\n selectedUseCases?: string[];\n isOpenSource?: boolean;\n page?: number;\n pageSize?: number;\n}): Promise<{\n data: ShowcaseProjectDocument[];\n total_items: number;\n total_pages: number;\n}> => {\n const {\n search,\n selectedUseCases,\n isOpenSource,\n page = 1,\n pageSize = 20,\n } = filters;\n\n const query: Record<string, unknown> = {};\n\n if (isOpenSource) {\n query.githubUrl = { $exists: true, $ne: null };\n }\n if (selectedUseCases && selectedUseCases.length > 0) {\n query.tags = { $in: selectedUseCases };\n }\n if (search?.trim()) {\n query.$or = [\n { title: { $regex: search, $options: 'i' } },\n { description: { $regex: search, $options: 'i' } },\n { tags: { $regex: search, $options: 'i' } },\n ];\n }\n\n const total_items = await ShowcaseProjectModel.countDocuments(query);\n const total_pages = Math.ceil(total_items / pageSize) || 1;\n\n const data = await ShowcaseProjectModel.aggregate([\n { $match: query },\n {\n $addFields: {\n score: {\n $subtract: [\n { $size: { $ifNull: ['$upvoters', []] } },\n { $size: { $ifNull: ['$downvoters', []] } },\n ],\n },\n },\n },\n { $sort: { score: -1, createdAt: -1 } },\n { $skip: (page - 1) * pageSize },\n { $limit: pageSize },\n ]);\n\n return {\n data: data as unknown as ShowcaseProjectDocument[],\n total_items,\n total_pages,\n };\n};\n\nexport const findShowcaseProjectById = async (\n projectId: string\n): Promise<ShowcaseProjectDocument> => {\n const project = await ShowcaseProjectModel.findById(projectId).lean();\n\n if (!project) {\n throw new GenericError('SHOWCASE_PROJECT_NOT_FOUND', { projectId });\n }\n\n return project as unknown as ShowcaseProjectDocument;\n};\n\n/**\n * Finds an existing project whose websiteUrl shares the same hostname as the\n * given URL. This treats `example.com` and `example.com/path` as duplicates\n * while allowing `sub.example.com` as a distinct entry.\n */\nexport const findShowcaseProjectByUrl = async (\n websiteUrl: string\n): Promise<ShowcaseProjectDocument | null> => {\n let hostname: string;\n try {\n hostname = new URL(websiteUrl).hostname;\n } catch {\n // Fallback to exact match if URL is unparseable\n const project = await ShowcaseProjectModel.findOne({\n websiteUrl: String(websiteUrl),\n }).lean();\n return project as unknown as ShowcaseProjectDocument | null;\n }\n\n // Match any stored URL whose authority (scheme + hostname) equals this hostname.\n // Anchored after the scheme so sub.example.com does NOT match example.com.\n const hostnameRegex = new RegExp(\n `^https?://${hostname.replace(/\\./g, '\\\\.')}(/|$)`,\n 'i'\n );\n const project = await ShowcaseProjectModel.findOne({\n websiteUrl: { $regex: hostnameRegex },\n }).lean();\n return project as unknown as ShowcaseProjectDocument | null;\n};\n\nexport const findOtherShowcaseProjects = async (\n excludeId: string,\n limit = 4\n): Promise<ShowcaseProjectDocument[]> => {\n const projects = await ShowcaseProjectModel.find({\n _id: { $ne: excludeId },\n })\n .limit(limit)\n .lean();\n\n return projects as unknown as ShowcaseProjectDocument[];\n};\n\nexport const createShowcaseProject = async (\n projectData: ShowcaseProjectData\n): Promise<ShowcaseProjectDocument> => {\n const newProject = new ShowcaseProjectModel({\n ...projectData,\n ...projectData,\n upvoters: [],\n downvoters: [],\n });\n\n await newProject.save();\n return newProject as unknown as ShowcaseProjectDocument;\n};\n\nexport type UpdateShowcaseProjectScanData = {\n title?: string;\n description?: string;\n websiteUrl?: string;\n githubUrl?: string | null;\n tags?: string[];\n intlayerVersion?: string;\n libsUsed?: string[];\n packageDetails?: Record<string, string>;\n scanDetails?: ShowcaseProjectData['scanDetails'];\n imageUrl?: string;\n isOpenSource?: boolean;\n status?: ShowcaseProjectStatus;\n lastScanDate?: Date;\n};\n\nexport const updateShowcaseProject = async (\n projectId: string,\n updates: UpdateShowcaseProjectScanData\n): Promise<ShowcaseProjectDocument> => {\n const project = await ShowcaseProjectModel.findByIdAndUpdate(\n projectId,\n { $set: updates },\n { new: true }\n ).lean();\n\n if (!project) {\n throw new GenericError('SHOWCASE_PROJECT_NOT_FOUND', { projectId });\n }\n\n return project as unknown as ShowcaseProjectDocument;\n};\n\nexport const deleteShowcaseProject = async (\n projectId: string\n): Promise<void> => {\n await ShowcaseProjectModel.findByIdAndDelete(projectId);\n};\n\ntype VoteResult = {\n upvotes: number;\n isUpVoted: boolean;\n downvotes: number;\n isDownVoted: boolean;\n};\n\nconst toggleShowcaseVote = async (\n projectId: string,\n userId: string,\n voteType: 'up' | 'down'\n): Promise<VoteResult> => {\n const project = await ShowcaseProjectModel.findById(projectId);\n\n if (!project) {\n throw new GenericError('SHOWCASE_PROJECT_NOT_FOUND', { projectId });\n }\n\n const upvoters: string[] = project.upvoters || [];\n const downvoters: string[] = project.downvoters || [];\n\n if (voteType === 'up') {\n const wasUpvoted = upvoters.includes(userId);\n\n if (wasUpvoted) {\n project.upvoters = upvoters.filter((id) => id !== userId);\n } else {\n project.upvoters.push(userId);\n if (downvoters.includes(userId)) {\n project.downvoters = downvoters.filter((id) => id !== userId);\n }\n }\n\n await project.save();\n\n return {\n upvotes: project.upvoters.length,\n isUpVoted: !wasUpvoted,\n downvotes: project.downvoters.length,\n isDownVoted: false,\n };\n } else {\n const wasDownvoted = downvoters.includes(userId);\n\n if (wasDownvoted) {\n project.downvoters = downvoters.filter((id) => id !== userId);\n } else {\n project.downvoters.push(userId);\n\n if (upvoters.includes(userId)) {\n project.upvoters = upvoters.filter((id) => id !== userId);\n }\n }\n await project.save();\n return {\n upvotes: project.upvoters.length,\n isUpVoted: false,\n downvotes: project.downvoters.length,\n isDownVoted: !wasDownvoted,\n };\n }\n};\n\nexport const toggleShowcaseUpvote = (\n projectId: string,\n userId: string\n): Promise<VoteResult> => toggleShowcaseVote(projectId, userId, 'up');\n\nexport const toggleShowcaseDownvote = (\n projectId: string,\n userId: string\n): Promise<VoteResult> => toggleShowcaseVote(projectId, userId, 'down');\n"],"mappings":";;;;AAQA,MAAa,uBAAuB,OAAO,YAUrC;CACJ,MAAM,EACJ,QACA,kBACA,cACA,OAAO,GACP,WAAW,OACT;CAEJ,MAAM,QAAiC,CAAC;CAExC,IAAI,cACF,MAAM,YAAY;EAAE,SAAS;EAAM,KAAK;CAAK;CAE/C,IAAI,oBAAoB,iBAAiB,SAAS,GAChD,MAAM,OAAO,EAAE,KAAK,iBAAiB;CAEvC,IAAI,QAAQ,KAAK,GACf,MAAM,MAAM;EACV,EAAE,OAAO;GAAE,QAAQ;GAAQ,UAAU;EAAI,EAAE;EAC3C,EAAE,aAAa;GAAE,QAAQ;GAAQ,UAAU;EAAI,EAAE;EACjD,EAAE,MAAM;GAAE,QAAQ;GAAQ,UAAU;EAAI,EAAE;CAC5C;CAGF,MAAM,cAAc,MAAM,qBAAqB,eAAe,KAAK;CACnE,MAAM,cAAc,KAAK,KAAK,cAAc,QAAQ,KAAK;CAmBzD,OAAO;EACL,MAAM,MAlBW,qBAAqB,UAAU;GAChD,EAAE,QAAQ,MAAM;GAChB,EACE,YAAY,EACV,OAAO,EACL,WAAW,CACT,EAAE,OAAO,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,GACxC,EAAE,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC,EAAE,EAAE,CAC5C,EACF,EACF,EACF;GACA,EAAE,OAAO;IAAE,OAAO;IAAI,WAAW;GAAG,EAAE;GACtC,EAAE,QAAQ,OAAO,KAAK,SAAS;GAC/B,EAAE,QAAQ,SAAS;EACrB,CAAC;EAIC;EACA;CACF;AACF;AAEA,MAAa,0BAA0B,OACrC,cACqC;CACrC,MAAM,UAAU,MAAM,qBAAqB,SAAS,SAAS,EAAE,KAAK;CAEpE,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,8BAA8B,EAAE,UAAU,CAAC;CAGpE,OAAO;AACT;;;;;;AAOA,MAAa,2BAA2B,OACtC,eAC4C;CAC5C,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,UAAU,EAAE;CACjC,QAAQ;EAKN,OAAO,MAHe,qBAAqB,QAAQ,EACjD,YAAY,OAAO,UAAU,EAC/B,CAAC,EAAE,KAAK;CAEV;CAIA,MAAM,gBAAgB,IAAI,OACxB,aAAa,SAAS,QAAQ,OAAO,KAAK,EAAE,QAC5C,GACF;CAIA,OAAO,MAHe,qBAAqB,QAAQ,EACjD,YAAY,EAAE,QAAQ,cAAc,EACtC,CAAC,EAAE,KAAK;AAEV;AAEA,MAAa,4BAA4B,OACvC,WACA,QAAQ,MAC+B;CAOvC,OAAO,MANgB,qBAAqB,KAAK,EAC/C,KAAK,EAAE,KAAK,UAAU,EACxB,CAAC,EACE,MAAM,KAAK,EACX,KAAK;AAGV;AAEA,MAAa,wBAAwB,OACnC,gBACqC;CACrC,MAAM,aAAa,IAAI,qBAAqB;EAC1C,GAAG;EACH,GAAG;EACH,UAAU,CAAC;EACX,YAAY,CAAC;CACf,CAAC;CAED,MAAM,WAAW,KAAK;CACtB,OAAO;AACT;AAkBA,MAAa,wBAAwB,OACnC,WACA,YACqC;CACrC,MAAM,UAAU,MAAM,qBAAqB,kBACzC,WACA,EAAE,MAAM,QAAQ,GAChB,EAAE,KAAK,KAAK,CACd,EAAE,KAAK;CAEP,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,8BAA8B,EAAE,UAAU,CAAC;CAGpE,OAAO;AACT;AAEA,MAAa,wBAAwB,OACnC,cACkB;CAClB,MAAM,qBAAqB,kBAAkB,SAAS;AACxD;AASA,MAAM,qBAAqB,OACzB,WACA,QACA,aACwB;CACxB,MAAM,UAAU,MAAM,qBAAqB,SAAS,SAAS;CAE7D,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,8BAA8B,EAAE,UAAU,CAAC;CAGpE,MAAM,WAAqB,QAAQ,YAAY,CAAC;CAChD,MAAM,aAAuB,QAAQ,cAAc,CAAC;CAEpD,IAAI,aAAa,MAAM;EACrB,MAAM,aAAa,SAAS,SAAS,MAAM;EAE3C,IAAI,YACF,QAAQ,WAAW,SAAS,QAAQ,OAAO,OAAO,MAAM;OACnD;GACL,QAAQ,SAAS,KAAK,MAAM;GAC5B,IAAI,WAAW,SAAS,MAAM,GAC5B,QAAQ,aAAa,WAAW,QAAQ,OAAO,OAAO,MAAM;EAEhE;EAEA,MAAM,QAAQ,KAAK;EAEnB,OAAO;GACL,SAAS,QAAQ,SAAS;GAC1B,WAAW,CAAC;GACZ,WAAW,QAAQ,WAAW;GAC9B,aAAa;EACf;CACF,OAAO;EACL,MAAM,eAAe,WAAW,SAAS,MAAM;EAE/C,IAAI,cACF,QAAQ,aAAa,WAAW,QAAQ,OAAO,OAAO,MAAM;OACvD;GACL,QAAQ,WAAW,KAAK,MAAM;GAE9B,IAAI,SAAS,SAAS,MAAM,GAC1B,QAAQ,WAAW,SAAS,QAAQ,OAAO,OAAO,MAAM;EAE5D;EACA,MAAM,QAAQ,KAAK;EACnB,OAAO;GACL,SAAS,QAAQ,SAAS;GAC1B,WAAW;GACX,WAAW,QAAQ,WAAW;GAC9B,aAAa,CAAC;EAChB;CACF;AACF;AAEA,MAAa,wBACX,WACA,WACwB,mBAAmB,WAAW,QAAQ,IAAI;AAEpE,MAAa,0BACX,WACA,WACwB,mBAAmB,WAAW,QAAQ,MAAM"}
1
+ {"version":3,"file":"showcaseProject.service.mjs","names":[],"sources":["../../../../src/services/showcase/showcaseProject.service.ts"],"sourcesContent":["import { ShowcaseProjectModel } from '@schemas/showcaseProject.schema';\nimport { GenericError } from '@utils/errors';\nimport type {\n ShowcaseProjectData,\n ShowcaseProjectDocument,\n ShowcaseProjectStatus,\n} from '@/types/showcaseProject.types';\n\nexport const findShowcaseProjects = async (filters: {\n search?: string;\n selectedUseCases?: string[];\n isOpenSource?: boolean;\n page?: number;\n pageSize?: number;\n}): Promise<{\n data: ShowcaseProjectDocument[];\n total_items: number;\n total_pages: number;\n}> => {\n const {\n search,\n selectedUseCases,\n isOpenSource,\n page = 1,\n pageSize = 20,\n } = filters;\n\n const query: Record<string, unknown> = {};\n\n if (isOpenSource) {\n query.githubUrl = { $exists: true, $ne: null };\n }\n if (selectedUseCases && selectedUseCases.length > 0) {\n query.tags = { $in: selectedUseCases };\n }\n if (search?.trim()) {\n query.$or = [\n { title: { $regex: search, $options: 'i' } },\n { description: { $regex: search, $options: 'i' } },\n { tags: { $regex: search, $options: 'i' } },\n ];\n }\n\n const total_items = await ShowcaseProjectModel.countDocuments(query);\n const total_pages = Math.ceil(total_items / pageSize) || 1;\n\n const data = await ShowcaseProjectModel.aggregate([\n { $match: query },\n {\n $addFields: {\n score: {\n $subtract: [\n { $size: { $ifNull: ['$upvoters', []] } },\n { $size: { $ifNull: ['$downvoters', []] } },\n ],\n },\n },\n },\n { $sort: { score: -1, createdAt: -1 } },\n { $skip: (page - 1) * pageSize },\n { $limit: pageSize },\n ]);\n\n return {\n data: data as unknown as ShowcaseProjectDocument[],\n total_items,\n total_pages,\n };\n};\n\nexport const findShowcaseProjectById = async (\n projectId: string\n): Promise<ShowcaseProjectDocument> => {\n const project = await ShowcaseProjectModel.findById(projectId).lean();\n\n if (!project) {\n throw new GenericError('SHOWCASE_PROJECT_NOT_FOUND', { projectId });\n }\n\n return project as unknown as ShowcaseProjectDocument;\n};\n\n/**\n * Finds an existing project whose websiteUrl shares the same hostname as the\n * given URL. This treats `example.com` and `example.com/path` as duplicates\n * while allowing `sub.example.com` as a distinct entry.\n */\nexport const findShowcaseProjectByUrl = async (\n websiteUrl: string\n): Promise<ShowcaseProjectDocument | null> => {\n let hostname: string;\n try {\n hostname = new URL(websiteUrl).hostname;\n } catch {\n // Fallback to exact match if URL is unparseable\n const project = await ShowcaseProjectModel.findOne({\n websiteUrl: String(websiteUrl),\n }).lean();\n return project as unknown as ShowcaseProjectDocument | null;\n }\n\n // Match any stored URL whose authority (scheme + hostname) equals this hostname.\n // Anchored after the scheme so sub.example.com does NOT match example.com.\n const hostnameRegex = new RegExp(\n `^https?://${hostname.replace(/\\./g, '\\\\.')}(/|$)`,\n 'i'\n );\n const project = await ShowcaseProjectModel.findOne({\n websiteUrl: { $regex: hostnameRegex },\n }).lean();\n return project as unknown as ShowcaseProjectDocument | null;\n};\n\nexport const findOtherShowcaseProjects = async (\n excludeId: string,\n limit = 4\n): Promise<ShowcaseProjectDocument[]> => {\n const projects = await ShowcaseProjectModel.find({\n _id: { $ne: excludeId },\n })\n .limit(limit)\n .lean();\n\n return projects as unknown as ShowcaseProjectDocument[];\n};\n\nexport const createShowcaseProject = async (\n projectData: ShowcaseProjectData\n): Promise<ShowcaseProjectDocument> => {\n const newProject = new ShowcaseProjectModel({\n ...projectData,\n ...projectData,\n upvoters: [],\n downvoters: [],\n });\n\n await newProject.save();\n return newProject as unknown as ShowcaseProjectDocument;\n};\n\nexport type UpdateShowcaseProjectScanData = {\n title?: string;\n description?: string;\n websiteUrl?: string;\n githubUrl?: string | null;\n tags?: string[];\n intlayerVersion?: string;\n libsUsed?: string[];\n packageDetails?: Record<string, string>;\n scanDetails?: ShowcaseProjectData['scanDetails'];\n imageUrl?: string;\n isOpenSource?: boolean;\n status?: ShowcaseProjectStatus;\n lastScanDate?: Date;\n};\n\nexport const updateShowcaseProject = async (\n projectId: string,\n updates: UpdateShowcaseProjectScanData\n): Promise<ShowcaseProjectDocument> => {\n const project = await ShowcaseProjectModel.findByIdAndUpdate(\n projectId,\n { $set: updates },\n { new: true }\n ).lean();\n\n if (!project) {\n throw new GenericError('SHOWCASE_PROJECT_NOT_FOUND', { projectId });\n }\n\n return project as unknown as ShowcaseProjectDocument;\n};\n\nexport const deleteShowcaseProject = async (\n projectId: string\n): Promise<void> => {\n await ShowcaseProjectModel.findByIdAndDelete(projectId);\n};\n\ntype VoteResult = {\n upvotes: number;\n isUpVoted: boolean;\n downvotes: number;\n isDownVoted: boolean;\n};\n\nconst toggleShowcaseVote = async (\n projectId: string,\n userId: string,\n voteType: 'up' | 'down'\n): Promise<VoteResult> => {\n const project = await ShowcaseProjectModel.findById(projectId);\n\n if (!project) {\n throw new GenericError('SHOWCASE_PROJECT_NOT_FOUND', { projectId });\n }\n\n const upvoters: string[] = project.upvoters || [];\n const downvoters: string[] = project.downvoters || [];\n\n if (voteType === 'up') {\n const wasUpvoted = upvoters.includes(userId);\n\n if (wasUpvoted) {\n project.upvoters = upvoters.filter((id) => id !== userId);\n } else {\n project.upvoters.push(userId);\n if (downvoters.includes(userId)) {\n project.downvoters = downvoters.filter((id) => id !== userId);\n }\n }\n\n await project.save();\n\n return {\n upvotes: project.upvoters.length,\n isUpVoted: !wasUpvoted,\n downvotes: project.downvoters.length,\n isDownVoted: false,\n };\n } else {\n const wasDownvoted = downvoters.includes(userId);\n\n if (wasDownvoted) {\n project.downvoters = downvoters.filter((id) => id !== userId);\n } else {\n project.downvoters.push(userId);\n\n if (upvoters.includes(userId)) {\n project.upvoters = upvoters.filter((id) => id !== userId);\n }\n }\n await project.save();\n return {\n upvotes: project.upvoters.length,\n isUpVoted: false,\n downvotes: project.downvoters.length,\n isDownVoted: !wasDownvoted,\n };\n }\n};\n\nexport const toggleShowcaseUpvote = (\n projectId: string,\n userId: string\n): Promise<VoteResult> => toggleShowcaseVote(projectId, userId, 'up');\n\nexport const toggleShowcaseDownvote = (\n projectId: string,\n userId: string\n): Promise<VoteResult> => toggleShowcaseVote(projectId, userId, 'down');\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { GenericError } from "../utils/errors/ErrorsClass.mjs";
2
2
  import { validateTag } from "../utils/validation/validateTag.mjs";
3
- import { TagModel } from "../models/tag.model.mjs";
3
+ import { TagModel } from "../schemas/tag.schema.mjs";
4
4
 
5
5
  //#region src/services/tag.service.ts
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tag.service.mjs","names":[],"sources":["../../../src/services/tag.service.ts"],"sourcesContent":["import { TagModel } from '@models/tag.model';\nimport { GenericError } from '@utils/errors';\nimport type { TagFilters } from '@utils/filtersAndPagination/getTagFiltersAndPagination';\nimport { type TagFields, validateTag } from '@utils/validation/validateTag';\nimport type { Types } from 'mongoose';\nimport type { Organization } from '@/types/organization.types';\nimport type { Tag, TagData, TagDocument } from '@/types/tag.types';\n\n/**\n * Finds tags based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of tags matching the filters.\n */\nexport const findTags = async (\n filters: TagFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>\n): Promise<TagDocument[]> => {\n let query = TagModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Finds a tag by its ID.\n * @param tagId - The ID of the tag to find.\n * @returns The tag matching the ID.\n */\nexport const getTagById = async (\n tagId: string | Types.ObjectId\n): Promise<TagDocument> => {\n const tag = await TagModel.findById(tagId);\n\n if (!tag) {\n throw new GenericError('TAG_NOT_FOUND', { tagId });\n }\n\n return tag;\n};\n\nexport const getTagsByKeys = async (\n keys: string[],\n organizationId: string | Organization['id']\n): Promise<TagDocument[]> => {\n const tags = await TagModel.find({ key: { $in: keys }, organizationId });\n\n return tags;\n};\n\n/**\n * Counts the total number of tags that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of tags.\n */\nexport const countTags = async (filters: TagFilters): Promise<number> => {\n const result = await TagModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('TAG_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new tag in the database.\n * @param tag - The tag data to create.\n * @returns The created tag.\n */\nexport const createTag = async (tag: TagData): Promise<TagDocument> => {\n const errors = await validateTag(tag, ['key']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('TAG_INVALID_FIELDS', { errors });\n }\n\n return await TagModel.create(tag);\n};\n\n/**\n * Updates an existing tag in the database by its ID.\n * @param tagId - The ID of the tag to update.\n * @param tag - The updated tag data.\n * @returns The updated tag.\n */\nexport const updateTagById = async (\n tagId: string | Types.ObjectId,\n tag: Partial<Tag>\n): Promise<TagDocument> => {\n const updatedKeys = Object.keys(tag) as TagFields;\n\n const errors = validateTag(tag, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('TAG_INVALID_FIELDS', {\n tagId,\n errors,\n });\n }\n\n const result = await TagModel.updateOne({ _id: tagId }, tag);\n\n if (result.matchedCount === 0) {\n throw new GenericError('TAG_UPDATE_FAILED', { tagId });\n }\n\n return await getTagById(tagId);\n};\n\n/**\n * Deletes a tag from the database by its ID.\n * @param tagId - The ID of the tag to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteTagById = async (\n tagId: string | Types.ObjectId\n): Promise<TagDocument> => {\n const tag = await TagModel.findByIdAndDelete(tagId);\n\n if (!tag) {\n throw new GenericError('TAG_NOT_FOUND', { tagId });\n }\n\n return tag;\n};\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,WAAW,OACtB,SACA,OAAO,GACP,QAAQ,KACR,gBAC2B;CAC3B,IAAI,QAAQ,SAAS,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAEzD,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,aAAa,OACxB,UACyB;CACzB,MAAM,MAAM,MAAM,SAAS,SAAS,KAAK;CAEzC,IAAI,CAAC,KACH,MAAM,IAAI,aAAa,iBAAiB,EAAE,MAAM,CAAC;CAGnD,OAAO;AACT;AAEA,MAAa,gBAAgB,OAC3B,MACA,mBAC2B;CAG3B,OAAO,MAFY,SAAS,KAAK;EAAE,KAAK,EAAE,KAAK,KAAK;EAAG;CAAe,CAAC;AAGzE;;;;;;AAOA,MAAa,YAAY,OAAO,YAAyC;CACvE,MAAM,SAAS,MAAM,SAAS,eAAe,OAAO;CAEpD,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,aAAa,oBAAoB,EAAE,QAAQ,CAAC;CAGxD,OAAO;AACT;;;;;;AAOA,MAAa,YAAY,OAAO,QAAuC;CACrE,MAAM,SAAS,MAAM,YAAY,KAAK,CAAC,KAAK,CAAC;CAE7C,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,sBAAsB,EAAE,OAAO,CAAC;CAGzD,OAAO,MAAM,SAAS,OAAO,GAAG;AAClC;;;;;;;AAQA,MAAa,gBAAgB,OAC3B,OACA,QACyB;CAGzB,MAAM,SAAS,YAAY,KAFP,OAAO,KAAK,GAEU,CAAC;CAE3C,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,sBAAsB;EAC3C;EACA;CACF,CAAC;CAKH,KAAI,MAFiB,SAAS,UAAU,EAAE,KAAK,MAAM,GAAG,GAAG,GAEhD,iBAAiB,GAC1B,MAAM,IAAI,aAAa,qBAAqB,EAAE,MAAM,CAAC;CAGvD,OAAO,MAAM,WAAW,KAAK;AAC/B;;;;;;AAOA,MAAa,gBAAgB,OAC3B,UACyB;CACzB,MAAM,MAAM,MAAM,SAAS,kBAAkB,KAAK;CAElD,IAAI,CAAC,KACH,MAAM,IAAI,aAAa,iBAAiB,EAAE,MAAM,CAAC;CAGnD,OAAO;AACT"}
1
+ {"version":3,"file":"tag.service.mjs","names":[],"sources":["../../../src/services/tag.service.ts"],"sourcesContent":["import { TagModel } from '@schemas/tag.schema';\nimport { GenericError } from '@utils/errors';\nimport type { TagFilters } from '@utils/filtersAndPagination/getTagFiltersAndPagination';\nimport { type TagFields, validateTag } from '@utils/validation/validateTag';\nimport type { Types } from 'mongoose';\nimport type { Organization } from '@/types/organization.types';\nimport type { Tag, TagData, TagDocument } from '@/types/tag.types';\n\n/**\n * Finds tags based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of tags matching the filters.\n */\nexport const findTags = async (\n filters: TagFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>\n): Promise<TagDocument[]> => {\n let query = TagModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Finds a tag by its ID.\n * @param tagId - The ID of the tag to find.\n * @returns The tag matching the ID.\n */\nexport const getTagById = async (\n tagId: string | Types.ObjectId\n): Promise<TagDocument> => {\n const tag = await TagModel.findById(tagId);\n\n if (!tag) {\n throw new GenericError('TAG_NOT_FOUND', { tagId });\n }\n\n return tag;\n};\n\nexport const getTagsByKeys = async (\n keys: string[],\n organizationId: string | Organization['id']\n): Promise<TagDocument[]> => {\n const tags = await TagModel.find({ key: { $in: keys }, organizationId });\n\n return tags;\n};\n\n/**\n * Counts the total number of tags that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of tags.\n */\nexport const countTags = async (filters: TagFilters): Promise<number> => {\n const result = await TagModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('TAG_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new tag in the database.\n * @param tag - The tag data to create.\n * @returns The created tag.\n */\nexport const createTag = async (tag: TagData): Promise<TagDocument> => {\n const errors = await validateTag(tag, ['key']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('TAG_INVALID_FIELDS', { errors });\n }\n\n return await TagModel.create(tag);\n};\n\n/**\n * Updates an existing tag in the database by its ID.\n * @param tagId - The ID of the tag to update.\n * @param tag - The updated tag data.\n * @returns The updated tag.\n */\nexport const updateTagById = async (\n tagId: string | Types.ObjectId,\n tag: Partial<Tag>\n): Promise<TagDocument> => {\n const updatedKeys = Object.keys(tag) as TagFields;\n\n const errors = validateTag(tag, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('TAG_INVALID_FIELDS', {\n tagId,\n errors,\n });\n }\n\n const result = await TagModel.updateOne({ _id: tagId }, tag);\n\n if (result.matchedCount === 0) {\n throw new GenericError('TAG_UPDATE_FAILED', { tagId });\n }\n\n return await getTagById(tagId);\n};\n\n/**\n * Deletes a tag from the database by its ID.\n * @param tagId - The ID of the tag to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteTagById = async (\n tagId: string | Types.ObjectId\n): Promise<TagDocument> => {\n const tag = await TagModel.findByIdAndDelete(tagId);\n\n if (!tag) {\n throw new GenericError('TAG_NOT_FOUND', { tagId });\n }\n\n return tag;\n};\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,WAAW,OACtB,SACA,OAAO,GACP,QAAQ,KACR,gBAC2B;CAC3B,IAAI,QAAQ,SAAS,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAEzD,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,aAAa,OACxB,UACyB;CACzB,MAAM,MAAM,MAAM,SAAS,SAAS,KAAK;CAEzC,IAAI,CAAC,KACH,MAAM,IAAI,aAAa,iBAAiB,EAAE,MAAM,CAAC;CAGnD,OAAO;AACT;AAEA,MAAa,gBAAgB,OAC3B,MACA,mBAC2B;CAG3B,OAAO,MAFY,SAAS,KAAK;EAAE,KAAK,EAAE,KAAK,KAAK;EAAG;CAAe,CAAC;AAGzE;;;;;;AAOA,MAAa,YAAY,OAAO,YAAyC;CACvE,MAAM,SAAS,MAAM,SAAS,eAAe,OAAO;CAEpD,IAAI,OAAO,WAAW,aACpB,MAAM,IAAI,aAAa,oBAAoB,EAAE,QAAQ,CAAC;CAGxD,OAAO;AACT;;;;;;AAOA,MAAa,YAAY,OAAO,QAAuC;CACrE,MAAM,SAAS,MAAM,YAAY,KAAK,CAAC,KAAK,CAAC;CAE7C,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,sBAAsB,EAAE,OAAO,CAAC;CAGzD,OAAO,MAAM,SAAS,OAAO,GAAG;AAClC;;;;;;;AAQA,MAAa,gBAAgB,OAC3B,OACA,QACyB;CAGzB,MAAM,SAAS,YAAY,KAFP,OAAO,KAAK,GAEU,CAAC;CAE3C,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,sBAAsB;EAC3C;EACA;CACF,CAAC;CAKH,KAAI,MAFiB,SAAS,UAAU,EAAE,KAAK,MAAM,GAAG,GAAG,GAEhD,iBAAiB,GAC1B,MAAM,IAAI,aAAa,qBAAqB,EAAE,MAAM,CAAC;CAGvD,OAAO,MAAM,WAAW,KAAK;AAC/B;;;;;;AAOA,MAAa,gBAAgB,OAC3B,UACyB;CACzB,MAAM,MAAM,MAAM,SAAS,kBAAkB,KAAK;CAElD,IAAI,CAAC,KACH,MAAM,IAAI,aAAa,iBAAiB,EAAE,MAAM,CAAC;CAGnD,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
1
  import { GenericError } from "../utils/errors/ErrorsClass.mjs";
2
2
  import { validateUser } from "../utils/validation/validateUser.mjs";
3
- import { UserModel } from "../models/user.model.mjs";
3
+ import { UserModel } from "../schemas/user.schema.mjs";
4
4
 
5
5
  //#region src/services/user.service.ts
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"user.service.mjs","names":[],"sources":["../../../src/services/user.service.ts"],"sourcesContent":["import { UserModel } from '@models/user.model';\nimport { GenericError } from '@utils/errors';\nimport type { UserFilters } from '@utils/filtersAndPagination/getUserFiltersAndPagination';\nimport {\n type FieldsToCheck,\n type UserFields,\n validateUser,\n} from '@utils/validation/validateUser';\nimport type { Types } from 'mongoose';\nimport type { User, UserAPI, UserDocument } from '@/types/user.types';\n\n/**\n * Creates a new user with password in the database and hashes the password.\n * @param user - User object with password not hashed.\n * @returns Created user object.\n */\nexport const createUser = async (\n user: Partial<User>\n): Promise<UserDocument> => {\n const fieldsToCheck: FieldsToCheck[] = ['email'];\n\n const errors = validateUser(user, fieldsToCheck);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('USER_INVALID_FIELDS', {\n userEmail: user.email,\n errors,\n });\n }\n\n const newUser: UserDocument = await UserModel.create(user);\n\n if (!newUser) {\n throw new GenericError('USER_CREATION_FAILED', { userEmail: user.email });\n }\n\n return newUser;\n};\n\n/**\n * Retrieves a user by email.\n * @param email - User's email.\n * @returns User object or null if no user was found.\n */\nexport const getUserByEmail = async (\n email: string\n): Promise<UserDocument | null> => {\n return await UserModel.findOne({ email: String(email) });\n};\n\n/**\n * Retrieves users list by email.\n * @param emails - Users email.\n * @returns User object or null if no user was found.\n */\nexport const getUsersByEmails = async (\n emails: string[]\n): Promise<UserDocument[] | null> => {\n return await UserModel.find({ email: { $in: emails } });\n};\n\n/**\n * Checks if a user exists by email.\n * @param email - User's email.\n * @returns True if the user exists, false otherwise.\n */\nexport const checkUserExists = async (email: string): Promise<boolean> => {\n const user = await UserModel.exists({ email });\n return user !== null;\n};\n\n/**\n * Retrieves a user by ID.\n * @param userId - User's ID.\n * @returns User object or null if no user was found.\n */\nexport const getUserById = async (\n userId: string | Types.ObjectId\n): Promise<UserDocument | null> => await UserModel.findById(userId);\n\n/**\n * Retrieves a user by ID.\n * @param userId - User's ID.\n * @returns User object or null if no user was found.\n */\nexport const getUsersByIds = async (\n userIds: (string | Types.ObjectId)[]\n): Promise<UserDocument[] | null> =>\n await UserModel.find({ _id: { $in: userIds } });\n\n/**\n * Finds users based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @param sortOptions - Sorting options.\n * @returns List of users matching the filters.\n */\nexport const findUsers = async (\n filters: UserFilters,\n skip: number,\n limit: number,\n sortOptions?: Record<string, 1 | -1>\n): Promise<UserDocument[]> => {\n let query = UserModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Counts the total number of users that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of users.\n */\nexport const countUsers = async (filters: UserFilters): Promise<number> => {\n const count = await UserModel.countDocuments(filters);\n\n if (typeof count === 'undefined') {\n throw new GenericError('USER_COUNT_FAILED');\n }\n\n return count;\n};\n\n/**\n * Updates a user's information.\n * @param user - The user object.\n * @param updates - The updates to apply to the user.\n * @returns The updated user.\n */\nexport const updateUserById = async (\n userId: string | Types.ObjectId,\n updates: Partial<UserAPI>\n): Promise<UserDocument> => {\n const { id, ...updatesWithoutId } = updates;\n\n const keyToValidate = Object.keys(updatesWithoutId) as UserFields;\n const errors = validateUser(updatesWithoutId, keyToValidate);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('USER_INVALID_FIELDS', {\n userId,\n errors,\n });\n }\n\n const result = await UserModel.updateOne(\n { _id: userId },\n { $set: updatesWithoutId }\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('USER_UPDATE_FAILED', { userId });\n }\n\n const updatedUser = await UserModel.findById(userId);\n\n if (!updatedUser) {\n throw new GenericError('USER_UPDATED_USER_NOT_FOUND', { userId });\n }\n\n return updatedUser;\n};\n\n/**\n * Deletes a user from the database.\n * @param userId - The user object.\n * @returns\n */\nexport const deleteUser = async (\n userId: string | Types.ObjectId\n): Promise<UserDocument> => {\n await getUserById(userId);\n\n const user = await UserModel.findByIdAndDelete(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', { userId });\n }\n\n return user;\n};\n"],"mappings":";;;;;;;;;;AAgBA,MAAa,aAAa,OACxB,SAC0B;CAG1B,MAAM,SAAS,aAAa,MAAM,CAFM,OAEM,CAAC;CAE/C,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,uBAAuB;EAC5C,WAAW,KAAK;EAChB;CACF,CAAC;CAGH,MAAM,UAAwB,MAAM,UAAU,OAAO,IAAI;CAEzD,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,wBAAwB,EAAE,WAAW,KAAK,MAAM,CAAC;CAG1E,OAAO;AACT;;;;;;AAOA,MAAa,iBAAiB,OAC5B,UACiC;CACjC,OAAO,MAAM,UAAU,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AACzD;;;;;;AAOA,MAAa,mBAAmB,OAC9B,WACmC;CACnC,OAAO,MAAM,UAAU,KAAK,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC;AACxD;;;;;;AAOA,MAAa,kBAAkB,OAAO,UAAoC;CAExE,OAAO,MADY,UAAU,OAAO,EAAE,MAAM,CAAC,MAC7B;AAClB;;;;;;AAOA,MAAa,cAAc,OACzB,WACiC,MAAM,UAAU,SAAS,MAAM;;;;;;AAOlE,MAAa,gBAAgB,OAC3B,YAEA,MAAM,UAAU,KAAK,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,CAAC;;;;;;;;;AAUhD,MAAa,YAAY,OACvB,SACA,MACA,OACA,gBAC4B;CAC5B,IAAI,QAAQ,UAAU,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAE1D,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,aAAa,OAAO,YAA0C;CACzE,MAAM,QAAQ,MAAM,UAAU,eAAe,OAAO;CAEpD,IAAI,OAAO,UAAU,aACnB,MAAM,IAAI,aAAa,mBAAmB;CAG5C,OAAO;AACT;;;;;;;AAQA,MAAa,iBAAiB,OAC5B,QACA,YAC0B;CAC1B,MAAM,EAAE,IAAI,GAAG,qBAAqB;CAGpC,MAAM,SAAS,aAAa,kBADN,OAAO,KAAK,gBACwB,CAAC;CAE3D,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,uBAAuB;EAC5C;EACA;CACF,CAAC;CAQH,KAAI,MALiB,UAAU,UAC7B,EAAE,KAAK,OAAO,GACd,EAAE,MAAM,iBAAiB,CAC3B,GAEW,iBAAiB,GAC1B,MAAM,IAAI,aAAa,sBAAsB,EAAE,OAAO,CAAC;CAGzD,MAAM,cAAc,MAAM,UAAU,SAAS,MAAM;CAEnD,IAAI,CAAC,aACH,MAAM,IAAI,aAAa,+BAA+B,EAAE,OAAO,CAAC;CAGlE,OAAO;AACT;;;;;;AAOA,MAAa,aAAa,OACxB,WAC0B;CAC1B,MAAM,YAAY,MAAM;CAExB,MAAM,OAAO,MAAM,UAAU,kBAAkB,MAAM;CAErD,IAAI,CAAC,MACH,MAAM,IAAI,aAAa,kBAAkB,EAAE,OAAO,CAAC;CAGrD,OAAO;AACT"}
1
+ {"version":3,"file":"user.service.mjs","names":[],"sources":["../../../src/services/user.service.ts"],"sourcesContent":["import { UserModel } from '@schemas/user.schema';\nimport { GenericError } from '@utils/errors';\nimport type { UserFilters } from '@utils/filtersAndPagination/getUserFiltersAndPagination';\nimport {\n type FieldsToCheck,\n type UserFields,\n validateUser,\n} from '@utils/validation/validateUser';\nimport type { Types } from 'mongoose';\nimport type { User, UserAPI, UserDocument } from '@/types/user.types';\n\n/**\n * Creates a new user with password in the database and hashes the password.\n * @param user - User object with password not hashed.\n * @returns Created user object.\n */\nexport const createUser = async (\n user: Partial<User>\n): Promise<UserDocument> => {\n const fieldsToCheck: FieldsToCheck[] = ['email'];\n\n const errors = validateUser(user, fieldsToCheck);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('USER_INVALID_FIELDS', {\n userEmail: user.email,\n errors,\n });\n }\n\n const newUser: UserDocument = await UserModel.create(user);\n\n if (!newUser) {\n throw new GenericError('USER_CREATION_FAILED', { userEmail: user.email });\n }\n\n return newUser;\n};\n\n/**\n * Retrieves a user by email.\n * @param email - User's email.\n * @returns User object or null if no user was found.\n */\nexport const getUserByEmail = async (\n email: string\n): Promise<UserDocument | null> => {\n return await UserModel.findOne({ email: String(email) });\n};\n\n/**\n * Retrieves users list by email.\n * @param emails - Users email.\n * @returns User object or null if no user was found.\n */\nexport const getUsersByEmails = async (\n emails: string[]\n): Promise<UserDocument[] | null> => {\n return await UserModel.find({ email: { $in: emails } });\n};\n\n/**\n * Checks if a user exists by email.\n * @param email - User's email.\n * @returns True if the user exists, false otherwise.\n */\nexport const checkUserExists = async (email: string): Promise<boolean> => {\n const user = await UserModel.exists({ email });\n return user !== null;\n};\n\n/**\n * Retrieves a user by ID.\n * @param userId - User's ID.\n * @returns User object or null if no user was found.\n */\nexport const getUserById = async (\n userId: string | Types.ObjectId\n): Promise<UserDocument | null> => await UserModel.findById(userId);\n\n/**\n * Retrieves a user by ID.\n * @param userId - User's ID.\n * @returns User object or null if no user was found.\n */\nexport const getUsersByIds = async (\n userIds: (string | Types.ObjectId)[]\n): Promise<UserDocument[] | null> =>\n await UserModel.find({ _id: { $in: userIds } });\n\n/**\n * Finds users based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @param sortOptions - Sorting options.\n * @returns List of users matching the filters.\n */\nexport const findUsers = async (\n filters: UserFilters,\n skip: number,\n limit: number,\n sortOptions?: Record<string, 1 | -1>\n): Promise<UserDocument[]> => {\n let query = UserModel.find(filters).skip(skip).limit(limit);\n\n if (sortOptions && Object.keys(sortOptions).length > 0) {\n query = query.sort(sortOptions);\n }\n\n return await query;\n};\n\n/**\n * Counts the total number of users that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of users.\n */\nexport const countUsers = async (filters: UserFilters): Promise<number> => {\n const count = await UserModel.countDocuments(filters);\n\n if (typeof count === 'undefined') {\n throw new GenericError('USER_COUNT_FAILED');\n }\n\n return count;\n};\n\n/**\n * Updates a user's information.\n * @param user - The user object.\n * @param updates - The updates to apply to the user.\n * @returns The updated user.\n */\nexport const updateUserById = async (\n userId: string | Types.ObjectId,\n updates: Partial<UserAPI>\n): Promise<UserDocument> => {\n const { id, ...updatesWithoutId } = updates;\n\n const keyToValidate = Object.keys(updatesWithoutId) as UserFields;\n const errors = validateUser(updatesWithoutId, keyToValidate);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('USER_INVALID_FIELDS', {\n userId,\n errors,\n });\n }\n\n const result = await UserModel.updateOne(\n { _id: userId },\n { $set: updatesWithoutId }\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('USER_UPDATE_FAILED', { userId });\n }\n\n const updatedUser = await UserModel.findById(userId);\n\n if (!updatedUser) {\n throw new GenericError('USER_UPDATED_USER_NOT_FOUND', { userId });\n }\n\n return updatedUser;\n};\n\n/**\n * Deletes a user from the database.\n * @param userId - The user object.\n * @returns\n */\nexport const deleteUser = async (\n userId: string | Types.ObjectId\n): Promise<UserDocument> => {\n await getUserById(userId);\n\n const user = await UserModel.findByIdAndDelete(userId);\n\n if (!user) {\n throw new GenericError('USER_NOT_FOUND', { userId });\n }\n\n return user;\n};\n"],"mappings":";;;;;;;;;;AAgBA,MAAa,aAAa,OACxB,SAC0B;CAG1B,MAAM,SAAS,aAAa,MAAM,CAFM,OAEM,CAAC;CAE/C,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,uBAAuB;EAC5C,WAAW,KAAK;EAChB;CACF,CAAC;CAGH,MAAM,UAAwB,MAAM,UAAU,OAAO,IAAI;CAEzD,IAAI,CAAC,SACH,MAAM,IAAI,aAAa,wBAAwB,EAAE,WAAW,KAAK,MAAM,CAAC;CAG1E,OAAO;AACT;;;;;;AAOA,MAAa,iBAAiB,OAC5B,UACiC;CACjC,OAAO,MAAM,UAAU,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AACzD;;;;;;AAOA,MAAa,mBAAmB,OAC9B,WACmC;CACnC,OAAO,MAAM,UAAU,KAAK,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC;AACxD;;;;;;AAOA,MAAa,kBAAkB,OAAO,UAAoC;CAExE,OAAO,MADY,UAAU,OAAO,EAAE,MAAM,CAAC,MAC7B;AAClB;;;;;;AAOA,MAAa,cAAc,OACzB,WACiC,MAAM,UAAU,SAAS,MAAM;;;;;;AAOlE,MAAa,gBAAgB,OAC3B,YAEA,MAAM,UAAU,KAAK,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,CAAC;;;;;;;;;AAUhD,MAAa,YAAY,OACvB,SACA,MACA,OACA,gBAC4B;CAC5B,IAAI,QAAQ,UAAU,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,KAAK;CAE1D,IAAI,eAAe,OAAO,KAAK,WAAW,EAAE,SAAS,GACnD,QAAQ,MAAM,KAAK,WAAW;CAGhC,OAAO,MAAM;AACf;;;;;;AAOA,MAAa,aAAa,OAAO,YAA0C;CACzE,MAAM,QAAQ,MAAM,UAAU,eAAe,OAAO;CAEpD,IAAI,OAAO,UAAU,aACnB,MAAM,IAAI,aAAa,mBAAmB;CAG5C,OAAO;AACT;;;;;;;AAQA,MAAa,iBAAiB,OAC5B,QACA,YAC0B;CAC1B,MAAM,EAAE,IAAI,GAAG,qBAAqB;CAGpC,MAAM,SAAS,aAAa,kBADN,OAAO,KAAK,gBACwB,CAAC;CAE3D,IAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAC/B,MAAM,IAAI,aAAa,uBAAuB;EAC5C;EACA;CACF,CAAC;CAQH,KAAI,MALiB,UAAU,UAC7B,EAAE,KAAK,OAAO,GACd,EAAE,MAAM,iBAAiB,CAC3B,GAEW,iBAAiB,GAC1B,MAAM,IAAI,aAAa,sBAAsB,EAAE,OAAO,CAAC;CAGzD,MAAM,cAAc,MAAM,UAAU,SAAS,MAAM;CAEnD,IAAI,CAAC,aACH,MAAM,IAAI,aAAa,+BAA+B,EAAE,OAAO,CAAC;CAGlE,OAAO;AACT;;;;;;AAOA,MAAa,aAAa,OACxB,WAC0B;CAC1B,MAAM,YAAY,MAAM;CAExB,MAAM,OAAO,MAAM,UAAU,kBAAkB,MAAM;CAErD,IAAI,CAAC,MACH,MAAM,IAAI,aAAa,kBAAkB,EAAE,OAAO,CAAC;CAGrD,OAAO;AACT"}