@proconnect-gouv/proconnect.identite 1.0.0

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 (252) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/data/organization/domains-whitelist.d.ts +2 -0
  3. package/dist/data/organization/domains-whitelist.d.ts.map +1 -0
  4. package/dist/data/organization/domains-whitelist.js +7 -0
  5. package/dist/data/organization/index.d.ts +2 -0
  6. package/dist/data/organization/index.d.ts.map +1 -0
  7. package/dist/data/organization/index.js +2 -0
  8. package/dist/errors/index.d.ts +22 -0
  9. package/dist/errors/index.d.ts.map +1 -0
  10. package/dist/errors/index.js +43 -0
  11. package/dist/managers/certification/distance.d.ts +4 -0
  12. package/dist/managers/certification/distance.d.ts.map +1 -0
  13. package/dist/managers/certification/distance.js +16 -0
  14. package/dist/managers/certification/index.d.ts +3 -0
  15. package/dist/managers/certification/index.d.ts.map +1 -0
  16. package/dist/managers/certification/index.js +3 -0
  17. package/dist/managers/certification/is-organization-dirigeant.d.ts +15 -0
  18. package/dist/managers/certification/is-organization-dirigeant.d.ts.map +1 -0
  19. package/dist/managers/certification/is-organization-dirigeant.js +60 -0
  20. package/dist/managers/franceconnect/index.d.ts +2 -0
  21. package/dist/managers/franceconnect/index.d.ts.map +1 -0
  22. package/dist/managers/franceconnect/index.js +2 -0
  23. package/dist/managers/franceconnect/openid-client.d.ts +37 -0
  24. package/dist/managers/franceconnect/openid-client.d.ts.map +1 -0
  25. package/dist/managers/franceconnect/openid-client.js +54 -0
  26. package/dist/managers/organization/force-join-organization.d.ts +29 -0
  27. package/dist/managers/organization/force-join-organization.d.ts.map +1 -0
  28. package/dist/managers/organization/force-join-organization.js +33 -0
  29. package/dist/managers/organization/get-organization-info.d.ts +5 -0
  30. package/dist/managers/organization/get-organization-info.d.ts.map +1 -0
  31. package/dist/managers/organization/get-organization-info.js +33 -0
  32. package/dist/managers/organization/index.d.ts +4 -0
  33. package/dist/managers/organization/index.d.ts.map +1 -0
  34. package/dist/managers/organization/index.js +4 -0
  35. package/dist/managers/organization/mark-domain-as-verified.d.ts +20 -0
  36. package/dist/managers/organization/mark-domain-as-verified.d.ts.map +1 -0
  37. package/dist/managers/organization/mark-domain-as-verified.js +63 -0
  38. package/dist/managers/user/assign-user-verification-type-to-domain.d.ts +10 -0
  39. package/dist/managers/user/assign-user-verification-type-to-domain.d.ts.map +1 -0
  40. package/dist/managers/user/assign-user-verification-type-to-domain.js +21 -0
  41. package/dist/managers/user/index.d.ts +2 -0
  42. package/dist/managers/user/index.d.ts.map +1 -0
  43. package/dist/managers/user/index.js +1 -0
  44. package/dist/mappers/certification/index.d.ts +4 -0
  45. package/dist/mappers/certification/index.d.ts.map +1 -0
  46. package/dist/mappers/certification/index.js +11 -0
  47. package/dist/mappers/index.d.ts +2 -0
  48. package/dist/mappers/index.d.ts.map +1 -0
  49. package/dist/mappers/index.js +2 -0
  50. package/dist/mappers/organization/from-siret.d.ts +5 -0
  51. package/dist/mappers/organization/from-siret.d.ts.map +1 -0
  52. package/dist/mappers/organization/from-siret.js +51 -0
  53. package/dist/mappers/organization/index.d.ts +2 -0
  54. package/dist/mappers/organization/index.d.ts.map +1 -0
  55. package/dist/mappers/organization/index.js +2 -0
  56. package/dist/repositories/email-domain/add-domain.d.ts +8 -0
  57. package/dist/repositories/email-domain/add-domain.d.ts.map +1 -0
  58. package/dist/repositories/email-domain/add-domain.js +25 -0
  59. package/dist/repositories/email-domain/delete-email-domains-by-verification-types.d.ts +8 -0
  60. package/dist/repositories/email-domain/delete-email-domains-by-verification-types.d.ts.map +1 -0
  61. package/dist/repositories/email-domain/delete-email-domains-by-verification-types.js +21 -0
  62. package/dist/repositories/email-domain/find-email-domains-by-organization-id.d.ts +4 -0
  63. package/dist/repositories/email-domain/find-email-domains-by-organization-id.d.ts.map +1 -0
  64. package/dist/repositories/email-domain/find-email-domains-by-organization-id.js +11 -0
  65. package/dist/repositories/email-domain/index.d.ts +12 -0
  66. package/dist/repositories/email-domain/index.d.ts.map +1 -0
  67. package/dist/repositories/email-domain/index.js +3 -0
  68. package/dist/repositories/organization/find-by-id.d.ts +4 -0
  69. package/dist/repositories/organization/find-by-id.d.ts.map +1 -0
  70. package/dist/repositories/organization/find-by-id.js +11 -0
  71. package/dist/repositories/organization/find-by-user-id.d.ts +12 -0
  72. package/dist/repositories/organization/find-by-user-id.d.ts.map +1 -0
  73. package/dist/repositories/organization/find-by-user-id.js +22 -0
  74. package/dist/repositories/organization/get-by-id.d.ts +4 -0
  75. package/dist/repositories/organization/get-by-id.d.ts.map +1 -0
  76. package/dist/repositories/organization/get-by-id.js +15 -0
  77. package/dist/repositories/organization/get-users-by-organization.d.ts +12 -0
  78. package/dist/repositories/organization/get-users-by-organization.d.ts.map +1 -0
  79. package/dist/repositories/organization/get-users-by-organization.js +23 -0
  80. package/dist/repositories/organization/index.d.ts +7 -0
  81. package/dist/repositories/organization/index.d.ts.map +1 -0
  82. package/dist/repositories/organization/index.js +7 -0
  83. package/dist/repositories/organization/link-user-to-organization.d.ts +16 -0
  84. package/dist/repositories/organization/link-user-to-organization.d.ts.map +1 -0
  85. package/dist/repositories/organization/link-user-to-organization.js +22 -0
  86. package/dist/repositories/organization/upsert.d.ts +7 -0
  87. package/dist/repositories/organization/upsert.d.ts.map +1 -0
  88. package/dist/repositories/organization/upsert.js +103 -0
  89. package/dist/repositories/user/create.d.ts +7 -0
  90. package/dist/repositories/user/create.d.ts.map +1 -0
  91. package/dist/repositories/user/create.js +25 -0
  92. package/dist/repositories/user/find-by-email.d.ts +4 -0
  93. package/dist/repositories/user/find-by-email.d.ts.map +1 -0
  94. package/dist/repositories/user/find-by-email.js +12 -0
  95. package/dist/repositories/user/find-by-id.d.ts +4 -0
  96. package/dist/repositories/user/find-by-id.d.ts.map +1 -0
  97. package/dist/repositories/user/find-by-id.js +13 -0
  98. package/dist/repositories/user/get-by-id.d.ts +4 -0
  99. package/dist/repositories/user/get-by-id.d.ts.map +1 -0
  100. package/dist/repositories/user/get-by-id.js +15 -0
  101. package/dist/repositories/user/get-franceconnect-user-info.d.ts +15 -0
  102. package/dist/repositories/user/get-franceconnect-user-info.d.ts.map +1 -0
  103. package/dist/repositories/user/get-franceconnect-user-info.js +13 -0
  104. package/dist/repositories/user/index.d.ts +9 -0
  105. package/dist/repositories/user/index.d.ts.map +1 -0
  106. package/dist/repositories/user/index.js +9 -0
  107. package/dist/repositories/user/update-user-organization-link.d.ts +16 -0
  108. package/dist/repositories/user/update-user-organization-link.d.ts.map +1 -0
  109. package/dist/repositories/user/update-user-organization-link.js +20 -0
  110. package/dist/repositories/user/update.d.ts +4 -0
  111. package/dist/repositories/user/update.d.ts.map +1 -0
  112. package/dist/repositories/user/update.js +19 -0
  113. package/dist/repositories/user/upsert-franceconnect-userinfo.d.ts +15 -0
  114. package/dist/repositories/user/upsert-franceconnect-userinfo.d.ts.map +1 -0
  115. package/dist/repositories/user/upsert-franceconnect-userinfo.js +23 -0
  116. package/dist/services/organization/index.d.ts +3 -0
  117. package/dist/services/organization/index.d.ts.map +1 -0
  118. package/dist/services/organization/index.js +3 -0
  119. package/dist/services/organization/is-domain-allowed-for-organization.d.ts +2 -0
  120. package/dist/services/organization/is-domain-allowed-for-organization.d.ts.map +1 -0
  121. package/dist/services/organization/is-domain-allowed-for-organization.js +8 -0
  122. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts +9 -0
  123. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts.map +1 -0
  124. package/dist/services/organization/is-entreprise-unipersonnelle.js +18 -0
  125. package/dist/services/postgres/hash-to-postgres-params.d.ts +6 -0
  126. package/dist/services/postgres/hash-to-postgres-params.d.ts.map +1 -0
  127. package/dist/services/postgres/hash-to-postgres-params.js +21 -0
  128. package/dist/services/postgres/index.d.ts +2 -0
  129. package/dist/services/postgres/index.d.ts.map +1 -0
  130. package/dist/services/postgres/index.js +2 -0
  131. package/dist/types/claims.d.ts +16 -0
  132. package/dist/types/claims.d.ts.map +1 -0
  133. package/dist/types/claims.js +16 -0
  134. package/dist/types/contexts.d.ts +5 -0
  135. package/dist/types/contexts.d.ts.map +1 -0
  136. package/dist/types/contexts.js +2 -0
  137. package/dist/types/dirigeant.d.ts +9 -0
  138. package/dist/types/dirigeant.d.ts.map +1 -0
  139. package/dist/types/dirigeant.js +9 -0
  140. package/dist/types/email-domain.d.ts +25 -0
  141. package/dist/types/email-domain.d.ts.map +1 -0
  142. package/dist/types/email-domain.js +14 -0
  143. package/dist/types/franceconnect.d.ts +28 -0
  144. package/dist/types/franceconnect.d.ts.map +1 -0
  145. package/dist/types/franceconnect.js +22 -0
  146. package/dist/types/index.d.ts +10 -0
  147. package/dist/types/index.d.ts.map +1 -0
  148. package/dist/types/index.js +10 -0
  149. package/dist/types/organization-info.d.ts +27 -0
  150. package/dist/types/organization-info.d.ts.map +1 -0
  151. package/dist/types/organization-info.js +26 -0
  152. package/dist/types/organization.d.ts +26 -0
  153. package/dist/types/organization.d.ts.map +1 -0
  154. package/dist/types/organization.js +2 -0
  155. package/dist/types/user-organization-link.d.ts +92 -0
  156. package/dist/types/user-organization-link.d.ts.map +1 -0
  157. package/dist/types/user-organization-link.js +44 -0
  158. package/dist/types/user.d.ts +28 -0
  159. package/dist/types/user.d.ts.map +1 -0
  160. package/dist/types/user.js +2 -0
  161. package/package.json +68 -0
  162. package/src/data/organization/domains-whitelist.ts +8 -0
  163. package/src/data/organization/index.ts +3 -0
  164. package/src/errors/index.ts +50 -0
  165. package/src/managers/certification/distance.test.ts +109 -0
  166. package/src/managers/certification/distance.ts +41 -0
  167. package/src/managers/certification/index.ts +4 -0
  168. package/src/managers/certification/is-organization-dirigeant.test.ts +125 -0
  169. package/src/managers/certification/is-organization-dirigeant.ts +136 -0
  170. package/src/managers/franceconnect/index.ts +3 -0
  171. package/src/managers/franceconnect/openid-client.ts +131 -0
  172. package/src/managers/organization/force-join-organization.test.ts +94 -0
  173. package/src/managers/organization/force-join-organization.ts +90 -0
  174. package/src/managers/organization/get-organization-info.test.ts +47 -0
  175. package/src/managers/organization/get-organization-info.test.ts.snapshot +68 -0
  176. package/src/managers/organization/get-organization-info.ts +58 -0
  177. package/src/managers/organization/index.ts +5 -0
  178. package/src/managers/organization/mark-domain-as-verified.test.ts +90 -0
  179. package/src/managers/organization/mark-domain-as-verified.test.ts.snapshot +52 -0
  180. package/src/managers/organization/mark-domain-as-verified.ts +140 -0
  181. package/src/managers/user/assign-user-verification-type-to-domain.ts +50 -0
  182. package/src/managers/user/index.ts +1 -0
  183. package/src/mappers/certification/index.ts +18 -0
  184. package/src/mappers/index.ts +3 -0
  185. package/src/mappers/organization/from-siret.test.ts +26 -0
  186. package/src/mappers/organization/from-siret.test.ts.snapshot +68 -0
  187. package/src/mappers/organization/from-siret.ts +75 -0
  188. package/src/mappers/organization/index.ts +3 -0
  189. package/src/repositories/email-domain/add-domain.test.ts +43 -0
  190. package/src/repositories/email-domain/add-domain.ts +48 -0
  191. package/src/repositories/email-domain/delete-email-domains-by-verification-types.test.ts +49 -0
  192. package/src/repositories/email-domain/delete-email-domains-by-verification-types.ts +45 -0
  193. package/src/repositories/email-domain/find-email-domains-by-organization-id.test.ts +55 -0
  194. package/src/repositories/email-domain/find-email-domains-by-organization-id.ts +28 -0
  195. package/src/repositories/email-domain/index.ts +13 -0
  196. package/src/repositories/organization/find-by-id.test.ts +51 -0
  197. package/src/repositories/organization/find-by-id.ts +22 -0
  198. package/src/repositories/organization/find-by-user-id.test.ts +70 -0
  199. package/src/repositories/organization/find-by-user-id.test.ts.snapshot +102 -0
  200. package/src/repositories/organization/find-by-user-id.ts +38 -0
  201. package/src/repositories/organization/get-by-id.test.ts +60 -0
  202. package/src/repositories/organization/get-by-id.ts +21 -0
  203. package/src/repositories/organization/get-users-by-organization.test.ts +50 -0
  204. package/src/repositories/organization/get-users-by-organization.test.ts.snapshot +38 -0
  205. package/src/repositories/organization/get-users-by-organization.ts +46 -0
  206. package/src/repositories/organization/index.ts +8 -0
  207. package/src/repositories/organization/link-user-to-organization.test.ts +65 -0
  208. package/src/repositories/organization/link-user-to-organization.test.ts.snapshot +31 -0
  209. package/src/repositories/organization/link-user-to-organization.ts +45 -0
  210. package/src/repositories/organization/upsert.ts +142 -0
  211. package/src/repositories/organization/upset.test.ts +95 -0
  212. package/src/repositories/user/create.test.ts +49 -0
  213. package/src/repositories/user/create.ts +44 -0
  214. package/src/repositories/user/find-by-email.test.ts +62 -0
  215. package/src/repositories/user/find-by-email.ts +22 -0
  216. package/src/repositories/user/find-by-id.test.ts +64 -0
  217. package/src/repositories/user/find-by-id.ts +23 -0
  218. package/src/repositories/user/get-by-id.test.ts +63 -0
  219. package/src/repositories/user/get-by-id.ts +21 -0
  220. package/src/repositories/user/get-franceconnect-user-info.test.ts +58 -0
  221. package/src/repositories/user/get-franceconnect-user-info.ts +25 -0
  222. package/src/repositories/user/index.ts +10 -0
  223. package/src/repositories/user/update-user-organization-link.test.ts +46 -0
  224. package/src/repositories/user/update-user-organization-link.ts +42 -0
  225. package/src/repositories/user/update.test.ts +26 -0
  226. package/src/repositories/user/update.ts +34 -0
  227. package/src/repositories/user/upsert-franceconnect-userinfo.test.ts +100 -0
  228. package/src/repositories/user/upsert-franceconnect-userinfo.ts +41 -0
  229. package/src/services/organization/index.ts +4 -0
  230. package/src/services/organization/is-domain-allowed-for-organization.ts +10 -0
  231. package/src/services/organization/is-entreprise-unipersonnelle.test.ts +32 -0
  232. package/src/services/organization/is-entreprise-unipersonnelle.ts +31 -0
  233. package/src/services/postgres/hash-to-postgres-params.ts +34 -0
  234. package/src/services/postgres/index.ts +3 -0
  235. package/src/types/claims.ts +21 -0
  236. package/src/types/contexts.ts +9 -0
  237. package/src/types/dirigeant.ts +13 -0
  238. package/src/types/email-domain.ts +48 -0
  239. package/src/types/franceconnect.ts +37 -0
  240. package/src/types/index.ts +11 -0
  241. package/src/types/organization-info.ts +32 -0
  242. package/src/types/organization.ts +30 -0
  243. package/src/types/user-organization-link.ts +71 -0
  244. package/src/types/user.ts +29 -0
  245. package/testing/index.ts +31 -0
  246. package/testing/seed/franceconnect/index.ts +40 -0
  247. package/testing/seed/insee/index.ts +22 -0
  248. package/testing/seed/mandataires/index.ts +32 -0
  249. package/testing/seed/organizations/index.ts +41 -0
  250. package/tsconfig.json +17 -0
  251. package/tsconfig.lib.json +9 -0
  252. package/tsconfig.lib.tsbuildinfo +1 -0
