@endora-commerce/mod-customer-accounts 0.100.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/customer-groups-client.d.ts +27 -0
  4. package/dist/admin/api/customer-groups-client.d.ts.map +1 -0
  5. package/dist/admin/api/customer-groups-client.js +17 -0
  6. package/dist/admin/api/customer-groups-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +33 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +38 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/CustomerGroupsPage.d.ts +18 -0
  12. package/dist/admin/pages/CustomerGroupsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/CustomerGroupsPage.js +108 -0
  14. package/dist/admin/pages/CustomerGroupsPage.js.map +1 -0
  15. package/dist/backend/entities/customer-account.entity.d.ts +111 -0
  16. package/dist/backend/entities/customer-account.entity.d.ts.map +1 -0
  17. package/dist/backend/entities/customer-account.entity.js +230 -0
  18. package/dist/backend/entities/customer-account.entity.js.map +1 -0
  19. package/dist/backend/entities/customer-group.entity.d.ts +29 -0
  20. package/dist/backend/entities/customer-group.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/customer-group.entity.js +70 -0
  22. package/dist/backend/entities/customer-group.entity.js.map +1 -0
  23. package/dist/backend/entities/password-reset-token.entity.d.ts +16 -0
  24. package/dist/backend/entities/password-reset-token.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/password-reset-token.entity.js +59 -0
  26. package/dist/backend/entities/password-reset-token.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +144 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +407 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/routes.admin.d.ts +26 -0
  32. package/dist/backend/routes.admin.d.ts.map +1 -0
  33. package/dist/backend/routes.admin.js +35 -0
  34. package/dist/backend/routes.admin.js.map +1 -0
  35. package/dist/backend/services/audit-references.d.ts +22 -0
  36. package/dist/backend/services/audit-references.d.ts.map +1 -0
  37. package/dist/backend/services/audit-references.js +46 -0
  38. package/dist/backend/services/audit-references.js.map +1 -0
  39. package/dist/backend/services/customer-account-lifecycle-ports.d.ts +168 -0
  40. package/dist/backend/services/customer-account-lifecycle-ports.d.ts.map +1 -0
  41. package/dist/backend/services/customer-account-lifecycle-ports.js +420 -0
  42. package/dist/backend/services/customer-account-lifecycle-ports.js.map +1 -0
  43. package/dist/backend/services/customer-account-ports.d.ts +143 -0
  44. package/dist/backend/services/customer-account-ports.d.ts.map +1 -0
  45. package/dist/backend/services/customer-account-ports.js +422 -0
  46. package/dist/backend/services/customer-account-ports.js.map +1 -0
  47. package/dist/backend/services/customer-auth-service.d.ts +62 -0
  48. package/dist/backend/services/customer-auth-service.d.ts.map +1 -0
  49. package/dist/backend/services/customer-auth-service.js +105 -0
  50. package/dist/backend/services/customer-auth-service.js.map +1 -0
  51. package/dist/backend/services/customer-group-read-port.d.ts +24 -0
  52. package/dist/backend/services/customer-group-read-port.d.ts.map +1 -0
  53. package/dist/backend/services/customer-group-read-port.js +44 -0
  54. package/dist/backend/services/customer-group-read-port.js.map +1 -0
  55. package/dist/backend/services/customer-group-service.d.ts +21 -0
  56. package/dist/backend/services/customer-group-service.d.ts.map +1 -0
  57. package/dist/backend/services/customer-group-service.js +97 -0
  58. package/dist/backend/services/customer-group-service.js.map +1 -0
  59. package/dist/backend/services/customer-rollup-scope.d.ts +30 -0
  60. package/dist/backend/services/customer-rollup-scope.d.ts.map +1 -0
  61. package/dist/backend/services/customer-rollup-scope.js +42 -0
  62. package/dist/backend/services/customer-rollup-scope.js.map +1 -0
  63. package/dist/backend/services/password-reset-service.d.ts +15 -0
  64. package/dist/backend/services/password-reset-service.d.ts.map +1 -0
  65. package/dist/backend/services/password-reset-service.js +90 -0
  66. package/dist/backend/services/password-reset-service.js.map +1 -0
  67. package/dist/backend/services/role-service.d.ts +22 -0
  68. package/dist/backend/services/role-service.d.ts.map +1 -0
  69. package/dist/backend/services/role-service.js +83 -0
  70. package/dist/backend/services/role-service.js.map +1 -0
  71. package/dist/backend/services/two-factor-enrolments.d.ts +18 -0
  72. package/dist/backend/services/two-factor-enrolments.d.ts.map +1 -0
  73. package/dist/backend/services/two-factor-enrolments.js +2 -0
  74. package/dist/backend/services/two-factor-enrolments.js.map +1 -0
  75. package/dist/manifest.d.ts +169 -0
  76. package/dist/manifest.d.ts.map +1 -0
  77. package/dist/manifest.js +212 -0
  78. package/dist/manifest.js.map +1 -0
  79. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.d.ts +6 -0
  80. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.d.ts.map +1 -0
  81. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.js +25 -0
  82. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.js.map +1 -0
  83. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.d.ts +34 -0
  84. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.d.ts.map +1 -0
  85. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.js +43 -0
  86. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.js.map +1 -0
  87. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.d.ts +54 -0
  88. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.d.ts.map +1 -0
  89. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.js +97 -0
  90. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.js.map +1 -0
  91. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.d.ts +12 -0
  92. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.d.ts.map +1 -0
  93. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.js +16 -0
  94. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.js.map +1 -0
  95. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.d.ts +14 -0
  96. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.d.ts.map +1 -0
  97. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.js +18 -0
  98. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.js.map +1 -0
  99. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.d.ts +22 -0
  100. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.d.ts.map +1 -0
  101. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.js +26 -0
  102. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.js.map +1 -0
  103. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.d.ts +46 -0
  104. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.d.ts.map +1 -0
  105. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.js +97 -0
  106. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.js.map +1 -0
  107. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.d.ts +31 -0
  108. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.d.ts.map +1 -0
  109. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js +35 -0
  110. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js.map +1 -0
  111. package/dist/migrations/20260825T141659_customer_accounts_organization_required.d.ts +58 -0
  112. package/dist/migrations/20260825T141659_customer_accounts_organization_required.d.ts.map +1 -0
  113. package/dist/migrations/20260825T141659_customer_accounts_organization_required.js +133 -0
  114. package/dist/migrations/20260825T141659_customer_accounts_organization_required.js.map +1 -0
  115. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts +27 -0
  116. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts.map +1 -0
  117. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.js +44 -0
  118. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.js.map +1 -0
  119. package/dist/migrations/index.d.ts +36 -0
  120. package/dist/migrations/index.d.ts.map +1 -0
  121. package/dist/migrations/index.js +47 -0
  122. package/dist/migrations/index.js.map +1 -0
  123. package/docs/customer_accounts.md +38 -0
  124. package/i18n/en.json +30 -0
  125. package/i18n/pl.json +30 -0
  126. package/package.json +94 -0
  127. package/tailwind.css +14 -0
