@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,38 @@
1
+ exports[`getUsersByOrganizationFactory > should find users by organization id 1`] = `
2
+ [
3
+ {
4
+ "id": 1,
5
+ "email": "lion.eljonson@darkangels.world",
6
+ "encrypted_password": "",
7
+ "reset_password_token": null,
8
+ "reset_password_sent_at": null,
9
+ "sign_in_count": 0,
10
+ "last_sign_in_at": null,
11
+ "created_at": "4444-04-04T00:00:00.000Z",
12
+ "updated_at": "4444-04-04T00:00:00.000Z",
13
+ "email_verified": false,
14
+ "verify_email_token": null,
15
+ "verify_email_sent_at": null,
16
+ "given_name": "lion",
17
+ "family_name": "el'jonson",
18
+ "phone_number": "i",
19
+ "job": "primarque",
20
+ "magic_link_token": null,
21
+ "magic_link_sent_at": null,
22
+ "email_verified_at": null,
23
+ "current_challenge": null,
24
+ "needs_inclusionconnect_welcome_page": false,
25
+ "needs_inclusionconnect_onboarding_help": false,
26
+ "encrypted_totp_key": null,
27
+ "totp_key_verified_at": null,
28
+ "force_2fa": false,
29
+ "is_external": false,
30
+ "verification_type": "no_verification_means_available",
31
+ "verified_at": null,
32
+ "has_been_greeted": false,
33
+ "needs_official_contact_email_verification": false,
34
+ "official_contact_email_verification_token": null,
35
+ "official_contact_email_verification_sent_at": null
36
+ }
37
+ ]
38
+ `;
@@ -0,0 +1,46 @@
1
+ //
2
+
3
+ import type {
4
+ BaseUserOrganizationLink,
5
+ DatabaseContext,
6
+ User,
7
+ } from "#src/types";
8
+ import type { QueryResult } from "pg";
9
+
10
+ //
11
+
12
+ export function getUsersByOrganizationFactory({ pg }: DatabaseContext) {
13
+ return async function getUsersByOrganization(
14
+ organization_id: number,
15
+ additionalWhereClause: string = "",
16
+ additionalParams: any[] = [],
17
+ ) {
18
+ const connection = pg;
19
+ const baseParams = [organization_id];
20
+
21
+ const { rows }: QueryResult<User & BaseUserOrganizationLink> =
22
+ await connection.query(
23
+ `
24
+ SELECT
25
+ u.*,
26
+ uo.is_external,
27
+ uo.verification_type,
28
+ uo.verified_at,
29
+ uo.has_been_greeted,
30
+ uo.needs_official_contact_email_verification,
31
+ uo.official_contact_email_verification_token,
32
+ uo.official_contact_email_verification_sent_at
33
+ FROM users u
34
+ INNER JOIN users_organizations AS uo ON uo.user_id = u.id
35
+ WHERE uo.organization_id = $1
36
+ ${additionalWhereClause}`,
37
+ [...baseParams, ...additionalParams],
38
+ );
39
+
40
+ return rows;
41
+ };
42
+ }
43
+
44
+ export type GetUsersByOrganizationHandler = ReturnType<
45
+ typeof getUsersByOrganizationFactory
46
+ >;
@@ -0,0 +1,8 @@
1
+ //
2
+
3
+ export * from "./find-by-id.js";
4
+ export * from "./find-by-user-id.js";
5
+ export * from "./get-by-id.js";
6
+ export * from "./get-users-by-organization.js";
7
+ export * from "./link-user-to-organization.js";
8
+ export * from "./upsert.js";
@@ -0,0 +1,65 @@
1
+ import { emptyDatabase, migrate, pg } from "#testing";
2
+ import { before, beforeEach, mock, suite, test } from "node:test";
3
+ import { linkUserToOrganizationFactory } from "./link-user-to-organization.js";
4
+
5
+ //
6
+
7
+ const linkUserToOrganization = linkUserToOrganizationFactory({ pg: pg as any });
8
+
9
+ suite("linkUserToOrganizationFactory", () => {
10
+ before(migrate);
11
+ beforeEach(emptyDatabase);
12
+ before(() => {
13
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
14
+ });
15
+
16
+ test("should link user to organization", 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
+ await pg.sql`
25
+ INSERT INTO users
26
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
27
+ VALUES
28
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque')
29
+ ;
30
+ `;
31
+
32
+ const userOrganizationLink = await linkUserToOrganization({
33
+ organization_id: 1,
34
+ user_id: 1,
35
+ verification_type: null,
36
+ });
37
+
38
+ t.assert.snapshot(userOrganizationLink);
39
+ });
40
+
41
+ test("should mark a user as organization_dirigeant", async (t) => {
42
+ await pg.sql`
43
+ INSERT INTO organizations
44
+ (cached_libelle, cached_nom_complet, id, siret, created_at, updated_at)
45
+ VALUES
46
+ ('Necron', 'Necrontyr', 1, '⚰️', '1967-12-19', '1967-12-19')
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, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque')
54
+ ;
55
+ `;
56
+
57
+ const userOrganizationLink = await linkUserToOrganization({
58
+ organization_id: 1,
59
+ user_id: 1,
60
+ verification_type: "organization_dirigeant",
61
+ });
62
+
63
+ t.assert.snapshot(userOrganizationLink);
64
+ });
65
+ });
@@ -0,0 +1,31 @@
1
+ exports[`linkUserToOrganizationFactory > should link user to organization 1`] = `
2
+ {
3
+ "user_id": 1,
4
+ "organization_id": 1,
5
+ "is_external": false,
6
+ "created_at": "4444-04-04T00:00:00.000Z",
7
+ "updated_at": "4444-04-04T00:00:00.000Z",
8
+ "verification_type": null,
9
+ "has_been_greeted": false,
10
+ "needs_official_contact_email_verification": false,
11
+ "official_contact_email_verification_token": null,
12
+ "official_contact_email_verification_sent_at": null,
13
+ "verified_at": null
14
+ }
15
+ `;
16
+
17
+ exports[`linkUserToOrganizationFactory > should mark a user as organization_dirigeant 1`] = `
18
+ {
19
+ "user_id": 1,
20
+ "organization_id": 1,
21
+ "is_external": false,
22
+ "created_at": "4444-04-04T00:00:00.000Z",
23
+ "updated_at": "4444-04-04T00:00:00.000Z",
24
+ "verification_type": "organization_dirigeant",
25
+ "has_been_greeted": false,
26
+ "needs_official_contact_email_verification": false,
27
+ "official_contact_email_verification_token": null,
28
+ "official_contact_email_verification_sent_at": null,
29
+ "verified_at": "4444-04-04T00:00:00.000Z"
30
+ }
31
+ `;
@@ -0,0 +1,45 @@
1
+ import { hashToPostgresParams } from "#src/services/postgres";
2
+ import type {
3
+ DatabaseContext,
4
+ InsertUserOrganizationLink,
5
+ UserOrganizationLink,
6
+ } from "#src/types";
7
+ import type { QueryResult } from "pg";
8
+
9
+ export function linkUserToOrganizationFactory({ pg }: DatabaseContext) {
10
+ return async function linkUserToOrganization({
11
+ is_external = false,
12
+ needs_official_contact_email_verification = false,
13
+ organization_id,
14
+ user_id,
15
+ verification_type,
16
+ }: InsertUserOrganizationLink) {
17
+ const { paramsString, valuesString, values } =
18
+ hashToPostgresParams<UserOrganizationLink>({
19
+ created_at: new Date(),
20
+ is_external,
21
+ needs_official_contact_email_verification,
22
+ organization_id,
23
+ updated_at: new Date(),
24
+ user_id,
25
+ verification_type,
26
+ verified_at: verification_type ? new Date() : undefined,
27
+ });
28
+
29
+ const { rows }: QueryResult<UserOrganizationLink> = await pg.query(
30
+ `
31
+ INSERT INTO users_organizations
32
+ ${paramsString}
33
+ VALUES
34
+ ${valuesString}
35
+ RETURNING *;`,
36
+ values,
37
+ );
38
+
39
+ return rows.shift()!;
40
+ };
41
+ }
42
+
43
+ export type LinkUserToOrganizationHandler = ReturnType<
44
+ typeof linkUserToOrganizationFactory
45
+ >;
@@ -0,0 +1,142 @@
1
+ //
2
+
3
+ import type {
4
+ DatabaseContext,
5
+ Organization,
6
+ OrganizationInfo,
7
+ } from "#src/types";
8
+ import type { QueryResult } from "pg";
9
+
10
+ //
11
+
12
+ export function upsertFactory({ pg }: DatabaseContext) {
13
+ return async function upsert({
14
+ siret,
15
+ organizationInfo: {
16
+ libelle: cached_libelle,
17
+ nomComplet: cached_nom_complet,
18
+ enseigne: cached_enseigne,
19
+ trancheEffectifs: cached_tranche_effectifs,
20
+ trancheEffectifsUniteLegale: cached_tranche_effectifs_unite_legale,
21
+ libelleTrancheEffectif: cached_libelle_tranche_effectif,
22
+ etatAdministratif: cached_etat_administratif,
23
+ estActive: cached_est_active,
24
+ statutDiffusion: cached_statut_diffusion,
25
+ estDiffusible: cached_est_diffusible,
26
+ adresse: cached_adresse,
27
+ codePostal: cached_code_postal,
28
+ codeOfficielGeographique: cached_code_officiel_geographique,
29
+ activitePrincipale: cached_activite_principale,
30
+ libelleActivitePrincipale: cached_libelle_activite_principale,
31
+ categorieJuridique: cached_categorie_juridique,
32
+ libelleCategorieJuridique: cached_libelle_categorie_juridique,
33
+ },
34
+ }: {
35
+ siret: string;
36
+ organizationInfo: OrganizationInfo;
37
+ }) {
38
+ const { rows }: QueryResult<Organization> = await pg.query(
39
+ `
40
+ INSERT INTO organizations
41
+ (
42
+ siret,
43
+ cached_libelle,
44
+ cached_nom_complet,
45
+ cached_enseigne,
46
+ cached_tranche_effectifs,
47
+ cached_tranche_effectifs_unite_legale,
48
+ cached_libelle_tranche_effectif,
49
+ cached_etat_administratif,
50
+ cached_est_active,
51
+ cached_statut_diffusion,
52
+ cached_est_diffusible,
53
+ cached_adresse,
54
+ cached_code_postal,
55
+ cached_code_officiel_geographique,
56
+ cached_activite_principale,
57
+ cached_libelle_activite_principale,
58
+ cached_categorie_juridique,
59
+ cached_libelle_categorie_juridique,
60
+ organization_info_fetched_at,
61
+ updated_at,
62
+ created_at
63
+ )
64
+ VALUES
65
+ ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
66
+ ON CONFLICT (siret)
67
+ DO UPDATE
68
+ SET (
69
+ siret,
70
+ cached_libelle,
71
+ cached_nom_complet,
72
+ cached_enseigne,
73
+ cached_tranche_effectifs,
74
+ cached_tranche_effectifs_unite_legale,
75
+ cached_libelle_tranche_effectif,
76
+ cached_etat_administratif,
77
+ cached_est_active,
78
+ cached_statut_diffusion,
79
+ cached_est_diffusible,
80
+ cached_adresse,
81
+ cached_code_postal,
82
+ cached_code_officiel_geographique,
83
+ cached_activite_principale,
84
+ cached_libelle_activite_principale,
85
+ cached_categorie_juridique,
86
+ cached_libelle_categorie_juridique,
87
+ organization_info_fetched_at,
88
+ updated_at
89
+ ) = (
90
+ EXCLUDED.siret,
91
+ EXCLUDED.cached_libelle,
92
+ EXCLUDED.cached_nom_complet,
93
+ EXCLUDED.cached_enseigne,
94
+ EXCLUDED.cached_tranche_effectifs,
95
+ EXCLUDED.cached_tranche_effectifs_unite_legale,
96
+ EXCLUDED.cached_libelle_tranche_effectif,
97
+ EXCLUDED.cached_etat_administratif,
98
+ EXCLUDED.cached_est_active,
99
+ EXCLUDED.cached_statut_diffusion,
100
+ EXCLUDED.cached_est_diffusible,
101
+ EXCLUDED.cached_adresse,
102
+ EXCLUDED.cached_code_postal,
103
+ EXCLUDED.cached_code_officiel_geographique,
104
+ EXCLUDED.cached_activite_principale,
105
+ EXCLUDED.cached_libelle_activite_principale,
106
+ EXCLUDED.cached_categorie_juridique,
107
+ EXCLUDED.cached_libelle_categorie_juridique,
108
+ EXCLUDED.organization_info_fetched_at,
109
+ EXCLUDED.updated_at
110
+ )
111
+ RETURNING *
112
+ `,
113
+ [
114
+ siret,
115
+ cached_libelle,
116
+ cached_nom_complet,
117
+ cached_enseigne,
118
+ cached_tranche_effectifs,
119
+ cached_tranche_effectifs_unite_legale,
120
+ cached_libelle_tranche_effectif,
121
+ cached_etat_administratif,
122
+ cached_est_active,
123
+ cached_statut_diffusion,
124
+ cached_est_diffusible,
125
+ cached_adresse,
126
+ cached_code_postal,
127
+ cached_code_officiel_geographique,
128
+ cached_activite_principale,
129
+ cached_libelle_activite_principale,
130
+ cached_categorie_juridique,
131
+ cached_libelle_categorie_juridique,
132
+ new Date(),
133
+ new Date(),
134
+ new Date(),
135
+ ],
136
+ );
137
+
138
+ return rows.shift()!;
139
+ };
140
+ }
141
+
142
+ export type UpsertHandler = ReturnType<typeof upsertFactory>;
@@ -0,0 +1,95 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, mock, suite, test } from "node:test";
6
+ import { upsertFactory } from "./upsert.js";
7
+
8
+ //
9
+
10
+ const upset = upsertFactory({ pg: pg as any });
11
+
12
+ suite("upset", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+ before(() => {
16
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
17
+ });
18
+
19
+ test("should create the Tau Empire organization", async () => {
20
+ const organization = await upset({
21
+ organizationInfo: {
22
+ libelle: "Tau Empire",
23
+ nomComplet: "Tau Empire",
24
+ } as any,
25
+ siret: "👽️",
26
+ });
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: "Tau Empire",
39
+ cached_libelle_activite_principale: null,
40
+ cached_libelle_categorie_juridique: null,
41
+ cached_libelle_tranche_effectif: null,
42
+ cached_nom_complet: "Tau Empire",
43
+ cached_statut_diffusion: null,
44
+ cached_tranche_effectifs: null,
45
+ cached_tranche_effectifs_unite_legale: null,
46
+ created_at: new Date("4444-04-04"),
47
+ id: 1,
48
+ organization_info_fetched_at: new Date("4444-04-04"),
49
+ siret: "👽️",
50
+ updated_at: new Date("4444-04-04"),
51
+ });
52
+ });
53
+
54
+ test("should update the Necron organization", async () => {
55
+ await pg.sql`
56
+ INSERT INTO organizations
57
+ (siret, created_at, updated_at)
58
+ VALUES
59
+ ('⚰️', '1967-12-19', '1967-12-19');
60
+ `;
61
+
62
+ const organization = await upset({
63
+ organizationInfo: {
64
+ libelle: "Necron",
65
+ nomComplet: "Necrontyr",
66
+ } as any,
67
+ siret: "⚰️",
68
+ });
69
+
70
+ assert.deepEqual(organization, {
71
+ cached_activite_principale: null,
72
+ cached_adresse: null,
73
+ cached_categorie_juridique: null,
74
+ cached_code_officiel_geographique: null,
75
+ cached_code_postal: null,
76
+ cached_enseigne: null,
77
+ cached_est_active: null,
78
+ cached_est_diffusible: null,
79
+ cached_etat_administratif: null,
80
+ cached_libelle: "Necron",
81
+ cached_libelle_activite_principale: null,
82
+ cached_libelle_categorie_juridique: null,
83
+ cached_libelle_tranche_effectif: null,
84
+ cached_nom_complet: "Necrontyr",
85
+ cached_statut_diffusion: null,
86
+ cached_tranche_effectifs: null,
87
+ cached_tranche_effectifs_unite_legale: null,
88
+ created_at: new Date("1967-12-19"),
89
+ id: 1,
90
+ organization_info_fetched_at: new Date("4444-04-04"),
91
+ siret: "⚰️",
92
+ updated_at: new Date("4444-04-04"),
93
+ });
94
+ });
95
+ });
@@ -0,0 +1,49 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, mock, suite, test } from "node:test";
6
+ import { createUserFactory } from "./create.js";
7
+
8
+ //
9
+
10
+ const createUser = createUserFactory({ pg: pg as any });
11
+
12
+ suite("createUserFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+
16
+ test("should create the god-emperor of mankind", async () => {
17
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
18
+
19
+ const user = await createUser({ email: "god-emperor@mankind" });
20
+
21
+ assert.deepEqual(user, {
22
+ created_at: new Date("4444-04-04"),
23
+ current_challenge: null,
24
+ email: "god-emperor@mankind",
25
+ email_verified: false,
26
+ email_verified_at: null,
27
+ encrypted_password: null,
28
+ encrypted_totp_key: null,
29
+ family_name: null,
30
+ force_2fa: false,
31
+ given_name: null,
32
+ id: 1,
33
+ job: null,
34
+ last_sign_in_at: null,
35
+ magic_link_sent_at: null,
36
+ magic_link_token: null,
37
+ needs_inclusionconnect_onboarding_help: false,
38
+ needs_inclusionconnect_welcome_page: false,
39
+ phone_number: null,
40
+ reset_password_sent_at: null,
41
+ reset_password_token: null,
42
+ sign_in_count: 0,
43
+ totp_key_verified_at: null,
44
+ updated_at: new Date("4444-04-04"),
45
+ verify_email_sent_at: null,
46
+ verify_email_token: null,
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,44 @@
1
+ //
2
+
3
+ import { hashToPostgresParams } from "#src/services/postgres";
4
+ import type { DatabaseContext, User } from "#src/types";
5
+ import type { QueryResult } from "pg";
6
+
7
+ //
8
+
9
+ export function createUserFactory({ pg }: DatabaseContext) {
10
+ return async function createUser({
11
+ email,
12
+ encrypted_password = null,
13
+ }: {
14
+ email: string;
15
+ encrypted_password?: string | null;
16
+ }) {
17
+ const userWithTimestamps = {
18
+ email,
19
+ email_verified: false,
20
+ verify_email_token: null,
21
+ verify_email_sent_at: null,
22
+ encrypted_password,
23
+ magic_link_token: null,
24
+ magic_link_sent_at: null,
25
+ reset_password_token: null,
26
+ reset_password_sent_at: null,
27
+ sign_in_count: 0,
28
+ last_sign_in_at: null,
29
+ created_at: new Date(),
30
+ updated_at: new Date(),
31
+ };
32
+
33
+ const { paramsString, valuesString, values } =
34
+ hashToPostgresParams<User>(userWithTimestamps);
35
+
36
+ const { rows }: QueryResult<User> = await pg.query(
37
+ `INSERT INTO users ${paramsString} VALUES ${valuesString} RETURNING *;`,
38
+ values,
39
+ );
40
+ return rows.shift()!;
41
+ };
42
+ }
43
+
44
+ export type CreateUserHandler = ReturnType<typeof createUserFactory>;
@@ -0,0 +1,62 @@
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 { findByEmailFactory } from "./find-by-email.js";
7
+
8
+ //
9
+
10
+ const findByEmail = findByEmailFactory({ pg: pg as any });
11
+
12
+ suite("findByEmailFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+
16
+ test("should find a user by email", async () => {
17
+ await pg.sql`
18
+ INSERT INTO users
19
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
20
+ VALUES
21
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque'),
22
+ (2, 'perturabo@ironwarriors.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'iv', 'primarque')
23
+ ;
24
+ `;
25
+
26
+ const user = await findByEmail("lion.eljonson@darkangels.world");
27
+
28
+ assert.deepEqual(user, {
29
+ created_at: new Date("4444-04-04"),
30
+ current_challenge: null,
31
+ email: "lion.eljonson@darkangels.world",
32
+ email_verified: false,
33
+ email_verified_at: null,
34
+ encrypted_password: "",
35
+ encrypted_totp_key: null,
36
+ family_name: "el'jonson",
37
+ force_2fa: false,
38
+ given_name: "lion",
39
+ id: 1,
40
+ job: "primarque",
41
+ last_sign_in_at: null,
42
+ magic_link_sent_at: null,
43
+ magic_link_token: null,
44
+ needs_inclusionconnect_onboarding_help: false,
45
+ needs_inclusionconnect_welcome_page: false,
46
+ phone_number: "i",
47
+ reset_password_sent_at: null,
48
+ reset_password_token: null,
49
+ sign_in_count: 0,
50
+ totp_key_verified_at: null,
51
+ updated_at: new Date("4444-04-04"),
52
+ verify_email_sent_at: null,
53
+ verify_email_token: null,
54
+ });
55
+ });
56
+
57
+ test("❎ fail to find the God-Emperor of Mankind", async () => {
58
+ const user = await findByEmail("the God-Emperor of Mankind");
59
+
60
+ assert.deepEqual(user, undefined);
61
+ });
62
+ });
@@ -0,0 +1,22 @@
1
+ //
2
+
3
+ import type { DatabaseContext, User } from "#src/types";
4
+ import { type QueryResult } from "pg";
5
+
6
+ //
7
+
8
+ export function findByEmailFactory({ pg }: DatabaseContext) {
9
+ return async function findByEmail(email: string) {
10
+ const { rows }: QueryResult<User> = await pg.query(
11
+ `
12
+ SELECT *
13
+ FROM users WHERE email = $1
14
+ `,
15
+ [email],
16
+ );
17
+
18
+ return rows.shift();
19
+ };
20
+ }
21
+
22
+ export type FindByEmailHandler = ReturnType<typeof findByEmailFactory>;