@endora-commerce/mod-organizations 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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/components/OrganizationSalesRepsTab.d.ts +27 -0
- package/dist/admin/components/OrganizationSalesRepsTab.d.ts.map +1 -0
- package/dist/admin/components/OrganizationSalesRepsTab.js +170 -0
- package/dist/admin/components/OrganizationSalesRepsTab.js.map +1 -0
- package/dist/admin/index.d.ts +30 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +59 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/OrganizationDetail.d.ts +9 -0
- package/dist/admin/pages/OrganizationDetail.d.ts.map +1 -0
- package/dist/admin/pages/OrganizationDetail.js +247 -0
- package/dist/admin/pages/OrganizationDetail.js.map +1 -0
- package/dist/admin/pages/OrganizationsList.d.ts +9 -0
- package/dist/admin/pages/OrganizationsList.d.ts.map +1 -0
- package/dist/admin/pages/OrganizationsList.js +73 -0
- package/dist/admin/pages/OrganizationsList.js.map +1 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.d.ts +12 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.d.ts.map +1 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.js +49 -0
- package/dist/admin/panels/ApplicablePriceListsPanel.js.map +1 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.d.ts +16 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.d.ts.map +1 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.js +29 -0
- package/dist/admin/panels/FulfilmentStrategyPanel.js.map +1 -0
- package/dist/admin/panels/HierarchyPanel.d.ts +15 -0
- package/dist/admin/panels/HierarchyPanel.d.ts.map +1 -0
- package/dist/admin/panels/HierarchyPanel.js +65 -0
- package/dist/admin/panels/HierarchyPanel.js.map +1 -0
- package/dist/admin/panels/ModerationActionsPanel.d.ts +28 -0
- package/dist/admin/panels/ModerationActionsPanel.d.ts.map +1 -0
- package/dist/admin/panels/ModerationActionsPanel.js +111 -0
- package/dist/admin/panels/ModerationActionsPanel.js.map +1 -0
- package/dist/admin/panels/RestrictionsPanel.d.ts +24 -0
- package/dist/admin/panels/RestrictionsPanel.d.ts.map +1 -0
- package/dist/admin/panels/RestrictionsPanel.js +121 -0
- package/dist/admin/panels/RestrictionsPanel.js.map +1 -0
- package/dist/admin/panels/VatValidationPanel.d.ts +28 -0
- package/dist/admin/panels/VatValidationPanel.d.ts.map +1 -0
- package/dist/admin/panels/VatValidationPanel.js +79 -0
- package/dist/admin/panels/VatValidationPanel.js.map +1 -0
- package/dist/backend/commands/move.command.d.ts +11 -0
- package/dist/backend/commands/move.command.d.ts.map +1 -0
- package/dist/backend/commands/move.command.js +11 -0
- package/dist/backend/commands/move.command.js.map +1 -0
- package/dist/backend/commands/set-cart-approval-policy.command.d.ts +31 -0
- package/dist/backend/commands/set-cart-approval-policy.command.d.ts.map +1 -0
- package/dist/backend/commands/set-cart-approval-policy.command.js +56 -0
- package/dist/backend/commands/set-cart-approval-policy.command.js.map +1 -0
- package/dist/backend/commands/set-credit-mode.command.d.ts +16 -0
- package/dist/backend/commands/set-credit-mode.command.d.ts.map +1 -0
- package/dist/backend/commands/set-credit-mode.command.js +25 -0
- package/dist/backend/commands/set-credit-mode.command.js.map +1 -0
- package/dist/backend/commands/set-parent.command.d.ts +26 -0
- package/dist/backend/commands/set-parent.command.d.ts.map +1 -0
- package/dist/backend/commands/set-parent.command.js +51 -0
- package/dist/backend/commands/set-parent.command.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +20 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +11 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +44 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +44 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +20 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +28 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/email-templates/invitation.d.ts +23 -0
- package/dist/backend/email-templates/invitation.d.ts.map +1 -0
- package/dist/backend/email-templates/invitation.js +35 -0
- package/dist/backend/email-templates/invitation.js.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts +54 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.js +242 -0
- package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
- package/dist/backend/email-templates/verification.d.ts +14 -0
- package/dist/backend/email-templates/verification.d.ts.map +1 -0
- package/dist/backend/email-templates/verification.js +34 -0
- package/dist/backend/email-templates/verification.js.map +1 -0
- package/dist/backend/entities/email-verification-token.entity.d.ts +17 -0
- package/dist/backend/entities/email-verification-token.entity.d.ts.map +1 -0
- package/dist/backend/entities/email-verification-token.entity.js +60 -0
- package/dist/backend/entities/email-verification-token.entity.js.map +1 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.d.ts +14 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.js +41 -0
- package/dist/backend/entities/organization-delivery-method-link.entity.js.map +1 -0
- package/dist/backend/entities/organization-invitation.entity.d.ts +21 -0
- package/dist/backend/entities/organization-invitation.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-invitation.entity.js +85 -0
- package/dist/backend/entities/organization-invitation.entity.js.map +1 -0
- package/dist/backend/entities/organization-payment-method-link.entity.d.ts +14 -0
- package/dist/backend/entities/organization-payment-method-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-payment-method-link.entity.js +41 -0
- package/dist/backend/entities/organization-payment-method-link.entity.js.map +1 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.d.ts +17 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.js +56 -0
- package/dist/backend/entities/organization-sales-rep-assignment.entity.js.map +1 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.d.ts +29 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.js +78 -0
- package/dist/backend/entities/organization-tax-id-validation.entity.js.map +1 -0
- package/dist/backend/entities/organization-warehouse-link.entity.d.ts +15 -0
- package/dist/backend/entities/organization-warehouse-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization-warehouse-link.entity.js +42 -0
- package/dist/backend/entities/organization-warehouse-link.entity.js.map +1 -0
- package/dist/backend/entities/organization.entity.d.ts +125 -0
- package/dist/backend/entities/organization.entity.d.ts.map +1 -0
- package/dist/backend/entities/organization.entity.js +282 -0
- package/dist/backend/entities/organization.entity.js.map +1 -0
- package/dist/backend/index.d.ts +250 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +506 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.d.ts +22 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.d.ts.map +1 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.js +172 -0
- package/dist/backend/integrations/ministerstwo-finansow-client.js.map +1 -0
- package/dist/backend/integrations/vies-client.d.ts +18 -0
- package/dist/backend/integrations/vies-client.d.ts.map +1 -0
- package/dist/backend/integrations/vies-client.js +133 -0
- package/dist/backend/integrations/vies-client.js.map +1 -0
- package/dist/backend/plugin.d.ts +126 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +139 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +66 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +711 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.customer.d.ts +33 -0
- package/dist/backend/routes.customer.d.ts.map +1 -0
- package/dist/backend/routes.customer.js +139 -0
- package/dist/backend/routes.customer.js.map +1 -0
- package/dist/backend/routes.members.d.ts +31 -0
- package/dist/backend/routes.members.d.ts.map +1 -0
- package/dist/backend/routes.members.js +109 -0
- package/dist/backend/routes.members.js.map +1 -0
- package/dist/backend/routes.public.d.ts +49 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +220 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/routes.sales-reps.d.ts +46 -0
- package/dist/backend/routes.sales-reps.d.ts.map +1 -0
- package/dist/backend/routes.sales-reps.js +114 -0
- package/dist/backend/routes.sales-reps.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +25 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +65 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/schemas/organization.d.ts +59 -0
- package/dist/backend/schemas/organization.d.ts.map +1 -0
- package/dist/backend/schemas/organization.js +55 -0
- package/dist/backend/schemas/organization.js.map +1 -0
- package/dist/backend/schemas/settings.d.ts +30 -0
- package/dist/backend/schemas/settings.d.ts.map +1 -0
- package/dist/backend/schemas/settings.js +35 -0
- package/dist/backend/schemas/settings.js.map +1 -0
- package/dist/backend/services/email-verification-service.d.ts +45 -0
- package/dist/backend/services/email-verification-service.d.ts.map +1 -0
- package/dist/backend/services/email-verification-service.js +95 -0
- package/dist/backend/services/email-verification-service.js.map +1 -0
- package/dist/backend/services/invitation-service.d.ts +85 -0
- package/dist/backend/services/invitation-service.d.ts.map +1 -0
- package/dist/backend/services/invitation-service.js +266 -0
- package/dist/backend/services/invitation-service.js.map +1 -0
- package/dist/backend/services/normalize-name.d.ts +14 -0
- package/dist/backend/services/normalize-name.d.ts.map +1 -0
- package/dist/backend/services/normalize-name.js +14 -0
- package/dist/backend/services/normalize-name.js.map +1 -0
- package/dist/backend/services/org-registration-notifier.d.ts +36 -0
- package/dist/backend/services/org-registration-notifier.d.ts.map +1 -0
- package/dist/backend/services/org-registration-notifier.js +109 -0
- package/dist/backend/services/org-registration-notifier.js.map +1 -0
- package/dist/backend/services/org-template-email.d.ts +18 -0
- package/dist/backend/services/org-template-email.d.ts.map +1 -0
- package/dist/backend/services/org-template-email.js +16 -0
- package/dist/backend/services/org-template-email.js.map +1 -0
- package/dist/backend/services/organization-context-service.d.ts +52 -0
- package/dist/backend/services/organization-context-service.d.ts.map +1 -0
- package/dist/backend/services/organization-context-service.js +70 -0
- package/dist/backend/services/organization-context-service.js.map +1 -0
- package/dist/backend/services/organization-country-reference.d.ts +11 -0
- package/dist/backend/services/organization-country-reference.d.ts.map +1 -0
- package/dist/backend/services/organization-country-reference.js +24 -0
- package/dist/backend/services/organization-country-reference.js.map +1 -0
- package/dist/backend/services/organization-details-port.d.ts +33 -0
- package/dist/backend/services/organization-details-port.d.ts.map +1 -0
- package/dist/backend/services/organization-details-port.js +95 -0
- package/dist/backend/services/organization-details-port.js.map +1 -0
- package/dist/backend/services/organization-effective-pricelists-service.d.ts +50 -0
- package/dist/backend/services/organization-effective-pricelists-service.d.ts.map +1 -0
- package/dist/backend/services/organization-effective-pricelists-service.js +68 -0
- package/dist/backend/services/organization-effective-pricelists-service.js.map +1 -0
- package/dist/backend/services/organization-inheritance-service.d.ts +73 -0
- package/dist/backend/services/organization-inheritance-service.d.ts.map +1 -0
- package/dist/backend/services/organization-inheritance-service.js +63 -0
- package/dist/backend/services/organization-inheritance-service.js.map +1 -0
- package/dist/backend/services/organization-moderation-service.d.ts +124 -0
- package/dist/backend/services/organization-moderation-service.d.ts.map +1 -0
- package/dist/backend/services/organization-moderation-service.js +337 -0
- package/dist/backend/services/organization-moderation-service.js.map +1 -0
- package/dist/backend/services/organization-restriction-service.d.ts +70 -0
- package/dist/backend/services/organization-restriction-service.d.ts.map +1 -0
- package/dist/backend/services/organization-restriction-service.js +210 -0
- package/dist/backend/services/organization-restriction-service.js.map +1 -0
- package/dist/backend/services/organization-tax-id-validation-service.d.ts +59 -0
- package/dist/backend/services/organization-tax-id-validation-service.d.ts.map +1 -0
- package/dist/backend/services/organization-tax-id-validation-service.js +115 -0
- package/dist/backend/services/organization-tax-id-validation-service.js.map +1 -0
- package/dist/backend/services/organization-tree-service.d.ts +102 -0
- package/dist/backend/services/organization-tree-service.d.ts.map +1 -0
- package/dist/backend/services/organization-tree-service.js +184 -0
- package/dist/backend/services/organization-tree-service.js.map +1 -0
- package/dist/backend/services/personal-organization-service.d.ts +130 -0
- package/dist/backend/services/personal-organization-service.d.ts.map +1 -0
- package/dist/backend/services/personal-organization-service.js +207 -0
- package/dist/backend/services/personal-organization-service.js.map +1 -0
- package/dist/backend/services/registration-service.d.ts +80 -0
- package/dist/backend/services/registration-service.d.ts.map +1 -0
- package/dist/backend/services/registration-service.js +126 -0
- package/dist/backend/services/registration-service.js.map +1 -0
- package/dist/backend/services/sales-rep-assignment-service.d.ts +89 -0
- package/dist/backend/services/sales-rep-assignment-service.d.ts.map +1 -0
- package/dist/backend/services/sales-rep-assignment-service.js +176 -0
- package/dist/backend/services/sales-rep-assignment-service.js.map +1 -0
- package/dist/backend/services/vat-validator-port.d.ts +20 -0
- package/dist/backend/services/vat-validator-port.d.ts.map +1 -0
- package/dist/backend/services/vat-validator-port.js +2 -0
- package/dist/backend/services/vat-validator-port.js.map +1 -0
- package/dist/manifest.d.ts +176 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +503 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260424T205317_organizations_init.d.ts +13 -0
- package/dist/migrations/20260424T205317_organizations_init.d.ts.map +1 -0
- package/dist/migrations/20260424T205317_organizations_init.js +100 -0
- package/dist/migrations/20260424T205317_organizations_init.js.map +1 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.d.ts +13 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.d.ts.map +1 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.js +39 -0
- package/dist/migrations/20260425T051700_organizations_invitations_init.js.map +1 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.d.ts +35 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.d.ts.map +1 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.js +180 -0
- package/dist/migrations/20260611T140349_organizations_consolidation.js.map +1 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.d.ts +10 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.d.ts.map +1 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.js +14 -0
- package/dist/migrations/20260611T140358_organizations_org_order_confirmation_emails.js.map +1 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.d.ts +14 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.d.ts.map +1 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.js +20 -0
- package/dist/migrations/20260611T140402_organizations_org_fulfilment_strategy.js.map +1 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.d.ts +24 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.d.ts.map +1 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.js +63 -0
- package/dist/migrations/20260717T151403_organizations_personal_organizations.js.map +1 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.d.ts +12 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.d.ts.map +1 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.js +16 -0
- package/dist/migrations/20260718T200340_organizations_organization_custom_field_values.js.map +1 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.d.ts +23 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.d.ts.map +1 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.js +37 -0
- package/dist/migrations/20260720T044254_organizations_org_hierarchy.js.map +1 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.d.ts +27 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.d.ts.map +1 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.js +44 -0
- package/dist/migrations/20260912T094646_organizations_sales_channel_organizations.js.map +1 -0
- package/dist/migrations/index.d.ts +35 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +45 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +53 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/organization-hierarchy.md +117 -0
- package/docs/organizations.md +312 -0
- package/i18n/en.json +17 -0
- package/i18n/pl.json +17 -0
- package/package.json +107 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schemas for the two settings keys this feature introduces.
|
|
4
|
+
*
|
|
5
|
+
* The Settings module persists values as `unknown` (it's a generic
|
|
6
|
+
* key-value store); these schemas are how the moderation service +
|
|
7
|
+
* notification dispatcher coerce + validate at the read-side.
|
|
8
|
+
*/
|
|
9
|
+
export declare const moderationModeSchema: z.ZodEnum<{
|
|
10
|
+
manual: "manual";
|
|
11
|
+
auto: "auto";
|
|
12
|
+
}>;
|
|
13
|
+
export type ModerationMode = z.infer<typeof moderationModeSchema>;
|
|
14
|
+
export declare const notificationRecipientsSchema: z.ZodPipe<z.ZodArray<z.ZodString>, z.ZodTransform<string[], string[]>>;
|
|
15
|
+
export type NotificationRecipients = z.infer<typeof notificationRecipientsSchema>;
|
|
16
|
+
/**
|
|
17
|
+
* Feature 056 — how a parent Organization's credit limit is consumed by
|
|
18
|
+
* sub-organizations with no own limit.
|
|
19
|
+
*
|
|
20
|
+
* Feature 072 (T138) moved this here. Both composition roots spelled it as an
|
|
21
|
+
* inline `z.enum([…])` behind a dynamic `import('zod')`, once each — the only
|
|
22
|
+
* one of this module's three setting schemas that did not live beside its two
|
|
23
|
+
* siblings, which is why the duplication went unnoticed.
|
|
24
|
+
*/
|
|
25
|
+
export declare const creditInheritanceModeSchema: z.ZodEnum<{
|
|
26
|
+
shared_pool: "shared_pool";
|
|
27
|
+
independent_default: "independent_default";
|
|
28
|
+
}>;
|
|
29
|
+
export type CreditInheritanceMode = z.infer<typeof creditInheritanceModeSchema>;
|
|
30
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/backend/schemas/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB;;;EAA6B,CAAC;AAC/D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,4BAA4B,wEAarC,CAAC;AACL,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B;;;EAAiD,CAAC;AAC1F,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schemas for the two settings keys this feature introduces.
|
|
4
|
+
*
|
|
5
|
+
* The Settings module persists values as `unknown` (it's a generic
|
|
6
|
+
* key-value store); these schemas are how the moderation service +
|
|
7
|
+
* notification dispatcher coerce + validate at the read-side.
|
|
8
|
+
*/
|
|
9
|
+
export const moderationModeSchema = z.enum(['manual', 'auto']);
|
|
10
|
+
export const notificationRecipientsSchema = z
|
|
11
|
+
.array(z.string().email().max(320))
|
|
12
|
+
.max(50)
|
|
13
|
+
.transform((entries) => {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const raw of entries) {
|
|
17
|
+
const key = raw.trim().toLowerCase();
|
|
18
|
+
if (!key || seen.has(key))
|
|
19
|
+
continue;
|
|
20
|
+
seen.add(key);
|
|
21
|
+
out.push(raw.trim());
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Feature 056 — how a parent Organization's credit limit is consumed by
|
|
27
|
+
* sub-organizations with no own limit.
|
|
28
|
+
*
|
|
29
|
+
* Feature 072 (T138) moved this here. Both composition roots spelled it as an
|
|
30
|
+
* inline `z.enum([…])` behind a dynamic `import('zod')`, once each — the only
|
|
31
|
+
* one of this module's three setting schemas that did not live beside its two
|
|
32
|
+
* siblings, which is why the duplication went unnoticed.
|
|
33
|
+
*/
|
|
34
|
+
export const creditInheritanceModeSchema = z.enum(['shared_pool', 'independent_default']);
|
|
35
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/backend/schemas/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAG/D,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC;KAC1C,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;KAClC,GAAG,CAAC,EAAE,CAAC;KACP,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE;IACrB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC,CAAC;AAGL;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import { type CustomerAccountMemberWritePort, type CustomerAccountReadPort } from '@endora-commerce/contracts';
|
|
3
|
+
import type { OrganizationEventBus } from './registration-service.js';
|
|
4
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
5
|
+
/**
|
|
6
|
+
* Email verification flow (T118).
|
|
7
|
+
*
|
|
8
|
+
* Consumes a raw token, looks up the matching EmailVerificationToken row by
|
|
9
|
+
* sha256 hash, marks it consumed (one-shot), sets CustomerAccount.emailVerifiedAt,
|
|
10
|
+
* and transitions the Organization to `active`. Emits organization.verified.v1.
|
|
11
|
+
*/
|
|
12
|
+
export interface EmailVerificationAccountPorts {
|
|
13
|
+
/**
|
|
14
|
+
* `customer_accounts`' published read and write (feature 075, Phase C).
|
|
15
|
+
* Verifying an address stamps a column on *that* module's row; this service
|
|
16
|
+
* used to load the entity and assign to it.
|
|
17
|
+
*
|
|
18
|
+
* With `customer_accounts` off the verification fails closed, which is the
|
|
19
|
+
* only coherent answer for a flow whose whole subject is an account.
|
|
20
|
+
*/
|
|
21
|
+
read: CustomerAccountReadPort;
|
|
22
|
+
write: CustomerAccountMemberWritePort;
|
|
23
|
+
}
|
|
24
|
+
export declare class EmailVerificationService {
|
|
25
|
+
private readonly emFactory;
|
|
26
|
+
private readonly events;
|
|
27
|
+
private readonly accounts;
|
|
28
|
+
private readonly auditLog?;
|
|
29
|
+
constructor(emFactory: () => EntityManager, events: OrganizationEventBus, accounts: EmailVerificationAccountPorts, auditLog?: AuditPort | undefined);
|
|
30
|
+
verify(rawToken: string): Promise<{
|
|
31
|
+
organizationId: string;
|
|
32
|
+
customerAccountId: string;
|
|
33
|
+
verifiedAt: Date;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Test-only helper used by the `/api/v1/_test/latest-verification-token`
|
|
37
|
+
* probe — exposes the most recent UNCONSUMED, unexpired token so integration
|
|
38
|
+
* tests can complete the verification step without touching Mailhog.
|
|
39
|
+
*
|
|
40
|
+
* Returns null if no such token exists. Callers gate this behind an env
|
|
41
|
+
* check so the probe is never exposed outside of tests.
|
|
42
|
+
*/
|
|
43
|
+
latestUnconsumedTokenId(): Promise<string | null>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=email-verification-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-verification-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-verification-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAIpC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;;;;;;OAOG;IACH,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,8BAA8B,CAAC;CACvC;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAHT,SAAS,EAAE,MAAM,aAAa,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,6BAA6B,EACvC,QAAQ,CAAC,EAAE,SAAS,YAAA;IAGjC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,IAAI,CAAC;KAClB,CAAC;IAyFF;;;;;;;OAOG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CASxD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
import { ERROR_CODES, } from '@endora-commerce/contracts';
|
|
3
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
4
|
+
import { Organization } from '../entities/organization.entity.js';
|
|
5
|
+
import { EmailVerificationToken } from '../entities/email-verification-token.entity.js';
|
|
6
|
+
import { recordAuditFromContext } from '@endora-commerce/platform/commands';
|
|
7
|
+
export class EmailVerificationService {
|
|
8
|
+
emFactory;
|
|
9
|
+
events;
|
|
10
|
+
accounts;
|
|
11
|
+
auditLog;
|
|
12
|
+
constructor(emFactory, events, accounts, auditLog) {
|
|
13
|
+
this.emFactory = emFactory;
|
|
14
|
+
this.events = events;
|
|
15
|
+
this.accounts = accounts;
|
|
16
|
+
this.auditLog = auditLog;
|
|
17
|
+
}
|
|
18
|
+
async verify(rawToken) {
|
|
19
|
+
const em = this.emFactory();
|
|
20
|
+
const tokenHash = sha256Hex(rawToken);
|
|
21
|
+
const token = await em.findOne(EmailVerificationToken, { tokenHash });
|
|
22
|
+
if (!token) {
|
|
23
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification token is invalid or expired.');
|
|
24
|
+
}
|
|
25
|
+
if (token.consumedAt) {
|
|
26
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification token has already been consumed.');
|
|
27
|
+
}
|
|
28
|
+
if (token.expiresAt.getTime() <= Date.now()) {
|
|
29
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification token has expired.');
|
|
30
|
+
}
|
|
31
|
+
const customer = await this.accounts.read.findById(token.customerAccountId);
|
|
32
|
+
if (!customer) {
|
|
33
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Associated account was removed.');
|
|
34
|
+
}
|
|
35
|
+
// Feature 026 US2 — Customer may have no Organization (post-026 schema).
|
|
36
|
+
// Verification through this flow always carries an org today
|
|
37
|
+
// (RegistrationService.registerOrganization always creates the pair),
|
|
38
|
+
// but guard defensively in case a no-org Customer ever lands here.
|
|
39
|
+
if (!customer.organizationId) {
|
|
40
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Verification flow requires an Organization.');
|
|
41
|
+
}
|
|
42
|
+
const organization = await em.findOne(Organization, { id: customer.organizationId });
|
|
43
|
+
if (!organization) {
|
|
44
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Associated organization was removed.');
|
|
45
|
+
}
|
|
46
|
+
const now = new Date();
|
|
47
|
+
// Two units of work rather than one since the boundary cut: the account
|
|
48
|
+
// column belongs to `customer_accounts`, the Organization status and the
|
|
49
|
+
// token to this module. The account is stamped **first** because that half
|
|
50
|
+
// is the recoverable one — the token is still unconsumed if the second
|
|
51
|
+
// flush fails, so a retried verification re-runs the whole flow, and
|
|
52
|
+
// `markEmailVerified` keeps the first timestamp rather than moving it.
|
|
53
|
+
await this.accounts.write.markEmailVerified(customer.id, now);
|
|
54
|
+
organization.status = 'active';
|
|
55
|
+
token.consumedAt = now;
|
|
56
|
+
if (this.auditLog) {
|
|
57
|
+
recordAuditFromContext(this.auditLog, em, {
|
|
58
|
+
action: 'organization.email_verified',
|
|
59
|
+
objectType: 'organization',
|
|
60
|
+
objectId: organization.id,
|
|
61
|
+
stateBefore: { status: 'pending' },
|
|
62
|
+
stateAfter: { status: 'active', customerAccountId: customer.id },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
await em.flush();
|
|
66
|
+
this.events.emit('organization.verified.v1', {
|
|
67
|
+
eventId: randomUUID(),
|
|
68
|
+
occurredAt: now.toISOString(),
|
|
69
|
+
organizationId: organization.id,
|
|
70
|
+
customerAccountId: customer.id,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
organizationId: organization.id,
|
|
74
|
+
customerAccountId: customer.id,
|
|
75
|
+
verifiedAt: now,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Test-only helper used by the `/api/v1/_test/latest-verification-token`
|
|
80
|
+
* probe — exposes the most recent UNCONSUMED, unexpired token so integration
|
|
81
|
+
* tests can complete the verification step without touching Mailhog.
|
|
82
|
+
*
|
|
83
|
+
* Returns null if no such token exists. Callers gate this behind an env
|
|
84
|
+
* check so the probe is never exposed outside of tests.
|
|
85
|
+
*/
|
|
86
|
+
async latestUnconsumedTokenId() {
|
|
87
|
+
const em = this.emFactory();
|
|
88
|
+
const row = await em.findOne(EmailVerificationToken, { consumedAt: null }, { orderBy: { createdAt: 'desc' } });
|
|
89
|
+
return row?.id ?? null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function sha256Hex(input) {
|
|
93
|
+
return createHash('sha256').update(input, 'utf8').digest('hex');
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=email-verification-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-verification-service.js","sourceRoot":"","sources":["../../../src/backend/services/email-verification-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import { type CustomerAccountMemberWritePort, type CustomerAccountReadPort, type CustomerAccountRecord, type EmailMailerPort } from '@endora-commerce/contracts';
|
|
3
|
+
import { OrganizationInvitation } from '../entities/organization-invitation.entity.js';
|
|
4
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import { type OrgTemplateEmail } from './org-template-email.js';
|
|
6
|
+
import { type OrganizationEventBus } from './registration-service.js';
|
|
7
|
+
export interface InvitationResult {
|
|
8
|
+
invitation: OrganizationInvitation;
|
|
9
|
+
rawToken: string;
|
|
10
|
+
}
|
|
11
|
+
export interface InvitationServiceOptions {
|
|
12
|
+
/** Storefront base used when building the redemption link. */
|
|
13
|
+
acceptBaseUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `customer_accounts`' published read and write (feature 075, Phase C).
|
|
17
|
+
*
|
|
18
|
+
* An invitation is this module's row; the member it creates is that module's.
|
|
19
|
+
* The membership pre-checks used to be `em.findOne`/`em.count` on its entity
|
|
20
|
+
* and the accept used to `em.create` one, with a `hashPassword` imported from
|
|
21
|
+
* `auth`.
|
|
22
|
+
*
|
|
23
|
+
* With `customer_accounts` off every invitation path fails closed — inviting a
|
|
24
|
+
* member the platform cannot see, or accepting into an account it cannot
|
|
25
|
+
* create, are both incoherent rather than degraded.
|
|
26
|
+
*/
|
|
27
|
+
export interface InvitationAccountPorts {
|
|
28
|
+
read: CustomerAccountReadPort;
|
|
29
|
+
write: CustomerAccountMemberWritePort;
|
|
30
|
+
}
|
|
31
|
+
export declare class InvitationService {
|
|
32
|
+
private readonly emFactory;
|
|
33
|
+
private readonly accounts;
|
|
34
|
+
private readonly events?;
|
|
35
|
+
private readonly auditLog?;
|
|
36
|
+
private readonly mailer;
|
|
37
|
+
private readonly acceptBaseUrl;
|
|
38
|
+
private readonly templateEmail;
|
|
39
|
+
constructor(emFactory: () => EntityManager, accounts: InvitationAccountPorts, mailer?: EmailMailerPort | null, options?: InvitationServiceOptions, events?: OrganizationEventBus | undefined, templateEmail?: OrgTemplateEmail, auditLog?: AuditPort | undefined);
|
|
40
|
+
invite(actor: {
|
|
41
|
+
organizationId: string;
|
|
42
|
+
customerAccountId?: string | null;
|
|
43
|
+
}, input: {
|
|
44
|
+
email: string;
|
|
45
|
+
role?: 'organization_admin' | 'regular_user';
|
|
46
|
+
}): Promise<InvitationResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Lists every pending invitation on the actor's organization. "Pending"
|
|
49
|
+
* means not yet consumed and not revoked; expired rows are intentionally
|
|
50
|
+
* still surfaced so the admin can see what needs cleaning up.
|
|
51
|
+
*/
|
|
52
|
+
listPending(actor: {
|
|
53
|
+
organizationId: string;
|
|
54
|
+
}): Promise<OrganizationInvitation[]>;
|
|
55
|
+
revoke(actor: {
|
|
56
|
+
organizationId: string;
|
|
57
|
+
}, invitationId: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* The account insert and the invitation's `consumedAt` were one flush until
|
|
60
|
+
* feature 075's Phase C, and they are two units of work now: the account is
|
|
61
|
+
* `customer_accounts`' row, the invitation is this module's, and no port
|
|
62
|
+
* parameter may be a unit of work (D-78). The two modules share no foreign
|
|
63
|
+
* key between *these* two rows, so D-78's second answer does not apply and
|
|
64
|
+
* the seam is split rather than declared.
|
|
65
|
+
*
|
|
66
|
+
* The account is created **first**, deliberately. If the consume then fails,
|
|
67
|
+
* the buyer has the account they just set a password for and can sign in;
|
|
68
|
+
* what is left behind is a pending invitation an org admin can revoke. The
|
|
69
|
+
* other order loses the account instead, which is the half that cannot be
|
|
70
|
+
* recovered from the screen the buyer is looking at.
|
|
71
|
+
*/
|
|
72
|
+
acceptByToken(rawToken: string, input: {
|
|
73
|
+
password: string;
|
|
74
|
+
firstName: string;
|
|
75
|
+
lastName: string;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
customerAccount: CustomerAccountRecord;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Latest unconsumed token id — exposed only by the test probe so
|
|
81
|
+
* integration tests can complete the accept step without the email.
|
|
82
|
+
*/
|
|
83
|
+
latestUnconsumedTokenInvitationId(): Promise<string | null>;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=invitation-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invitation-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invitation-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAEvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAmBnC,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,sBAAsB,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,8BAA8B,CAAC;CACvC;AAED,qBAAa,iBAAiB;IAO1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAGzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAExB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAZ5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;gBAG9B,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,EAAE,sBAAsB,EACjD,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,EAC/B,OAAO,CAAC,EAAE,wBAAwB,EACjB,MAAM,CAAC,EAAE,oBAAoB,YAAA,EAC9C,aAAa,CAAC,EAAE,gBAAgB,EACf,QAAQ,CAAC,EAAE,SAAS,YAAA;IAOjC,MAAM,CACV,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EACpE,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,oBAAoB,GAAG,cAAc,CAAA;KAAE,GACrE,OAAO,CAAC,gBAAgB,CAAC;IAoH5B;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAajF,MAAM,CACV,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,EACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAgDhB;;;;;;;;;;;;;OAaG;IACG,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAC/D,OAAO,CAAC;QAAE,eAAe,EAAE,qBAAqB,CAAA;KAAE,CAAC;IAiEtD;;;OAGG;IACG,iCAAiC,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CASlE"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
2
|
+
import { UniqueConstraintViolationException } from '@mikro-orm/core';
|
|
3
|
+
import { ERROR_CODES, normalizeEmailAddress, } from '@endora-commerce/contracts';
|
|
4
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
5
|
+
import { Organization } from '../entities/organization.entity.js';
|
|
6
|
+
import { OrganizationInvitation } from '../entities/organization-invitation.entity.js';
|
|
7
|
+
import { recordAuditFromContext } from '@endora-commerce/platform/commands';
|
|
8
|
+
import { buildInvitationEmail } from '../email-templates/invitation.js';
|
|
9
|
+
import { noopOrgTemplateEmail } from './org-template-email.js';
|
|
10
|
+
import { emitCustomerAccountCreated, } from './registration-service.js';
|
|
11
|
+
/**
|
|
12
|
+
* InvitationService (T173, FR-043).
|
|
13
|
+
*
|
|
14
|
+
* Rules:
|
|
15
|
+
* - Org Admin invites by email + role. Existing CustomerAccount with that
|
|
16
|
+
* email rejects with EMAIL_ALREADY_IN_ORGANIZATION (same org) or
|
|
17
|
+
* EMAIL_BELONGS_TO_ANOTHER_ORGANIZATION (different org).
|
|
18
|
+
* - One pending invitation per (org, email) — partial unique index in
|
|
19
|
+
* migration 005 enforces this.
|
|
20
|
+
* - acceptByToken: anonymous flow that needs password + first/last name.
|
|
21
|
+
* Creates the CustomerAccount with role from the invitation, marks the
|
|
22
|
+
* invitation consumed.
|
|
23
|
+
* - revoke: soft-clears the pending invitation.
|
|
24
|
+
*/
|
|
25
|
+
const INVITATION_TTL_DAYS = 14;
|
|
26
|
+
export class InvitationService {
|
|
27
|
+
emFactory;
|
|
28
|
+
accounts;
|
|
29
|
+
events;
|
|
30
|
+
auditLog;
|
|
31
|
+
mailer;
|
|
32
|
+
acceptBaseUrl;
|
|
33
|
+
templateEmail;
|
|
34
|
+
constructor(emFactory, accounts, mailer, options, events, templateEmail, auditLog) {
|
|
35
|
+
this.emFactory = emFactory;
|
|
36
|
+
this.accounts = accounts;
|
|
37
|
+
this.events = events;
|
|
38
|
+
this.auditLog = auditLog;
|
|
39
|
+
this.mailer = mailer ?? null;
|
|
40
|
+
this.acceptBaseUrl = options?.acceptBaseUrl ?? 'https://storefront.local';
|
|
41
|
+
this.templateEmail = templateEmail ?? noopOrgTemplateEmail;
|
|
42
|
+
}
|
|
43
|
+
async invite(actor, input) {
|
|
44
|
+
const em = this.emFactory();
|
|
45
|
+
const role = input.role ?? 'regular_user';
|
|
46
|
+
// This module keeps a row of its own on the same address, so it folds it
|
|
47
|
+
// the same way the account table does rather than by a private rule.
|
|
48
|
+
const invitedEmail = normalizeEmailAddress(input.email);
|
|
49
|
+
// Feature 051 — a personal (B2C) organization is single-member by
|
|
50
|
+
// definition; it cannot invite additional members.
|
|
51
|
+
const actorOrg = await em.findOne(Organization, { id: actor.organizationId });
|
|
52
|
+
if (actorOrg?.isPersonal) {
|
|
53
|
+
throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'A personal (individual) account cannot invite members.');
|
|
54
|
+
}
|
|
55
|
+
// Pre-check existing membership.
|
|
56
|
+
const existingByEmail = await this.accounts.read.findByEmail(invitedEmail);
|
|
57
|
+
if (existingByEmail) {
|
|
58
|
+
if (existingByEmail.organizationId === actor.organizationId) {
|
|
59
|
+
throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_IN_ORGANIZATION, 'This email already belongs to a member of your organization.');
|
|
60
|
+
}
|
|
61
|
+
throw new HttpError(409, ERROR_CODES.EMAIL_BELONGS_TO_ANOTHER_ORGANIZATION, 'This email belongs to a member of another organization.');
|
|
62
|
+
}
|
|
63
|
+
const rawToken = randomBytes(32).toString('base64url');
|
|
64
|
+
const invitation = em.create(OrganizationInvitation, {
|
|
65
|
+
organizationId: actor.organizationId,
|
|
66
|
+
invitedByCustomerAccountId: actor.customerAccountId ?? null,
|
|
67
|
+
email: invitedEmail,
|
|
68
|
+
role,
|
|
69
|
+
tokenHash: sha256Hex(rawToken),
|
|
70
|
+
expiresAt: new Date(Date.now() + INVITATION_TTL_DAYS * 24 * 60 * 60 * 1_000),
|
|
71
|
+
});
|
|
72
|
+
if (this.auditLog) {
|
|
73
|
+
recordAuditFromContext(this.auditLog, em, {
|
|
74
|
+
action: 'organization.invite',
|
|
75
|
+
objectType: 'organization',
|
|
76
|
+
objectId: actor.organizationId,
|
|
77
|
+
stateBefore: null,
|
|
78
|
+
stateAfter: { email: invitedEmail, role },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await em.persistAndFlush(invitation);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof UniqueConstraintViolationException) {
|
|
86
|
+
throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_IN_ORGANIZATION, 'A pending invitation for this email already exists.');
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
if (this.mailer || this.templateEmail !== noopOrgTemplateEmail) {
|
|
91
|
+
const organization = await em.findOne(Organization, { id: actor.organizationId });
|
|
92
|
+
const inviter = actor.customerAccountId
|
|
93
|
+
? await this.accounts.read.findById(actor.customerAccountId)
|
|
94
|
+
: null;
|
|
95
|
+
const inviterName = inviter
|
|
96
|
+
? [inviter.firstName, inviter.lastName].filter(Boolean).join(' ').trim() || inviter.email
|
|
97
|
+
: 'Platform support';
|
|
98
|
+
const organizationName = organization?.name ?? 'your organization';
|
|
99
|
+
const message = buildInvitationEmail({
|
|
100
|
+
invitationId: invitation.id,
|
|
101
|
+
rawToken,
|
|
102
|
+
inviteeEmail: invitation.email,
|
|
103
|
+
inviterName,
|
|
104
|
+
organizationName,
|
|
105
|
+
role: invitation.role,
|
|
106
|
+
expiresAt: invitation.expiresAt,
|
|
107
|
+
acceptBaseUrl: this.acceptBaseUrl,
|
|
108
|
+
});
|
|
109
|
+
const acceptUrl = message.meta?.acceptUrl ?? '';
|
|
110
|
+
const sentViaTemplate = await this.templateEmail.trySend({
|
|
111
|
+
code: 'organization_invitation',
|
|
112
|
+
to: invitation.email,
|
|
113
|
+
messageId: message.messageId,
|
|
114
|
+
variables: {
|
|
115
|
+
organizationName,
|
|
116
|
+
inviterName,
|
|
117
|
+
roleLabel: invitation.role === 'organization_admin' ? 'Organization Admin' : 'Member',
|
|
118
|
+
acceptUrl,
|
|
119
|
+
expiresOn: invitation.expiresAt.toISOString().slice(0, 10),
|
|
120
|
+
},
|
|
121
|
+
meta: message.meta,
|
|
122
|
+
});
|
|
123
|
+
if (!sentViaTemplate && this.mailer) {
|
|
124
|
+
const outcome = await this.mailer.send(message);
|
|
125
|
+
if (outcome.status !== 'sent') {
|
|
126
|
+
// This method answers the invitation, not the message, so the
|
|
127
|
+
// non-send is named rather than returned — and since D-59 the same
|
|
128
|
+
// fact is a row an operator can read after the log has rotated.
|
|
129
|
+
console.warn('[organizations] the invitation e-mail was not sent', {
|
|
130
|
+
invitationId: invitation.id,
|
|
131
|
+
reason: outcome.reason,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { invitation, rawToken };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Lists every pending invitation on the actor's organization. "Pending"
|
|
140
|
+
* means not yet consumed and not revoked; expired rows are intentionally
|
|
141
|
+
* still surfaced so the admin can see what needs cleaning up.
|
|
142
|
+
*/
|
|
143
|
+
async listPending(actor) {
|
|
144
|
+
const em = this.emFactory();
|
|
145
|
+
return em.find(OrganizationInvitation, {
|
|
146
|
+
organizationId: actor.organizationId,
|
|
147
|
+
consumedAt: null,
|
|
148
|
+
revokedAt: null,
|
|
149
|
+
}, { orderBy: { createdAt: 'desc' } });
|
|
150
|
+
}
|
|
151
|
+
async revoke(actor, invitationId) {
|
|
152
|
+
const em = this.emFactory();
|
|
153
|
+
const invitation = await em.findOne(OrganizationInvitation, {
|
|
154
|
+
id: invitationId,
|
|
155
|
+
organizationId: actor.organizationId,
|
|
156
|
+
});
|
|
157
|
+
if (!invitation) {
|
|
158
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Invitation not found.');
|
|
159
|
+
}
|
|
160
|
+
if (invitation.consumedAt || invitation.revokedAt)
|
|
161
|
+
return;
|
|
162
|
+
if (invitation.role === 'organization_admin') {
|
|
163
|
+
const adminCount = await this.accounts.read.countByOrganizationRole(actor.organizationId, 'organization_admin', { activeOnly: true });
|
|
164
|
+
if (adminCount === 0) {
|
|
165
|
+
const otherPending = await em.count(OrganizationInvitation, {
|
|
166
|
+
organizationId: actor.organizationId,
|
|
167
|
+
role: 'organization_admin',
|
|
168
|
+
consumedAt: null,
|
|
169
|
+
revokedAt: null,
|
|
170
|
+
id: { $ne: invitation.id },
|
|
171
|
+
});
|
|
172
|
+
if (otherPending === 0) {
|
|
173
|
+
throw new HttpError(409, ERROR_CODES.CANNOT_REVOKE_LAST_ADMIN_INVITE, 'Cannot revoke the only pending administrator invitation while the organization has no active administrator.');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
invitation.revokedAt = new Date();
|
|
178
|
+
if (this.auditLog) {
|
|
179
|
+
recordAuditFromContext(this.auditLog, em, {
|
|
180
|
+
action: 'organization.invite_revoke',
|
|
181
|
+
objectType: 'organization',
|
|
182
|
+
objectId: invitation.organizationId,
|
|
183
|
+
stateBefore: { email: invitation.email, role: invitation.role },
|
|
184
|
+
stateAfter: null,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
await em.flush();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The account insert and the invitation's `consumedAt` were one flush until
|
|
191
|
+
* feature 075's Phase C, and they are two units of work now: the account is
|
|
192
|
+
* `customer_accounts`' row, the invitation is this module's, and no port
|
|
193
|
+
* parameter may be a unit of work (D-78). The two modules share no foreign
|
|
194
|
+
* key between *these* two rows, so D-78's second answer does not apply and
|
|
195
|
+
* the seam is split rather than declared.
|
|
196
|
+
*
|
|
197
|
+
* The account is created **first**, deliberately. If the consume then fails,
|
|
198
|
+
* the buyer has the account they just set a password for and can sign in;
|
|
199
|
+
* what is left behind is a pending invitation an org admin can revoke. The
|
|
200
|
+
* other order loses the account instead, which is the half that cannot be
|
|
201
|
+
* recovered from the screen the buyer is looking at.
|
|
202
|
+
*/
|
|
203
|
+
async acceptByToken(rawToken, input) {
|
|
204
|
+
const em = this.emFactory();
|
|
205
|
+
const tokenHash = sha256Hex(rawToken);
|
|
206
|
+
const invitation = await em.findOne(OrganizationInvitation, { tokenHash });
|
|
207
|
+
if (!invitation || invitation.consumedAt || invitation.revokedAt) {
|
|
208
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Invitation is invalid or has already been consumed.');
|
|
209
|
+
}
|
|
210
|
+
if (invitation.expiresAt.getTime() <= Date.now()) {
|
|
211
|
+
throw new HttpError(400, ERROR_CODES.TOKEN_INVALID_OR_EXPIRED, 'Invitation has expired.');
|
|
212
|
+
}
|
|
213
|
+
// FR-041: at most one CustomerAccount per email installation-wide. The
|
|
214
|
+
// owner re-checks inside `create`; this one keeps the invitation
|
|
215
|
+
// unconsumed when the address is already taken.
|
|
216
|
+
const existing = await this.accounts.read.findByEmail(invitation.email);
|
|
217
|
+
if (existing) {
|
|
218
|
+
throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
|
|
219
|
+
}
|
|
220
|
+
const organization = await em.findOne(Organization, { id: invitation.organizationId });
|
|
221
|
+
if (!organization) {
|
|
222
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Organization no longer exists.');
|
|
223
|
+
}
|
|
224
|
+
const customer = await this.accounts.write.create({
|
|
225
|
+
organizationId: invitation.organizationId,
|
|
226
|
+
email: invitation.email,
|
|
227
|
+
password: input.password,
|
|
228
|
+
firstName: input.firstName,
|
|
229
|
+
lastName: input.lastName,
|
|
230
|
+
role: invitation.role,
|
|
231
|
+
// Accepting an invitation implies a confirmed address: it was delivered
|
|
232
|
+
// to that address.
|
|
233
|
+
emailVerified: true,
|
|
234
|
+
});
|
|
235
|
+
invitation.consumedAt = new Date();
|
|
236
|
+
if (this.auditLog) {
|
|
237
|
+
recordAuditFromContext(this.auditLog, em, {
|
|
238
|
+
action: 'organization.invite_accept',
|
|
239
|
+
objectType: 'organization',
|
|
240
|
+
objectId: invitation.organizationId,
|
|
241
|
+
stateBefore: null,
|
|
242
|
+
stateAfter: { email: invitation.email, role: invitation.role, customerAccountId: customer.id },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
await em.flush();
|
|
246
|
+
if (this.events) {
|
|
247
|
+
emitCustomerAccountCreated(this.events, customer.id, invitation.organizationId);
|
|
248
|
+
}
|
|
249
|
+
return { customerAccount: customer };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Latest unconsumed token id — exposed only by the test probe so
|
|
253
|
+
* integration tests can complete the accept step without the email.
|
|
254
|
+
*/
|
|
255
|
+
async latestUnconsumedTokenInvitationId() {
|
|
256
|
+
const em = this.emFactory();
|
|
257
|
+
const row = await em.findOne(OrganizationInvitation, { consumedAt: null, revokedAt: null }, { orderBy: { createdAt: 'desc' } });
|
|
258
|
+
return row?.id ?? null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function sha256Hex(input) {
|
|
262
|
+
return createHash('sha256').update(input, 'utf8').digest('hex');
|
|
263
|
+
}
|
|
264
|
+
// silence unused — randomUUID may be needed by callers later
|
|
265
|
+
void randomUUID;
|
|
266
|
+
//# sourceMappingURL=invitation-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invitation-service.js","sourceRoot":"","sources":["../../../src/backend/services/invitation-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `normalizeOrganizationName` moved to `@endora-commerce/contracts` in feature 075's
|
|
3
|
+
* Phase P: it is pure over its argument, so a gated port answering 503 to
|
|
4
|
+
* "fold these diacritics" would be a bug rather than a degrade (FR-013).
|
|
5
|
+
*
|
|
6
|
+
* It has to be one implementation, not two — the entity's `@BeforeCreate` /
|
|
7
|
+
* `@BeforeUpdate` hooks write `name_search` with it and `orders`' list filter
|
|
8
|
+
* matches against that column with it, so a copy that drifted would make the
|
|
9
|
+
* filter stop matching silently.
|
|
10
|
+
*
|
|
11
|
+
* Re-exported here for the length of Phase P, which cuts no consumer.
|
|
12
|
+
*/
|
|
13
|
+
export { normalizeOrganizationName } from '@endora-commerce/contracts';
|
|
14
|
+
//# sourceMappingURL=normalize-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-name.d.ts","sourceRoot":"","sources":["../../../src/backend/services/normalize-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `normalizeOrganizationName` moved to `@endora-commerce/contracts` in feature 075's
|
|
3
|
+
* Phase P: it is pure over its argument, so a gated port answering 503 to
|
|
4
|
+
* "fold these diacritics" would be a bug rather than a degrade (FR-013).
|
|
5
|
+
*
|
|
6
|
+
* It has to be one implementation, not two — the entity's `@BeforeCreate` /
|
|
7
|
+
* `@BeforeUpdate` hooks write `name_search` with it and `orders`' list filter
|
|
8
|
+
* matches against that column with it, so a copy that drifted would make the
|
|
9
|
+
* filter stop matching silently.
|
|
10
|
+
*
|
|
11
|
+
* Re-exported here for the length of Phase P, which cuts no consumer.
|
|
12
|
+
*/
|
|
13
|
+
export { normalizeOrganizationName } from '@endora-commerce/contracts';
|
|
14
|
+
//# sourceMappingURL=normalize-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-name.js","sourceRoot":"","sources":["../../../src/backend/services/normalize-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
|