@@ -0,0 +1,133 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * D-178 — `customer_accounts.organization_id` becomes `NOT NULL`.
4
+ *
5
+ * The column was relaxed by `20260611T140351_customer_accounts_organization_optional`
6
+ * for feature 026 US2 ("Customer accounts may exist without an Organization"), a
7
+ * design feature 051 replaced and D-178 confirms is dead: every transacting
8
+ * customer is backed by an Organization, an individual by a single-member
9
+ * **personal** one, and there is no "no-organization" scoping path
10
+ * (Constitution XI).
11
+ *
12
+ * **The constraint is the only refusal available.** MikroORM applies its tenant
13
+ * filter to `SELECT` / `UPDATE` / `DELETE` and not to `INSERT`, so no guard can
14
+ * stop a tenant-less account being written; the column can.
15
+ *
16
+ * ## Provision, then count, then refuse — never delete
17
+ *
18
+ * These rows are customer logins: a person's credentials, the owner of their
19
+ * order history and the parent of their address book. A migration that deleted
20
+ * one to satisfy a constraint would be deleting a customer, so this one
21
+ * provisions instead and raises on whatever it could not provision.
22
+ *
23
+ * Feature 051's backfill
24
+ * (`organizations/20260717T151403_organizations_personal_organizations`) ran the
25
+ * same provisioning once. It closed the population as of the moment it ran and
26
+ * closed no write path, so three producers went on filling it: standalone
27
+ * registration's two-flush window, federated sign-in (which provisioned no
28
+ * organisation at all), and the admin "un-assign" button. All three are closed
29
+ * in the merge request that carries this migration — without that, the
30
+ * constraint would convert three silent defects into three runtime failures.
31
+ *
32
+ * Two deliberate differences from that backfill:
33
+ *
34
+ * - **Soft-deleted accounts are included.** 051 skipped them with its own
35
+ * `and ca."deleted_at" is null`. A soft-deleted row still has to satisfy the
36
+ * constraint, and `customer_account.restored` would otherwise resurrect an
37
+ * account the platform cannot scope.
38
+ * - **The insert skips an account whose personal organisation already exists**,
39
+ * and a second statement links it. `tax_id` is derived from the account id
40
+ * and is globally unique, so an account that was provisioned once and then
41
+ * un-assigned would otherwise collide — the migration would abort on a
42
+ * constraint violation rather than on the sentence it wants to say.
43
+ *
44
+ * The provisioning mirrors `PersonalOrganizationService.provisionFor`: name from
45
+ * the trimmed "first last" or else the e-mail local part, `tax_id` the account
46
+ * UUID with the dashes stripped (32 hex characters — an individual has no
47
+ * company tax id), `vat_status = 'vat_exempt'`, `status = 'active'`,
48
+ * `is_personal = true`, and the placeholder registered address the column's
49
+ * `not null` demands.
50
+ *
51
+ * `organizations` is a hard `dependencies` entry of this module's manifest, so
52
+ * that module's tables exist by the time this runs.
53
+ */
54
+ export class Migration20260825T141659CustomerAccountsOrganizationRequired extends Migration {
55
+ async up() {
56
+ // 1. Provision a personal organisation for every account that has none and
57
+ // whose derived one does not already exist.
58
+ this.addSql(`
59
+ insert into "organizations" (
60
+ "id", "name", "tax_id", "status", "vat_status", "is_personal",
61
+ "registered_address", "created_at", "updated_at"
62
+ )
63
+ select
64
+ gen_random_uuid(),
65
+ coalesce(
66
+ nullif(trim(coalesce(ca."first_name", '') || ' ' || coalesce(ca."last_name", '')), ''),
67
+ split_part(ca."email", '@', 1)
68
+ ),
69
+ replace(ca."id"::text, '-', ''),
70
+ 'active',
71
+ 'vat_exempt',
72
+ true,
73
+ '{"street":"-","city":"-","postalCode":"-","country":"PL"}'::json,
74
+ now(), now()
75
+ from "customer_accounts" ca
76
+ where ca."organization_id" is null
77
+ and not exists (
78
+ select 1 from "organizations" o
79
+ where o."tax_id" = replace(ca."id"::text, '-', '')
80
+ );
81
+ `);
82
+ // 2. Link every such account to it — both the ones just created and the ones
83
+ // whose organisation survived an earlier provisioning (the state an
84
+ // un-assigned account was left in before this ruling).
85
+ //
86
+ // `is_personal` is the load-bearing word. Without it, an account whose
87
+ // derived `tax_id` happened to be held by a **company** organisation
88
+ // would be linked into that company's tenant — a stranger's — silently.
89
+ // With it, such an account is served by neither statement and falls
90
+ // through to the refusal below, which is what step 3's message describes.
91
+ this.addSql(`
92
+ update "customer_accounts" ca
93
+ set "organization_id" = o."id"
94
+ from "organizations" o
95
+ where ca."organization_id" is null
96
+ and o."is_personal" = true
97
+ and o."tax_id" = replace(ca."id"::text, '-', '');
98
+ `);
99
+ // 3. Count what is left and refuse. It can only be non-zero if an account's
100
+ // provisioning failed — a `tax_id` collision with a company organisation
101
+ // is the plausible one, since that column is globally unique. The merge
102
+ // request that hits this decides what to do; deleting is never it.
103
+ this.addSql(`
104
+ do $$
105
+ declare
106
+ remaining bigint;
107
+ sample text;
108
+ begin
109
+ select count(*) into remaining
110
+ from "customer_accounts" where "organization_id" is null;
111
+ if remaining > 0 then
112
+ select string_agg(id::text, ', ') into sample from (
113
+ select "id" from "customer_accounts"
114
+ where "organization_id" is null order by "id" limit 20
115
+ ) s;
116
+ raise exception
117
+ 'D-178: % customer_accounts row(s) still have organization_id IS NULL after provisioning. First ids: %. These rows are customer logins - do not delete them. The likely cause is a tax_id collision on organizations.tax_id, which is globally unique.',
118
+ remaining, sample;
119
+ end if;
120
+ end $$;
121
+ `);
122
+ // 4. The constraint.
123
+ this.addSql(`alter table "customer_accounts" alter column "organization_id" set not null;`);
124
+ }
125
+ async down() {
126
+ // Re-drop the constraint, and nothing else. The NULLs this migration filled
127
+ // in were a defect rather than data, so there is nothing to restore — and
128
+ // the personal organisations it created are left in place, because an
129
+ // account still points at each of them.
130
+ this.addSql(`alter table "customer_accounts" alter column "organization_id" drop not null;`);
131
+ }
132
+ }
133
+ //# sourceMappingURL=20260825T141659_customer_accounts_organization_required.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260825T141659_customer_accounts_organization_required.js","sourceRoot":"","sources":["../../src/migrations/20260825T141659_customer_accounts_organization_required.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,OAAO,4DAA6D,SAAQ,SAAS;IAChF,KAAK,CAAC,EAAE;QACf,2EAA2E;QAC3E,+CAA+C;QAC/C,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;KAuBX,CAAC,CAAC;QAEH,6EAA6E;QAC7E,uEAAuE;QACvE,0DAA0D;QAC1D,EAAE;QACF,0EAA0E;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,uEAAuE;QACvE,6EAA6E;QAC7E,IAAI,CAAC,MAAM,CAAC;;;;;;;KAOX,CAAC,CAAC;QAEH,4EAA4E;QAC5E,4EAA4E;QAC5E,2EAA2E;QAC3E,sEAAsE;QACtE,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;KAkBX,CAAC,CAAC;QAEH,qBAAqB;QACrB,IAAI,CAAC,MAAM,CAAC,8EAA8E,CAAC,CAAC;IAC9F,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,4EAA4E;QAC5E,0EAA0E;QAC1E,sEAAsE;QACtE,wCAAwC;QACxC,IAAI,CAAC,MAAM,CAAC,+EAA+E,CAAC,CAAC;IAC/F,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * The `sales_channel_customer_accounts` sales-channel bridge — the customers a channel is visible to.
4
+ *
5
+ * It was created by the platform's frozen `Migration20260430T170044CoreSalesChannelsPromote`
6
+ * until `specs/120-migration-closure-bridge-ownership/` Phase 2. Under D-226 a
7
+ * bridge between an always-present near side (`sales_channels`, a kernel table)
8
+ * and a switchable far side belongs to the far side, so this module creates it:
9
+ * an instance that does not install `customer_accounts` no longer has a migration corpus
10
+ * naming a table nothing builds.
11
+ *
12
+ * `if not exists`, because every database that has already applied the frozen
13
+ * migration has this table. The storage keys on the class name and holds no
14
+ * checksum, so nothing is re-offered there and this migration is the no-op it
15
+ * reads as; on a fresh database it is the creation.
16
+ *
17
+ * The statements are the frozen ones verbatim — same columns, same primary key,
18
+ * same two foreign keys, same index — so the two paths reach one schema. Its
19
+ * position needs nothing declared: `sales_channels` is the kernel's and
20
+ * `customer_accounts` is created inside the frozen prefix, which every
21
+ * above-watermark migration runs after.
22
+ */
23
+ export declare class Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts extends Migration {
24
+ up(): Promise<void>;
25
+ down(): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts","sourceRoot":"","sources":["../../src/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,oEAAqE,SAAQ,SAAS;IAClF,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
@@ -0,0 +1,44 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * The `sales_channel_customer_accounts` sales-channel bridge — the customers a channel is visible to.
4
+ *
5
+ * It was created by the platform's frozen `Migration20260430T170044CoreSalesChannelsPromote`
6
+ * until `specs/120-migration-closure-bridge-ownership/` Phase 2. Under D-226 a
7
+ * bridge between an always-present near side (`sales_channels`, a kernel table)
8
+ * and a switchable far side belongs to the far side, so this module creates it:
9
+ * an instance that does not install `customer_accounts` no longer has a migration corpus
10
+ * naming a table nothing builds.
11
+ *
12
+ * `if not exists`, because every database that has already applied the frozen
13
+ * migration has this table. The storage keys on the class name and holds no
14
+ * checksum, so nothing is re-offered there and this migration is the no-op it
15
+ * reads as; on a fresh database it is the creation.
16
+ *
17
+ * The statements are the frozen ones verbatim — same columns, same primary key,
18
+ * same two foreign keys, same index — so the two paths reach one schema. Its
19
+ * position needs nothing declared: `sales_channels` is the kernel's and
20
+ * `customer_accounts` is created inside the frozen prefix, which every
21
+ * above-watermark migration runs after.
22
+ */
23
+ export class Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts extends Migration {
24
+ async up() {
25
+ this.addSql(`
26
+ create table if not exists "sales_channel_customer_accounts" (
27
+ "sales_channel_id" uuid not null,
28
+ "customer_account_id" uuid not null,
29
+ constraint "sales_channel_customer_accounts_pkey"
30
+ primary key ("sales_channel_id", "customer_account_id"),
31
+ constraint "sales_channel_customer_accounts_channel_fk"
32
+ foreign key ("sales_channel_id") references "sales_channels" ("id") on delete cascade,
33
+ constraint "sales_channel_customer_accounts_customer_account_fk"
34
+ foreign key ("customer_account_id") references "customer_accounts" ("id") on delete cascade
35
+ );
36
+ `);
37
+ this.addSql('create index if not exists "sales_channel_customer_accounts_customer_account_id_index" ' +
38
+ 'on "sales_channel_customer_accounts" ("customer_account_id");');
39
+ }
40
+ async down() {
41
+ this.addSql('drop table if exists "sales_channel_customer_accounts" cascade;');
42
+ }
43
+ }
44
+ //# sourceMappingURL=20260912T094701_customer_accounts_sales_channel_customer_accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260912T094701_customer_accounts_sales_channel_customer_accounts.js","sourceRoot":"","sources":["../../src/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,oEAAqE,SAAQ,SAAS;IACxF,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;KAWX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,yFAAyF;YACvF,+DAA+D,CAClE,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,iEAAiE,CAAC,CAAC;IACjF,CAAC;CACF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which orders **this module's own** migrations
10
+ * and nothing else (feature 081). Where the block sits relative to every other
11
+ * module's is decided by the manifest `dependencies` graph.
12
+ *
13
+ * The **named** exports stay, and the asymmetry with `./backend` — which
14
+ * publishes an array and no entity class by name (D-168) — is deliberate.
15
+ * `db/migrations-registry.generated.ts` imports each class by name from this
16
+ * specifier, and a migration class name is contract in a way an entity class
17
+ * name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom
22
+ * is a query against a table nobody created.
23
+ */
24
+ import { Migration20260425T055041CustomerAccountsPasswordResetTokens } from './20260425T055041_customer_accounts_password_reset_tokens.js';
25
+ import { Migration20260611T140351CustomerAccountsOrganizationOptional } from './20260611T140351_customer_accounts_organization_optional.js';
26
+ import { Migration20260611T140403CustomerAccountsLifecycle } from './20260611T140403_customer_accounts_lifecycle.js';
27
+ import { Migration20260718T200341CustomerAccountsCustomerAccountCustomFieldValues } from './20260718T200341_customer_accounts_customer_account_custom_field_values.js';
28
+ import { Migration20260720T044255CustomerAccountsCustomerSubtreeRollup } from './20260720T044255_customer_accounts_customer_subtree_rollup.js';
29
+ import { Migration20260819T074816CustomerAccountsPasswordSetAt } from './20260819T074816_customer_accounts_password_set_at.js';
30
+ import { Migration20260819T142837CustomerAccountsFoldEmailCase } from './20260819T142837_customer_accounts_fold_email_case.js';
31
+ import { Migration20260825T124759CustomerAccountsDropLegacyTwoFactorSecret } from './20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js';
32
+ import { Migration20260825T141659CustomerAccountsOrganizationRequired } from './20260825T141659_customer_accounts_organization_required.js';
33
+ import { Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts } from './20260912T094701_customer_accounts_sales_channel_customer_accounts.js';
34
+ export declare const migrations: (typeof Migration20260425T055041CustomerAccountsPasswordResetTokens)[];
35
+ export { Migration20260425T055041CustomerAccountsPasswordResetTokens, Migration20260611T140351CustomerAccountsOrganizationOptional, Migration20260611T140403CustomerAccountsLifecycle, Migration20260718T200341CustomerAccountsCustomerAccountCustomFieldValues, Migration20260720T044255CustomerAccountsCustomerSubtreeRollup, Migration20260819T074816CustomerAccountsPasswordSetAt, Migration20260819T142837CustomerAccountsFoldEmailCase, Migration20260825T124759CustomerAccountsDropLegacyTwoFactorSecret, Migration20260825T141659CustomerAccountsOrganizationRequired, Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts, };
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,2DAA2D,EAAE,MAAM,8DAA8D,CAAC;AAC3I,OAAO,EAAE,4DAA4D,EAAE,MAAM,8DAA8D,CAAC;AAC5I,OAAO,EAAE,iDAAiD,EAAE,MAAM,kDAAkD,CAAC;AACrH,OAAO,EAAE,wEAAwE,EAAE,MAAM,6EAA6E,CAAC;AACvK,OAAO,EAAE,6DAA6D,EAAE,MAAM,gEAAgE,CAAC;AAC/I,OAAO,EAAE,qDAAqD,EAAE,MAAM,wDAAwD,CAAC;AAC/H,OAAO,EAAE,qDAAqD,EAAE,MAAM,wDAAwD,CAAC;AAC/H,OAAO,EAAE,iEAAiE,EAAE,MAAM,sEAAsE,CAAC;AACzJ,OAAO,EAAE,4DAA4D,EAAE,MAAM,8DAA8D,CAAC;AAC5I,OAAO,EAAE,oEAAoE,EAAE,MAAM,wEAAwE,CAAC;AAE9J,eAAO,MAAM,UAAU,wEAWtB,CAAC;AAEF,OAAO,EACL,2DAA2D,EAC3D,4DAA4D,EAC5D,iDAAiD,EACjD,wEAAwE,EACxE,6DAA6D,EAC7D,qDAAqD,EACrD,qDAAqD,EACrD,iEAAiE,EACjE,4DAA4D,EAC5D,oEAAoE,GACrE,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which orders **this module's own** migrations
10
+ * and nothing else (feature 081). Where the block sits relative to every other
11
+ * module's is decided by the manifest `dependencies` graph.
12
+ *
13
+ * The **named** exports stay, and the asymmetry with `./backend` — which
14
+ * publishes an array and no entity class by name (D-168) — is deliberate.
15
+ * `db/migrations-registry.generated.ts` imports each class by name from this
16
+ * specifier, and a migration class name is contract in a way an entity class
17
+ * name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom
22
+ * is a query against a table nobody created.
23
+ */
24
+ import { Migration20260425T055041CustomerAccountsPasswordResetTokens } from './20260425T055041_customer_accounts_password_reset_tokens.js';
25
+ import { Migration20260611T140351CustomerAccountsOrganizationOptional } from './20260611T140351_customer_accounts_organization_optional.js';
26
+ import { Migration20260611T140403CustomerAccountsLifecycle } from './20260611T140403_customer_accounts_lifecycle.js';
27
+ import { Migration20260718T200341CustomerAccountsCustomerAccountCustomFieldValues } from './20260718T200341_customer_accounts_customer_account_custom_field_values.js';
28
+ import { Migration20260720T044255CustomerAccountsCustomerSubtreeRollup } from './20260720T044255_customer_accounts_customer_subtree_rollup.js';
29
+ import { Migration20260819T074816CustomerAccountsPasswordSetAt } from './20260819T074816_customer_accounts_password_set_at.js';
30
+ import { Migration20260819T142837CustomerAccountsFoldEmailCase } from './20260819T142837_customer_accounts_fold_email_case.js';
31
+ import { Migration20260825T124759CustomerAccountsDropLegacyTwoFactorSecret } from './20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js';
32
+ import { Migration20260825T141659CustomerAccountsOrganizationRequired } from './20260825T141659_customer_accounts_organization_required.js';
33
+ import { Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts } from './20260912T094701_customer_accounts_sales_channel_customer_accounts.js';
34
+ export const migrations = [
35
+ Migration20260425T055041CustomerAccountsPasswordResetTokens,
36
+ Migration20260611T140351CustomerAccountsOrganizationOptional,
37
+ Migration20260611T140403CustomerAccountsLifecycle,
38
+ Migration20260718T200341CustomerAccountsCustomerAccountCustomFieldValues,
39
+ Migration20260720T044255CustomerAccountsCustomerSubtreeRollup,
40
+ Migration20260819T074816CustomerAccountsPasswordSetAt,
41
+ Migration20260819T142837CustomerAccountsFoldEmailCase,
42
+ Migration20260825T124759CustomerAccountsDropLegacyTwoFactorSecret,
43
+ Migration20260825T141659CustomerAccountsOrganizationRequired,
44
+ Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts,
45
+ ];
46
+ export { Migration20260425T055041CustomerAccountsPasswordResetTokens, Migration20260611T140351CustomerAccountsOrganizationOptional, Migration20260611T140403CustomerAccountsLifecycle, Migration20260718T200341CustomerAccountsCustomerAccountCustomFieldValues, Migration20260720T044255CustomerAccountsCustomerSubtreeRollup, Migration20260819T074816CustomerAccountsPasswordSetAt, Migration20260819T142837CustomerAccountsFoldEmailCase, Migration20260825T124759CustomerAccountsDropLegacyTwoFactorSecret, Migration20260825T141659CustomerAccountsOrganizationRequired, Migration20260912T094701CustomerAccountsSalesChannelCustomerAccounts, };
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,2DAA2D,EAAE,MAAM,8DAA8D,CAAC;AAC3I,OAAO,EAAE,4DAA4D,EAAE,MAAM,8DAA8D,CAAC;AAC5I,OAAO,EAAE,iDAAiD,EAAE,MAAM,kDAAkD,CAAC;AACrH,OAAO,EAAE,wEAAwE,EAAE,MAAM,6EAA6E,CAAC;AACvK,OAAO,EAAE,6DAA6D,EAAE,MAAM,gEAAgE,CAAC;AAC/I,OAAO,EAAE,qDAAqD,EAAE,MAAM,wDAAwD,CAAC;AAC/H,OAAO,EAAE,qDAAqD,EAAE,MAAM,wDAAwD,CAAC;AAC/H,OAAO,EAAE,iEAAiE,EAAE,MAAM,sEAAsE,CAAC;AACzJ,OAAO,EAAE,4DAA4D,EAAE,MAAM,8DAA8D,CAAC;AAC5I,OAAO,EAAE,oEAAoE,EAAE,MAAM,wEAAwE,CAAC;AAE9J,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,2DAA2D;IAC3D,4DAA4D;IAC5D,iDAAiD;IACjD,wEAAwE;IACxE,6DAA6D;IAC7D,qDAAqD;IACrD,qDAAqD;IACrD,iEAAiE;IACjE,4DAA4D;IAC5D,oEAAoE;CACrE,CAAC;AAEF,OAAO,EACL,2DAA2D,EAC3D,4DAA4D,EAC5D,iDAAiD,EACjD,wEAAwE,EACxE,6DAA6D,EAC7D,qDAAqD,EACrD,qDAAqD,EACrD,iEAAiE,EACjE,4DAA4D,EAC5D,oEAAoE,GACrE,CAAC"}
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: customer_accounts
3
+ description: Customer login, password reset, 2FA, role assignment
4
+ ---
5
+
6
+ # `customer_accounts`
7
+
8
+ Per-user state for Customers: login, password management, optional 2FA,
9
+ role within their Organization. Backed by the shared `auth` session
10
+ service; this module owns the customer-facing identity surface.
11
+
12
+ ## Public surface
13
+
14
+ | Verb + Path | Purpose |
15
+ | --- | --- |
16
+ | `POST /api/v1/auth/login` | Email + password (+ TOTP step if 2FA enabled) |
17
+ | `POST /api/v1/auth/logout` | Destroy session |
18
+ | `POST /api/v1/me/password` | Change password (rejects wrong `currentPassword`) |
19
+ | `POST /api/v1/auth/password-reset/request` | Begin reset flow |
20
+ | `POST /api/v1/auth/password-reset/confirm` | Redeem reset token |
21
+
22
+ ## Entities
23
+
24
+ `CustomerAccount` (email, passwordHash, role), `PasswordResetToken`. Role is
25
+ enum `organization_admin | regular_user`.
26
+
27
+ ## Extension points
28
+
29
+ - **Password policy** — `password-hasher.ts` wraps argon2id; tune cost
30
+ parameters there.
31
+ - **Backup codes and second factors** — not this module's. Customer and admin
32
+ 2FA is the `mfa` module's, over `mfa_enrolments`, served at
33
+ `/api/v1/account/mfa/*`; recovery codes are single-use rows there. The
34
+ superseded scaffolding this bullet used to point at
35
+ (`totp-enrolment-service.ts`) was deleted on 2026-08-25 — it could never
36
+ complete a single enrolment.
37
+ - **Login throttling** — relies on Fastify `@fastify/rate-limit` at server
38
+ level; per-account lockout would be added here.
package/i18n/en.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "errors.CANNOT_DEMOTE_LAST_ADMIN": "This is the only administrator in the organization, and an organization always keeps at least one — demoting them would leave nobody able to manage members, addresses or approvals. Promote another member to administrator first, and then this role can be changed.",
3
+ "errors.CANNOT_REMOVE_LAST_ADMIN": "This is the only administrator in the organization, and an organization always keeps at least one — removing them would leave nobody able to manage members, addresses or approvals. Promote another member to administrator first, and then this one can be removed.",
4
+ "actions.openCustomerGroups.label": "Customer groups",
5
+ "actions.openCustomerGroups.description": "Manage the segmentation groups customers and organizations are assigned to",
6
+ "groups.page.title": "Customer groups",
7
+ "groups.page.description": "A customer group is a segment you assign to a customer or to an organization. Pricing rules, promotions and push audiences target these groups; a customer's own group wins over the one inherited from their organization.",
8
+ "groups.error.load": "Failed to load customer groups.",
9
+ "groups.error.save": "Failed to save the customer group.",
10
+ "groups.error.delete": "Failed to delete the customer group.",
11
+ "groups.loading": "Loading customer groups…",
12
+ "groups.empty": "No customer groups yet.",
13
+ "groups.column.code": "Code",
14
+ "groups.column.name": "Name",
15
+ "groups.column.description": "Description",
16
+ "groups.form.newTitle": "New customer group",
17
+ "groups.form.editTitle": "Edit “{code}”",
18
+ "groups.field.code": "Code",
19
+ "groups.field.code.help": "Lower-case identifier used by pricing rules and promotions. It cannot be changed after the group is created.",
20
+ "groups.field.name": "Name",
21
+ "groups.field.description": "Description",
22
+ "groups.action.edit": "Edit",
23
+ "groups.action.delete": "Delete",
24
+ "groups.action.save": "Save",
25
+ "groups.action.cancel": "Cancel",
26
+ "groups.action.delete.confirm": "Delete customer group “{code}”? Customers and organizations assigned to it keep their records and lose the assignment.",
27
+ "groups.message.saved": "Customer group “{code}” saved.",
28
+ "groups.message.deleted": "Customer group deleted.",
29
+ "nav.customerGroups.label": "Customer groups"
30
+ }
package/i18n/pl.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "errors.CANNOT_DEMOTE_LAST_ADMIN": "To jedyny administrator w tej organizacji, a organizacja zawsze musi mieć co najmniej jednego — po odebraniu mu tej roli nikt nie mógłby zarządzać użytkownikami, adresami ani akceptacjami. Najpierw nadaj rolę administratora innemu użytkownikowi, a potem tę rolę będzie można zmienić.",
3
+ "errors.CANNOT_REMOVE_LAST_ADMIN": "To jedyny administrator w tej organizacji, a organizacja zawsze musi mieć co najmniej jednego — po jego usunięciu nikt nie mógłby zarządzać użytkownikami, adresami ani akceptacjami. Najpierw nadaj rolę administratora innemu użytkownikowi, a potem tego będzie można usunąć.",
4
+ "actions.openCustomerGroups.label": "Grupy klientów",
5
+ "actions.openCustomerGroups.description": "Zarządzaj grupami segmentacji przypisywanymi klientom i organizacjom",
6
+ "groups.page.title": "Grupy klientów",
7
+ "groups.page.description": "Grupa klientów to segment przypisywany klientowi lub organizacji. Reguły cenowe, promocje i odbiorcy powiadomień push celują w te grupy; grupa przypisana bezpośrednio klientowi ma pierwszeństwo przed grupą odziedziczoną z organizacji.",
8
+ "groups.error.load": "Nie udało się wczytać grup klientów.",
9
+ "groups.error.save": "Nie udało się zapisać grupy klientów.",
10
+ "groups.error.delete": "Nie udało się usunąć grupy klientów.",
11
+ "groups.loading": "Wczytywanie grup klientów…",
12
+ "groups.empty": "Nie ma jeszcze żadnych grup klientów.",
13
+ "groups.column.code": "Kod",
14
+ "groups.column.name": "Nazwa",
15
+ "groups.column.description": "Opis",
16
+ "groups.form.newTitle": "Nowa grupa klientów",
17
+ "groups.form.editTitle": "Edycja „{code}”",
18
+ "groups.field.code": "Kod",
19
+ "groups.field.code.help": "Identyfikator pisany małymi literami, używany przez reguły cenowe i promocje. Po utworzeniu grupy nie można go zmienić.",
20
+ "groups.field.name": "Nazwa",
21
+ "groups.field.description": "Opis",
22
+ "groups.action.edit": "Edytuj",
23
+ "groups.action.delete": "Usuń",
24
+ "groups.action.save": "Zapisz",
25
+ "groups.action.cancel": "Anuluj",
26
+ "groups.action.delete.confirm": "Usunąć grupę klientów „{code}”? Klienci i organizacje do niej przypisani zachowają swoje dane i utracą to przypisanie.",
27
+ "groups.message.saved": "Grupa klientów „{code}” została zapisana.",
28
+ "groups.message.deleted": "Grupa klientów została usunięta.",
29
+ "nav.customerGroups.label": "Grupy klientów"
30
+ }
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@endora-commerce/mod-customer-accounts",
3
+ "version": "0.100.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "Customer (B2B) account records, including organization membership and role.",
7
+ "license": "MIT",
8
+ "endora": {
9
+ "type": "module",
10
+ "id": "customer_accounts"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/endora-commerce/endora-commerce.git",
15
+ "directory": "packages/modules/customer_accounts"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/manifest.d.ts",
23
+ "default": "./dist/manifest.js"
24
+ },
25
+ "./backend": {
26
+ "types": "./dist/backend/index.d.ts",
27
+ "default": "./dist/backend/index.js"
28
+ },
29
+ "./migrations": {
30
+ "types": "./dist/migrations/index.d.ts",
31
+ "default": "./dist/migrations/index.js"
32
+ },
33
+ "./admin": {
34
+ "types": "./dist/admin/index.d.ts",
35
+ "default": "./dist/admin/index.js"
36
+ },
37
+ "./tailwind.css": "./tailwind.css",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "i18n",
43
+ "docs",
44
+ "tailwind.css"
45
+ ],
46
+ "engines": {
47
+ "node": ">=22.18.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@mikro-orm/core": "^6",
51
+ "@mikro-orm/migrations": "^6",
52
+ "@mikro-orm/postgresql": "^6",
53
+ "fastify": "^5",
54
+ "lucide-react": "^1",
55
+ "react": "^19",
56
+ "zod": "^4",
57
+ "@endora-commerce/admin-kit": "0.100.0",
58
+ "@endora-commerce/contracts": "0.100.0",
59
+ "@endora-commerce/mod-organizations": "0.100.0",
60
+ "@endora-commerce/platform": "0.100.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@endora-commerce/admin-kit": {
64
+ "optional": true
65
+ },
66
+ "lucide-react": {
67
+ "optional": true
68
+ },
69
+ "react": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "devDependencies": {
74
+ "@mikro-orm/core": "^6.6.13",
75
+ "@mikro-orm/migrations": "^6.6.13",
76
+ "@mikro-orm/postgresql": "^6.6.13",
77
+ "@types/node": "^22.9.0",
78
+ "@types/react": "^19.2.14",
79
+ "fastify": "^5.12.5",
80
+ "lucide-react": "^1.11.0",
81
+ "react": "^19.2.5",
82
+ "typescript": "^5.9.3",
83
+ "zod": "^4.2.0",
84
+ "@endora-commerce/admin-kit": "0.100.0",
85
+ "@endora-commerce/contracts": "0.100.0",
86
+ "@endora-commerce/mod-organizations": "0.100.0",
87
+ "@endora-commerce/platform": "0.100.0"
88
+ },
89
+ "scripts": {
90
+ "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
91
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
92
+ "lint": "eslint src"
93
+ }
94
+ }
package/tailwind.css ADDED
@@ -0,0 +1,14 @@
1
+ /* @endora-commerce/mod-customer-accounts — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
2
+ *
3
+ * The `@source` directives this package asks its host to scan
4
+ * (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
5
+ * They resolve relative to **this file**, so they hold wherever the package is
6
+ * installed — a workspace link here, `node_modules` in a client's instance.
7
+ *
8
+ * The `dist` line is what a published tarball ships and is what an instance
9
+ * scans; the `src` line is inert there and is what keeps `pnpm --filter admin
10
+ * run dev` reading source in this repository. Do not edit: run
11
+ * `pnpm --filter backend run manifests:generate`.
12
+ */
13
+ @source "./dist/admin";
14
+ @source "./src/admin";