@@ -0,0 +1,48 @@
1
+ //
2
+
3
+ import { hashToPostgresParams } from "#src/services/postgres";
4
+ import type { DatabaseContext, EmailDomain } from "#src/types";
5
+ import type { QueryResult } from "pg";
6
+
7
+ //
8
+
9
+ export function addDomainFactory({ pg }: DatabaseContext) {
10
+ return async function addDomain({
11
+ organization_id,
12
+ domain,
13
+ verification_type,
14
+ }: {
15
+ organization_id: number;
16
+ domain: string;
17
+ verification_type: EmailDomain["verification_type"];
18
+ }) {
19
+ const connection = pg;
20
+
21
+ const emailDomain = {
22
+ organization_id,
23
+ domain,
24
+ verification_type,
25
+ can_be_suggested: true,
26
+ verified_at: new Date(),
27
+ created_at: new Date(),
28
+ updated_at: new Date(),
29
+ };
30
+
31
+ const { paramsString, valuesString, values } =
32
+ hashToPostgresParams<EmailDomain>(emailDomain);
33
+
34
+ const { rows }: QueryResult<EmailDomain> = await connection.query(
35
+ `
36
+ INSERT INTO email_domains
37
+ ${paramsString}
38
+ VALUES
39
+ ${valuesString}
40
+ RETURNING *;`,
41
+ values,
42
+ );
43
+
44
+ return rows.shift()!;
45
+ };
46
+ }
47
+
48
+ export type AddDomainHandler = ReturnType<typeof addDomainFactory>;
@@ -0,0 +1,49 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, describe, it } from "node:test";
6
+ import { deleteEmailDomainsByVerificationTypesFactory } from "./delete-email-domains-by-verification-types.js";
7
+
8
+ //
9
+
10
+ const deleteEmailDomainsByVerificationTypes =
11
+ deleteEmailDomainsByVerificationTypesFactory({
12
+ pg: pg as any,
13
+ });
14
+
15
+ describe("deleteEmailDomainsByVerificationTypesFactory", function () {
16
+ before(migrate);
17
+ beforeEach(emptyDatabase);
18
+
19
+ it("should delete domain verification type NULL and 'verified'", async () => {
20
+ await pg.sql`
21
+ INSERT INTO organizations
22
+ (id, siret, created_at, updated_at)
23
+ VALUES
24
+ (1, '66204244933106', '3333-03-03', '3333-04-04')
25
+ ;
26
+ `;
27
+
28
+ await pg.sql`
29
+ INSERT INTO email_domains
30
+ (domain, organization_id, verification_type)
31
+ VALUES
32
+ ('darkangels.world', 1, NULL),
33
+ ('darkangels.world', 1, 'verified')
34
+ ;
35
+ `;
36
+
37
+ const result = await deleteEmailDomainsByVerificationTypes({
38
+ domain: "darkangels.world",
39
+ organization_id: 1,
40
+ domain_verification_types: [null, "verified"],
41
+ });
42
+
43
+ assert.deepEqual(result, {
44
+ affectedRows: 2,
45
+ fields: [],
46
+ rows: [],
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,45 @@
1
+ //
2
+
3
+ import type { DatabaseContext, EmailDomain } from "#src/types";
4
+
5
+ //
6
+
7
+ export function deleteEmailDomainsByVerificationTypesFactory({
8
+ pg,
9
+ }: DatabaseContext) {
10
+ return async function deleteEmailDomainsByVerificationTypes({
11
+ organization_id,
12
+ domain,
13
+ domain_verification_types,
14
+ }: {
15
+ organization_id: EmailDomain["organization_id"];
16
+ domain_verification_types: EmailDomain["verification_type"][];
17
+ domain: EmailDomain["domain"];
18
+ }) {
19
+ const SQL_VERIFICATION_TYPES = domain_verification_types
20
+ .map((type) =>
21
+ type === null
22
+ ? "verification_type IS NULL"
23
+ : `verification_type = '${type}'`,
24
+ )
25
+ .join(" OR ");
26
+
27
+ return pg.query(
28
+ `
29
+ DELETE FROM email_domains
30
+ WHERE
31
+ ${[
32
+ "organization_id = $1",
33
+ "domain = $2",
34
+ `(${SQL_VERIFICATION_TYPES})`,
35
+ ].join(" AND ")}
36
+ ;
37
+ `,
38
+ [organization_id, domain],
39
+ );
40
+ };
41
+ }
42
+
43
+ export type DeleteEmailDomainsByVerificationTypesHandler = ReturnType<
44
+ typeof deleteEmailDomainsByVerificationTypesFactory
45
+ >;
@@ -0,0 +1,55 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, describe, it } from "node:test";
6
+ import { findEmailDomainsByOrganizationIdFactory } from "./find-email-domains-by-organization-id.js";
7
+
8
+ //
9
+
10
+ const findEmailDomainsByOrganizationId =
11
+ findEmailDomainsByOrganizationIdFactory({ pg: pg as any });
12
+
13
+ describe("findEmailDomainsByOrganizationIdFactory", () => {
14
+ before(migrate);
15
+ beforeEach(emptyDatabase);
16
+
17
+ it("should find email domains by organization id", async () => {
18
+ await pg.sql`
19
+ INSERT INTO organizations
20
+ (id, siret, created_at, updated_at)
21
+ VALUES
22
+ (1, '66204244933106', '4444-04-04', '4444-04-04')
23
+ ;
24
+ `;
25
+
26
+ await pg.sql`
27
+ INSERT INTO email_domains
28
+ (id, domain, organization_id, created_at, updated_at)
29
+ VALUES
30
+ (1, 'darkangels.world', 1, '4444-04-04', '4444-04-04')
31
+ ;
32
+ `;
33
+
34
+ const emailDomains = await findEmailDomainsByOrganizationId(1);
35
+
36
+ assert.deepEqual(emailDomains, [
37
+ {
38
+ can_be_suggested: true,
39
+ created_at: new Date("4444-04-04"),
40
+ domain: "darkangels.world",
41
+ id: 1,
42
+ organization_id: 1,
43
+ updated_at: new Date("4444-04-04"),
44
+ verification_type: null,
45
+ verified_at: null,
46
+ },
47
+ ]);
48
+ });
49
+
50
+ it("❎ fail to find the organization 42", async () => {
51
+ const user = await findEmailDomainsByOrganizationId(42);
52
+
53
+ assert.deepEqual(user, []);
54
+ });
55
+ });
@@ -0,0 +1,28 @@
1
+ //
2
+
3
+ import type { DatabaseContext, EmailDomain } from "#src/types";
4
+ import type { QueryResult } from "pg";
5
+
6
+ //
7
+
8
+ export function findEmailDomainsByOrganizationIdFactory({
9
+ pg,
10
+ }: DatabaseContext) {
11
+ return async function findEmailDomainsByOrganizationId(
12
+ organization_id: number,
13
+ ) {
14
+ const { rows }: QueryResult<EmailDomain> = await pg.query(
15
+ `
16
+ SELECT *
17
+ FROM email_domains
18
+ WHERE organization_id = $1`,
19
+ [organization_id],
20
+ );
21
+
22
+ return rows;
23
+ };
24
+ }
25
+
26
+ export type FindEmailDomainsByOrganizationIdHandler = ReturnType<
27
+ typeof findEmailDomainsByOrganizationIdFactory
28
+ >;
@@ -0,0 +1,13 @@
1
+ export * from "./add-domain.js";
2
+ export * from "./delete-email-domains-by-verification-types.js";
3
+ export * from "./find-email-domains-by-organization-id.js";
4
+
5
+ import type { AddDomainHandler } from "./add-domain.js";
6
+ import type { DeleteEmailDomainsByVerificationTypesHandler } from "./delete-email-domains-by-verification-types.js";
7
+ import type { FindEmailDomainsByOrganizationIdHandler } from "./find-email-domains-by-organization-id.js";
8
+
9
+ export type EmailDomainRepository = {
10
+ addDomain: AddDomainHandler;
11
+ deleteEmailDomainsByVerificationTypes: DeleteEmailDomainsByVerificationTypesHandler;
12
+ findEmailDomainsByOrganizationId: FindEmailDomainsByOrganizationIdHandler;
13
+ };
@@ -0,0 +1,51 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, suite, test } from "node:test";
6
+ import { findByIdFactory } from "./find-by-id.js";
7
+
8
+ //
9
+
10
+ const findById = findByIdFactory({ pg: pg as any });
11
+
12
+ suite("findByIdFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+
16
+ test("should find the Necron organization", async () => {
17
+ await pg.sql`
18
+ INSERT INTO organizations
19
+ (cached_libelle, cached_nom_complet, id, siret, created_at, updated_at)
20
+ VALUES
21
+ ('Necron', 'Necrontyr', 1, '⚰️', '1967-12-19', '1967-12-19')
22
+ ;
23
+ `;
24
+ const organization = await findById(1);
25
+
26
+ assert.deepEqual(organization, {
27
+ cached_activite_principale: null,
28
+ cached_adresse: null,
29
+ cached_categorie_juridique: null,
30
+ cached_code_officiel_geographique: null,
31
+ cached_code_postal: null,
32
+ cached_enseigne: null,
33
+ cached_est_active: null,
34
+ cached_est_diffusible: null,
35
+ cached_etat_administratif: null,
36
+ cached_libelle_activite_principale: null,
37
+ cached_libelle_categorie_juridique: null,
38
+ cached_libelle_tranche_effectif: null,
39
+ cached_libelle: "Necron",
40
+ cached_nom_complet: "Necrontyr",
41
+ cached_statut_diffusion: null,
42
+ cached_tranche_effectifs_unite_legale: null,
43
+ cached_tranche_effectifs: null,
44
+ created_at: new Date("1967-12-19"),
45
+ organization_info_fetched_at: null,
46
+ id: 1,
47
+ siret: "⚰️",
48
+ updated_at: new Date("1967-12-19"),
49
+ });
50
+ });
51
+ });
@@ -0,0 +1,22 @@
1
+ //
2
+
3
+ import type { DatabaseContext, Organization } from "#src/types";
4
+ import type { QueryResult } from "pg";
5
+
6
+ //
7
+
8
+ export function findByIdFactory({ pg }: DatabaseContext) {
9
+ return async function findById(id: number) {
10
+ const { rows }: QueryResult<Organization> = await pg.query(
11
+ `
12
+ SELECT *
13
+ FROM organizations
14
+ WHERE id = $1`,
15
+ [id],
16
+ );
17
+
18
+ return rows.shift();
19
+ };
20
+ }
21
+
22
+ export type FindByIdHandler = ReturnType<typeof findByIdFactory>;
@@ -0,0 +1,70 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, suite, test } from "node:test";
6
+ import { findByUserIdFactory } from "./find-by-user-id.js";
7
+
8
+ //
9
+
10
+ const findByUserId = findByUserIdFactory({ pg: pg as any });
11
+
12
+ suite("findByUserIdFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+ beforeEach(seed);
16
+
17
+ test("should return empty array when user has no organizations", async () => {
18
+ const organizations = await findByUserId(42);
19
+ assert.deepEqual(organizations, []);
20
+ });
21
+
22
+ test("should return single organization when user has one organization", async (t) => {
23
+ const organizations = await findByUserId(6);
24
+ t.assert.snapshot(organizations);
25
+ });
26
+
27
+ test("should return multiple organizations when user has multiple organizations", async (t) => {
28
+ const organizations = await findByUserId(1);
29
+ t.assert.snapshot(organizations);
30
+ });
31
+ });
32
+
33
+ //
34
+
35
+ async function seed() {
36
+ await pg.sql`
37
+ INSERT INTO organizations
38
+ (id, cached_libelle, cached_nom_complet, siret, created_at, updated_at)
39
+ VALUES
40
+ (1, '⚰️', 'Sautekh Dynasty', '40000000000001', '2023-01-15', '2024-02-10'),
41
+ (2, '💀', 'Nihilakh Dynasty', '40000000000002', '2023-02-20', '2024-03-05'),
42
+ (3, '🤖', 'Szarekhan Dynasty', '40000000000003', '2023-03-25', '2024-04-12'),
43
+ (4, '🔥', 'Adeptus Mechanicus', '40000000000004', '2023-04-10', '2024-05-15'),
44
+ (5, '👨‍🚀', 'Ultramarines Chapter', '40000000000005', '2023-05-05', '2024-06-20'),
45
+ (6, '👹', 'Death Guard', '40000000000006', '2023-06-15', '2024-07-25')
46
+ ;
47
+ `;
48
+
49
+ await pg.sql`
50
+ INSERT INTO users
51
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
52
+ VALUES
53
+ (1, 'imotekh@sautekh.necron', '2023-01-20', '2024-02-15', 'Imotekh', 'The Stormlord', '00000000001', 'Phaeron'),
54
+ (2, 'trazyn@nihilakh.necron', '2023-02-25', '2024-03-10', 'Trazyn', 'The Infinite', '00000000002', 'Archaeologist'),
55
+ (6, 'marneus.calgar@ultramar.imperium', '2023-06-20', '2024-07-30', 'Marneus', 'Calgar', '00000000006', 'Chapter Master'),
56
+ (7, 'mortarion@plague.chaos', '2023-07-25', '2024-08-05', 'Mortarion', 'Primarch', '00000000007', 'Daemon Primarch')
57
+ ;
58
+ `;
59
+
60
+ await pg.sql`
61
+ INSERT INTO users_organizations
62
+ (user_id, organization_id, created_at, updated_at, is_external, verification_type, needs_official_contact_email_verification, official_contact_email_verification_token, official_contact_email_verification_sent_at, has_been_greeted)
63
+ VALUES
64
+ (1, 1, '2023-01-25', '2024-02-20', false, 'necron', false, 'necron_token_1', '2023-01-26', true),
65
+ (1, 3, '2023-04-01', '2024-05-05', true, 'alliance', true, 'alliance_token_1', '2023-04-02', false),
66
+ (2, 2, '2023-03-01', '2024-04-01', false, 'necron', false, 'necron_token_2', '2023-03-02', true),
67
+ (6, 5, '2023-07-05', '2024-08-15', false, 'imperial', false, 'imperial_token_6', '2023-07-06', true)
68
+ ;
69
+ `;
70
+ }
@@ -0,0 +1,102 @@
1
+ exports[`findByUserIdFactory > should return multiple organizations when user has multiple organizations 1`] = `
2
+ [
3
+ {
4
+ "id": 1,
5
+ "siret": "40000000000001",
6
+ "created_at": "2023-01-15T00:00:00.000Z",
7
+ "updated_at": "2024-02-10T00:00:00.000Z",
8
+ "cached_libelle": "⚰️",
9
+ "cached_nom_complet": "Sautekh Dynasty",
10
+ "cached_enseigne": null,
11
+ "cached_tranche_effectifs": null,
12
+ "cached_tranche_effectifs_unite_legale": null,
13
+ "cached_libelle_tranche_effectif": null,
14
+ "cached_etat_administratif": null,
15
+ "cached_est_active": null,
16
+ "cached_statut_diffusion": null,
17
+ "cached_est_diffusible": null,
18
+ "cached_adresse": null,
19
+ "cached_code_postal": null,
20
+ "cached_activite_principale": null,
21
+ "cached_libelle_activite_principale": null,
22
+ "cached_categorie_juridique": null,
23
+ "cached_libelle_categorie_juridique": null,
24
+ "organization_info_fetched_at": null,
25
+ "cached_code_officiel_geographique": null,
26
+ "is_external": false,
27
+ "verification_type": "necron",
28
+ "verified_at": null,
29
+ "has_been_greeted": true,
30
+ "needs_official_contact_email_verification": false,
31
+ "official_contact_email_verification_token": "necron_token_1",
32
+ "official_contact_email_verification_sent_at": "2023-01-26T00:00:00.000Z"
33
+ },
34
+ {
35
+ "id": 3,
36
+ "siret": "40000000000003",
37
+ "created_at": "2023-03-25T00:00:00.000Z",
38
+ "updated_at": "2024-04-12T00:00:00.000Z",
39
+ "cached_libelle": "🤖",
40
+ "cached_nom_complet": "Szarekhan Dynasty",
41
+ "cached_enseigne": null,
42
+ "cached_tranche_effectifs": null,
43
+ "cached_tranche_effectifs_unite_legale": null,
44
+ "cached_libelle_tranche_effectif": null,
45
+ "cached_etat_administratif": null,
46
+ "cached_est_active": null,
47
+ "cached_statut_diffusion": null,
48
+ "cached_est_diffusible": null,
49
+ "cached_adresse": null,
50
+ "cached_code_postal": null,
51
+ "cached_activite_principale": null,
52
+ "cached_libelle_activite_principale": null,
53
+ "cached_categorie_juridique": null,
54
+ "cached_libelle_categorie_juridique": null,
55
+ "organization_info_fetched_at": null,
56
+ "cached_code_officiel_geographique": null,
57
+ "is_external": true,
58
+ "verification_type": "alliance",
59
+ "verified_at": null,
60
+ "has_been_greeted": false,
61
+ "needs_official_contact_email_verification": true,
62
+ "official_contact_email_verification_token": "alliance_token_1",
63
+ "official_contact_email_verification_sent_at": "2023-04-02T00:00:00.000Z"
64
+ }
65
+ ]
66
+ `;
67
+
68
+ exports[`findByUserIdFactory > should return single organization when user has one organization 1`] = `
69
+ [
70
+ {
71
+ "id": 5,
72
+ "siret": "40000000000005",
73
+ "created_at": "2023-05-05T00:00:00.000Z",
74
+ "updated_at": "2024-06-20T00:00:00.000Z",
75
+ "cached_libelle": "👨‍🚀",
76
+ "cached_nom_complet": "Ultramarines Chapter",
77
+ "cached_enseigne": null,
78
+ "cached_tranche_effectifs": null,
79
+ "cached_tranche_effectifs_unite_legale": null,
80
+ "cached_libelle_tranche_effectif": null,
81
+ "cached_etat_administratif": null,
82
+ "cached_est_active": null,
83
+ "cached_statut_diffusion": null,
84
+ "cached_est_diffusible": null,
85
+ "cached_adresse": null,
86
+ "cached_code_postal": null,
87
+ "cached_activite_principale": null,
88
+ "cached_libelle_activite_principale": null,
89
+ "cached_categorie_juridique": null,
90
+ "cached_libelle_categorie_juridique": null,
91
+ "organization_info_fetched_at": null,
92
+ "cached_code_officiel_geographique": null,
93
+ "is_external": false,
94
+ "verification_type": "imperial",
95
+ "verified_at": null,
96
+ "has_been_greeted": true,
97
+ "needs_official_contact_email_verification": false,
98
+ "official_contact_email_verification_token": "imperial_token_6",
99
+ "official_contact_email_verification_sent_at": "2023-07-06T00:00:00.000Z"
100
+ }
101
+ ]
102
+ `;
@@ -0,0 +1,38 @@
1
+ //
2
+
3
+ import type {
4
+ BaseUserOrganizationLink,
5
+ DatabaseContext,
6
+ Organization,
7
+ } from "#src/types";
8
+ import type { QueryResult } from "pg";
9
+
10
+ //
11
+
12
+ export function findByUserIdFactory({ pg }: DatabaseContext) {
13
+ return async function findByUserId(userId: number) {
14
+ const { rows }: QueryResult<Organization & BaseUserOrganizationLink> =
15
+ await pg.query(
16
+ `
17
+ SELECT
18
+ o.*,
19
+ uo.is_external,
20
+ uo.verification_type,
21
+ uo.verified_at,
22
+ uo.has_been_greeted,
23
+ uo.needs_official_contact_email_verification,
24
+ uo.official_contact_email_verification_token,
25
+ uo.official_contact_email_verification_sent_at
26
+ FROM organizations o
27
+ INNER JOIN users_organizations uo ON uo.organization_id = o.id
28
+ WHERE uo.user_id = $1
29
+ ORDER BY o.created_at
30
+ `,
31
+ [userId],
32
+ );
33
+
34
+ return rows;
35
+ };
36
+ }
37
+
38
+ export type FindByUserIdHandler = ReturnType<typeof findByUserIdFactory>;
@@ -0,0 +1,60 @@
1
+ //
2
+
3
+ import { OrganizationNotFoundError } from "#src/errors";
4
+ import { emptyDatabase, migrate, pg } from "#testing";
5
+ import assert from "node:assert/strict";
6
+ import { before, beforeEach, suite, test } from "node:test";
7
+ import { getByIdFactory } from "./get-by-id.js";
8
+
9
+ //
10
+
11
+ const getById = getByIdFactory({ pg: pg as any });
12
+
13
+ suite("getByIdFactory", () => {
14
+ before(migrate);
15
+ beforeEach(emptyDatabase);
16
+
17
+ test("should find a user by id", async () => {
18
+ await pg.sql`
19
+ INSERT INTO organizations
20
+ (cached_libelle, cached_nom_complet, id, siret, created_at, updated_at)
21
+ VALUES
22
+ ('Necron', 'Necrontyr', 1, '⚰️', '1967-12-19', '1967-12-19')
23
+ ;
24
+ `;
25
+
26
+ const organization = await getById(1);
27
+
28
+ assert.deepEqual(organization, {
29
+ cached_activite_principale: null,
30
+ cached_adresse: null,
31
+ cached_categorie_juridique: null,
32
+ cached_code_officiel_geographique: null,
33
+ cached_code_postal: null,
34
+ cached_enseigne: null,
35
+ cached_est_active: null,
36
+ cached_est_diffusible: null,
37
+ cached_etat_administratif: null,
38
+ cached_libelle_activite_principale: null,
39
+ cached_libelle_categorie_juridique: null,
40
+ cached_libelle_tranche_effectif: null,
41
+ cached_libelle: "Necron",
42
+ cached_nom_complet: "Necrontyr",
43
+ cached_statut_diffusion: null,
44
+ cached_tranche_effectifs_unite_legale: null,
45
+ cached_tranche_effectifs: null,
46
+ created_at: new Date("1967-12-19"),
47
+ organization_info_fetched_at: null,
48
+ id: 1,
49
+ siret: "⚰️",
50
+ updated_at: new Date("1967-12-19"),
51
+ });
52
+ });
53
+
54
+ test("❎ fail to find the God-Emperor of Mankind", async () => {
55
+ await assert.rejects(
56
+ getById(42),
57
+ new OrganizationNotFoundError("Organization not found"),
58
+ );
59
+ });
60
+ });
@@ -0,0 +1,21 @@
1
+ //
2
+
3
+ import { OrganizationNotFoundError } from "#src/errors";
4
+ import type { DatabaseContext } from "#src/types";
5
+ import { isEmpty } from "lodash-es";
6
+ import { findByIdFactory } from "./find-by-id.js";
7
+
8
+ //
9
+
10
+ export function getByIdFactory({ pg }: DatabaseContext) {
11
+ const findById = findByIdFactory({ pg });
12
+ return async function getById(id: number) {
13
+ const organization = await findById(id);
14
+ if (isEmpty(organization)) {
15
+ throw new OrganizationNotFoundError("Organization not found");
16
+ }
17
+ return organization;
18
+ };
19
+ }
20
+
21
+ export type GetByIdHandler = ReturnType<typeof getByIdFactory>;
@@ -0,0 +1,50 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, describe, it } from "node:test";
6
+ import { getUsersByOrganizationFactory } from "./get-users-by-organization.js";
7
+
8
+ //
9
+
10
+ const getUsersByOrganization = getUsersByOrganizationFactory({ pg: pg as any });
11
+
12
+ describe("getUsersByOrganizationFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+
16
+ it("should find users by organization id", async (t) => {
17
+ await pg.sql`
18
+ INSERT INTO organizations
19
+ (cached_libelle, cached_nom_complet, id, siret, created_at, updated_at)
20
+ VALUES
21
+ ('Necron', 'Necrontyr', 1, '⚰️', '1967-12-19', '1967-12-19')
22
+ ;
23
+ `;
24
+
25
+ await pg.sql`
26
+ INSERT INTO users
27
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
28
+ VALUES
29
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque')
30
+ ;
31
+ `;
32
+
33
+ await pg.sql`
34
+ INSERT INTO users_organizations
35
+ (user_id, organization_id, created_at, updated_at, is_external, verification_type, needs_official_contact_email_verification, official_contact_email_verification_token, official_contact_email_verification_sent_at)
36
+ VALUES
37
+ (1, 1, '4444-04-04', '4444-04-04', false, 'no_verification_means_available', false, null, null)
38
+ ;
39
+ `;
40
+
41
+ const user = await getUsersByOrganization(1);
42
+
43
+ t.assert.snapshot(user);
44
+ });
45
+
46
+ it("❎ fail to find users for unknown organization id", async () => {
47
+ const user = await getUsersByOrganization(42);
48
+ assert.deepEqual(user, []);
49
+ });
50
+ });