@open-mercato/core 0.4.2-canary-e6bf6a353e → 0.4.2-canary-49d47ff90e
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/dist/generated/entities/notification/index.js +57 -0
- package/dist/generated/entities/notification/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +5 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/api_keys/backend/api-keys/page.js +1 -1
- package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js +4 -0
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +2 -0
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +4 -3
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +157 -0
- package/dist/modules/auth/api/profile/route.js.map +7 -0
- package/dist/modules/auth/api/reset/confirm.js +25 -2
- package/dist/modules/auth/api/reset/confirm.js.map +2 -2
- package/dist/modules/auth/api/reset.js +23 -0
- package/dist/modules/auth/api/reset.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +4 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +141 -0
- package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js +13 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
- package/dist/modules/auth/backend/roles/page.js +3 -3
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +14 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +15 -2
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +3 -3
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/cli.js +13 -0
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/commands/users.js +59 -2
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/validators.js +4 -2
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/auth/frontend/reset/[token]/page.js +20 -10
- package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/notifications.js +112 -0
- package/dist/modules/auth/notifications.js.map +7 -0
- package/dist/modules/auth/services/authService.js +3 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/page.js +4 -0
- package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/page.js +3 -0
- package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +28 -0
- package/dist/modules/business_rules/notifications.js.map +7 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
- package/dist/modules/catalog/components/PriceKindSettings.js +2 -0
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +2 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/notifications.js +28 -0
- package/dist/modules/catalog/notifications.js.map +7 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/configs/cli.js +6 -0
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +18 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +3 -0
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +2 -0
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +3 -0
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +3 -0
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +3 -0
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +31 -0
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
- package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
- package/dist/modules/customers/notifications.js +48 -0
- package/dist/modules/customers/notifications.js.map +7 -0
- package/dist/modules/dashboards/cli.js +32 -1
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/role-widgets.js +58 -0
- package/dist/modules/dashboards/lib/role-widgets.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +2 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/SystemEntitiesTable.js +1 -1
- package/dist/modules/entities/components/SystemEntitiesTable.js.map +2 -2
- package/dist/modules/entities/components/UserEntitiesTable.js +2 -2
- package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +3 -3
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/OverridesTable.js +1 -1
- package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
- package/dist/modules/notifications/acl.js +11 -0
- package/dist/modules/notifications/acl.js.map +7 -0
- package/dist/modules/notifications/api/[id]/action/route.js +74 -0
- package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/read/route.js +15 -0
- package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
- package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
- package/dist/modules/notifications/api/batch/route.js +17 -0
- package/dist/modules/notifications/api/batch/route.js.map +7 -0
- package/dist/modules/notifications/api/feature/route.js +17 -0
- package/dist/modules/notifications/api/feature/route.js.map +7 -0
- package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
- package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
- package/dist/modules/notifications/api/openapi.js +76 -0
- package/dist/modules/notifications/api/openapi.js.map +7 -0
- package/dist/modules/notifications/api/role/route.js +17 -0
- package/dist/modules/notifications/api/role/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +85 -0
- package/dist/modules/notifications/api/route.js.map +7 -0
- package/dist/modules/notifications/api/settings/route.js +155 -0
- package/dist/modules/notifications/api/settings/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +38 -0
- package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
- package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
- package/dist/modules/notifications/cli.js +16 -0
- package/dist/modules/notifications/cli.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +112 -0
- package/dist/modules/notifications/data/entities.js.map +7 -0
- package/dist/modules/notifications/data/validators.js +94 -0
- package/dist/modules/notifications/data/validators.js.map +7 -0
- package/dist/modules/notifications/di.js +13 -0
- package/dist/modules/notifications/di.js.map +7 -0
- package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
- package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
- package/dist/modules/notifications/index.js +14 -0
- package/dist/modules/notifications/index.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
- package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
- package/dist/modules/notifications/lib/events.js +12 -0
- package/dist/modules/notifications/lib/events.js.map +7 -0
- package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
- package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +54 -0
- package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
- package/dist/modules/notifications/lib/notificationMapper.js +34 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
- package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +279 -0
- package/dist/modules/notifications/lib/notificationService.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +101 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
- package/dist/modules/notifications/lib/safeHref.js +24 -0
- package/dist/modules/notifications/lib/safeHref.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
- package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
- package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +7 -1
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +26 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/ShippingMethodsSettings.js +2 -2
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/TaxRatesSettings.js +2 -2
- package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +2 -0
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +2 -0
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +2 -1
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -0
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/notifications.client.js +51 -0
- package/dist/modules/sales/notifications.client.js.map +7 -0
- package/dist/modules/sales/notifications.js +88 -0
- package/dist/modules/sales/notifications.js.map +7 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/index.js +7 -0
- package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
- package/dist/modules/staff/backend/staff/team-members/page.js +1 -1
- package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
- package/dist/modules/staff/commands/leave-requests.js +79 -0
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/notifications.js +75 -0
- package/dist/modules/staff/notifications.js.map +7 -0
- package/dist/modules/workflows/backend/definitions/page.js +5 -0
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/page.js +3 -0
- package/dist/modules/workflows/backend/instances/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +3 -0
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/notifications.js +28 -0
- package/dist/modules/workflows/notifications.js.map +7 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
- package/generated/entities/notification/index.ts +27 -0
- package/generated/entities.ids.generated.ts +5 -1
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
- package/src/modules/api_keys/backend/api-keys/page.tsx +1 -1
- package/src/modules/attachments/components/AttachmentLibrary.tsx +4 -0
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +2 -0
- package/src/modules/auth/api/admin/nav.ts +10 -6
- package/src/modules/auth/api/profile/route.ts +163 -0
- package/src/modules/auth/api/reset/confirm.ts +25 -2
- package/src/modules/auth/api/reset.ts +23 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
- package/src/modules/auth/api/users/route.ts +5 -2
- package/src/modules/auth/backend/auth/profile/page.meta.ts +9 -0
- package/src/modules/auth/backend/auth/profile/page.tsx +174 -0
- package/src/modules/auth/backend/roles/page.tsx +3 -3
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +18 -2
- package/src/modules/auth/backend/users/create/page.tsx +19 -2
- package/src/modules/auth/backend/users/page.tsx +3 -3
- package/src/modules/auth/cli.ts +14 -0
- package/src/modules/auth/commands/users.ts +73 -2
- package/src/modules/auth/data/validators.ts +5 -2
- package/src/modules/auth/frontend/reset/[token]/page.tsx +24 -11
- package/src/modules/auth/i18n/de.json +43 -1
- package/src/modules/auth/i18n/en.json +43 -1
- package/src/modules/auth/i18n/es.json +43 -1
- package/src/modules/auth/i18n/pl.json +43 -1
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/notifications.ts +109 -0
- package/src/modules/auth/services/authService.ts +4 -4
- package/src/modules/business_rules/backend/rules/page.tsx +4 -0
- package/src/modules/business_rules/backend/sets/page.tsx +3 -0
- package/src/modules/business_rules/i18n/en.json +3 -1
- package/src/modules/business_rules/notifications.ts +25 -0
- package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
- package/src/modules/catalog/components/PriceKindSettings.tsx +2 -0
- package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +2 -2
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +2 -0
- package/src/modules/catalog/i18n/en.json +3 -1
- package/src/modules/catalog/notifications.ts +25 -0
- package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
- package/src/modules/configs/cli.ts +6 -0
- package/src/modules/configs/lib/upgrade-actions.ts +18 -0
- package/src/modules/currencies/backend/currencies/page.tsx +3 -0
- package/src/modules/currencies/backend/exchange-rates/page.tsx +2 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +3 -0
- package/src/modules/customers/backend/customers/deals/page.tsx +3 -0
- package/src/modules/customers/backend/customers/people/page.tsx +3 -0
- package/src/modules/customers/commands/deals.ts +39 -0
- package/src/modules/customers/components/CustomerTodosTable.tsx +1 -0
- package/src/modules/customers/i18n/en.json +5 -1
- package/src/modules/customers/notifications.ts +44 -0
- package/src/modules/dashboards/cli.ts +41 -1
- package/src/modules/dashboards/lib/role-widgets.ts +80 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +2 -0
- package/src/modules/directory/backend/directory/organizations/page.tsx +2 -2
- package/src/modules/directory/backend/directory/tenants/page.tsx +2 -2
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +2 -2
- package/src/modules/entities/components/SystemEntitiesTable.tsx +1 -1
- package/src/modules/entities/components/UserEntitiesTable.tsx +2 -2
- package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +3 -4
- package/src/modules/feature_toggles/components/OverridesTable.tsx +1 -1
- package/src/modules/notifications/acl.ts +7 -0
- package/src/modules/notifications/api/[id]/action/route.ts +75 -0
- package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
- package/src/modules/notifications/api/[id]/read/route.ts +12 -0
- package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
- package/src/modules/notifications/api/batch/route.ts +14 -0
- package/src/modules/notifications/api/feature/route.ts +14 -0
- package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
- package/src/modules/notifications/api/openapi.ts +76 -0
- package/src/modules/notifications/api/role/route.ts +14 -0
- package/src/modules/notifications/api/route.ts +92 -0
- package/src/modules/notifications/api/settings/route.ts +157 -0
- package/src/modules/notifications/api/unread-count/route.ts +38 -0
- package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
- package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
- package/src/modules/notifications/cli.ts +18 -0
- package/src/modules/notifications/data/entities.ts +99 -0
- package/src/modules/notifications/data/validators.ts +110 -0
- package/src/modules/notifications/di.ts +11 -0
- package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
- package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
- package/src/modules/notifications/i18n/de.json +50 -0
- package/src/modules/notifications/i18n/en.json +50 -0
- package/src/modules/notifications/i18n/es.json +50 -0
- package/src/modules/notifications/i18n/pl.json +50 -0
- package/src/modules/notifications/index.ts +12 -0
- package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
- package/src/modules/notifications/lib/events.ts +48 -0
- package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
- package/src/modules/notifications/lib/notificationFactory.ts +76 -0
- package/src/modules/notifications/lib/notificationMapper.ts +33 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
- package/src/modules/notifications/lib/notificationService.ts +414 -0
- package/src/modules/notifications/lib/routeHelpers.ts +151 -0
- package/src/modules/notifications/lib/safeHref.ts +29 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
- package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
- package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
- package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
- package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +2 -2
- package/src/modules/query_index/components/QueryIndexesTable.tsx +8 -2
- package/src/modules/resources/backend/resources/resource-types/page.tsx +2 -2
- package/src/modules/resources/backend/resources/resources/page.tsx +2 -2
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +2 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +2 -0
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/payments.ts +33 -0
- package/src/modules/sales/components/AdjustmentKindSettings.tsx +2 -2
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +2 -2
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +2 -2
- package/src/modules/sales/components/TaxRatesSettings.tsx +2 -2
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +2 -0
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +2 -0
- package/src/modules/sales/components/documents/PaymentsSection.tsx +2 -1
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -0
- package/src/modules/sales/i18n/de.json +20 -0
- package/src/modules/sales/i18n/en.json +25 -1
- package/src/modules/sales/i18n/es.json +20 -0
- package/src/modules/sales/i18n/pl.json +20 -0
- package/src/modules/sales/notifications.client.ts +65 -0
- package/src/modules/sales/notifications.ts +82 -0
- package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/index.ts +2 -0
- package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
- package/src/modules/staff/backend/staff/team-members/page.tsx +1 -1
- package/src/modules/staff/backend/staff/team-roles/page.tsx +2 -2
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +2 -2
- package/src/modules/staff/backend/staff/teams/page.tsx +2 -2
- package/src/modules/staff/commands/leave-requests.ts +94 -0
- package/src/modules/staff/i18n/de.json +4 -0
- package/src/modules/staff/i18n/en.json +9 -1
- package/src/modules/staff/i18n/es.json +4 -0
- package/src/modules/staff/i18n/pl.json +4 -0
- package/src/modules/staff/notifications.ts +71 -0
- package/src/modules/workflows/backend/definitions/page.tsx +5 -0
- package/src/modules/workflows/backend/instances/page.tsx +4 -1
- package/src/modules/workflows/backend/tasks/page.tsx +4 -1
- package/src/modules/workflows/i18n/en.json +3 -1
- package/src/modules/workflows/notifications.ts +25 -0
- package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
|
@@ -64,12 +64,16 @@ export async function GET(req: Request) {
|
|
|
64
64
|
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
|
|
65
65
|
) ?? false
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
// For API key auth, use userId (the actual user) if available
|
|
68
|
+
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
|
|
69
|
+
const settings = effectiveUserId
|
|
70
|
+
? await loadSidebarPreference(em, {
|
|
71
|
+
userId: effectiveUserId,
|
|
72
|
+
tenantId: auth.tenantId ?? null,
|
|
73
|
+
organizationId: auth.orgId ?? null,
|
|
74
|
+
locale,
|
|
75
|
+
})
|
|
76
|
+
: null
|
|
73
77
|
|
|
74
78
|
let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []
|
|
75
79
|
if (canApplyToRoles) {
|
|
@@ -92,11 +96,11 @@ export async function GET(req: Request) {
|
|
|
92
96
|
return NextResponse.json({
|
|
93
97
|
locale,
|
|
94
98
|
settings: {
|
|
95
|
-
version: settings
|
|
96
|
-
groupOrder: settings
|
|
97
|
-
groupLabels: settings
|
|
98
|
-
itemLabels: settings
|
|
99
|
-
hiddenItems: settings
|
|
99
|
+
version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,
|
|
100
|
+
groupOrder: settings?.groupOrder ?? [],
|
|
101
|
+
groupLabels: settings?.groupLabels ?? {},
|
|
102
|
+
itemLabels: settings?.itemLabels ?? {},
|
|
103
|
+
hiddenItems: settings?.hiddenItems ?? [],
|
|
100
104
|
},
|
|
101
105
|
canApplyToRoles,
|
|
102
106
|
roles: rolesPayload,
|
|
@@ -106,6 +110,11 @@ export async function GET(req: Request) {
|
|
|
106
110
|
export async function PUT(req: Request) {
|
|
107
111
|
const auth = await getAuthFromRequest(req)
|
|
108
112
|
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
113
|
+
// For API key auth, use userId (the actual user) if available
|
|
114
|
+
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
|
|
115
|
+
if (!effectiveUserId) {
|
|
116
|
+
return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })
|
|
117
|
+
}
|
|
109
118
|
|
|
110
119
|
let parsedBody: unknown
|
|
111
120
|
try {
|
|
@@ -182,7 +191,7 @@ export async function PUT(req: Request) {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const settings = await saveSidebarPreference(em, {
|
|
185
|
-
userId:
|
|
194
|
+
userId: effectiveUserId,
|
|
186
195
|
tenantId: auth.tenantId ?? null,
|
|
187
196
|
organizationId: auth.orgId ?? null,
|
|
188
197
|
locale,
|
|
@@ -15,6 +15,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
15
15
|
import { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'
|
|
16
16
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
17
17
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
18
|
+
import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
18
19
|
|
|
19
20
|
const querySchema = z.object({
|
|
20
21
|
id: z.string().uuid().optional(),
|
|
@@ -27,9 +28,11 @@ const querySchema = z.object({
|
|
|
27
28
|
|
|
28
29
|
const rawBodySchema = z.object({}).passthrough()
|
|
29
30
|
|
|
31
|
+
const passwordSchema = buildPasswordSchema()
|
|
32
|
+
|
|
30
33
|
const userCreateSchema = z.object({
|
|
31
34
|
email: z.string().email(),
|
|
32
|
-
password:
|
|
35
|
+
password: passwordSchema,
|
|
33
36
|
organizationId: z.string().uuid(),
|
|
34
37
|
roles: z.array(z.string()).optional(),
|
|
35
38
|
})
|
|
@@ -37,7 +40,7 @@ const userCreateSchema = z.object({
|
|
|
37
40
|
const userUpdateSchema = z.object({
|
|
38
41
|
id: z.string().uuid(),
|
|
39
42
|
email: z.string().email().optional(),
|
|
40
|
-
password:
|
|
43
|
+
password: passwordSchema.optional(),
|
|
41
44
|
organizationId: z.string().uuid().optional(),
|
|
42
45
|
roles: z.array(z.string()).optional(),
|
|
43
46
|
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { useRouter } from 'next/navigation'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
import { Save } from 'lucide-react'
|
|
6
|
+
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
7
|
+
import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
|
|
8
|
+
import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
9
|
+
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
10
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
11
|
+
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
12
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
13
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
14
|
+
import { buildPasswordSchema, formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
15
|
+
|
|
16
|
+
type ProfileResponse = {
|
|
17
|
+
email?: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ProfileUpdateResponse = {
|
|
21
|
+
ok?: boolean
|
|
22
|
+
email?: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ProfileFormValues = {
|
|
26
|
+
email: string
|
|
27
|
+
password: string
|
|
28
|
+
confirmPassword: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function AuthProfilePage() {
|
|
32
|
+
const t = useT()
|
|
33
|
+
const router = useRouter()
|
|
34
|
+
const [loading, setLoading] = React.useState(true)
|
|
35
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
36
|
+
const [email, setEmail] = React.useState('')
|
|
37
|
+
const [formKey, setFormKey] = React.useState(0)
|
|
38
|
+
const formId = React.useId()
|
|
39
|
+
const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])
|
|
40
|
+
const passwordRequirements = React.useMemo(
|
|
41
|
+
() => formatPasswordRequirements(passwordPolicy, t),
|
|
42
|
+
[passwordPolicy, t],
|
|
43
|
+
)
|
|
44
|
+
const passwordDescription = React.useMemo(() => (
|
|
45
|
+
passwordRequirements
|
|
46
|
+
? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })
|
|
47
|
+
: undefined
|
|
48
|
+
), [passwordRequirements, t])
|
|
49
|
+
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
let cancelled = false
|
|
52
|
+
async function load() {
|
|
53
|
+
setLoading(true)
|
|
54
|
+
setError(null)
|
|
55
|
+
try {
|
|
56
|
+
const { ok, result } = await apiCall<ProfileResponse>('/api/auth/profile')
|
|
57
|
+
if (!ok) throw new Error('load_failed')
|
|
58
|
+
const resolvedEmail = typeof result?.email === 'string' ? result.email : ''
|
|
59
|
+
if (!cancelled) setEmail(resolvedEmail)
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error('Failed to load auth profile', err)
|
|
62
|
+
if (!cancelled) setError(t('auth.profile.form.errors.load', 'Failed to load profile.'))
|
|
63
|
+
} finally {
|
|
64
|
+
if (!cancelled) setLoading(false)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
load()
|
|
68
|
+
return () => { cancelled = true }
|
|
69
|
+
}, [t])
|
|
70
|
+
|
|
71
|
+
const fields = React.useMemo<CrudField[]>(() => [
|
|
72
|
+
{ id: 'email', label: t('auth.profile.form.email', 'Email'), type: 'text', required: true },
|
|
73
|
+
{
|
|
74
|
+
id: 'password',
|
|
75
|
+
label: t('auth.profile.form.password', 'New password'),
|
|
76
|
+
type: 'text',
|
|
77
|
+
description: passwordDescription,
|
|
78
|
+
},
|
|
79
|
+
{ id: 'confirmPassword', label: t('auth.profile.form.confirmPassword', 'Confirm new password'), type: 'text' },
|
|
80
|
+
], [passwordDescription, t])
|
|
81
|
+
|
|
82
|
+
const schema = React.useMemo(() => {
|
|
83
|
+
const passwordSchema = buildPasswordSchema({
|
|
84
|
+
policy: passwordPolicy,
|
|
85
|
+
message: t('auth.profile.form.errors.passwordRequirements', 'Password must meet the requirements.'),
|
|
86
|
+
})
|
|
87
|
+
const optionalPasswordSchema = z.union([z.literal(''), passwordSchema]).optional()
|
|
88
|
+
return z.object({
|
|
89
|
+
email: z.string().trim().min(1, t('auth.profile.form.errors.emailRequired', 'Email is required.')),
|
|
90
|
+
password: optionalPasswordSchema,
|
|
91
|
+
confirmPassword: z.string().optional(),
|
|
92
|
+
}).superRefine((values, ctx) => {
|
|
93
|
+
const password = values.password?.trim() ?? ''
|
|
94
|
+
const confirmPassword = values.confirmPassword?.trim() ?? ''
|
|
95
|
+
if ((password || confirmPassword) && password !== confirmPassword) {
|
|
96
|
+
ctx.addIssue({
|
|
97
|
+
code: z.ZodIssueCode.custom,
|
|
98
|
+
message: t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'),
|
|
99
|
+
path: ['confirmPassword'],
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}, [passwordPolicy, t])
|
|
104
|
+
|
|
105
|
+
const handleSubmit = React.useCallback(async (values: ProfileFormValues) => {
|
|
106
|
+
const nextEmail = values.email?.trim() ?? ''
|
|
107
|
+
const password = values.password?.trim() ?? ''
|
|
108
|
+
|
|
109
|
+
if (!password && nextEmail === email) {
|
|
110
|
+
throw createCrudFormError(t('auth.profile.form.errors.noChanges', 'No changes to save.'))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const payload: { email: string; password?: string } = { email: nextEmail }
|
|
114
|
+
if (password) payload.password = password
|
|
115
|
+
|
|
116
|
+
const result = await readApiResultOrThrow<ProfileUpdateResponse>(
|
|
117
|
+
'/api/auth/profile',
|
|
118
|
+
{
|
|
119
|
+
method: 'PUT',
|
|
120
|
+
headers: { 'content-type': 'application/json' },
|
|
121
|
+
body: JSON.stringify(payload),
|
|
122
|
+
},
|
|
123
|
+
{ errorMessage: t('auth.profile.form.errors.save', 'Failed to update profile.') },
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const resolvedEmail = typeof result?.email === 'string' ? result.email : nextEmail
|
|
127
|
+
setEmail(resolvedEmail)
|
|
128
|
+
setFormKey((prev) => prev + 1)
|
|
129
|
+
flash(t('auth.profile.form.success', 'Profile updated.'), 'success')
|
|
130
|
+
router.refresh()
|
|
131
|
+
}, [email, router, t])
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<Page>
|
|
135
|
+
<PageBody>
|
|
136
|
+
{loading ? (
|
|
137
|
+
<LoadingMessage label={t('auth.profile.form.loading', 'Loading profile...')} />
|
|
138
|
+
) : error ? (
|
|
139
|
+
<ErrorMessage label={error} />
|
|
140
|
+
) : (
|
|
141
|
+
<section className="space-y-6 rounded-lg border bg-background p-6">
|
|
142
|
+
<header className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
143
|
+
<div className="space-y-1">
|
|
144
|
+
<h2 className="text-lg font-semibold">{t('auth.profile.title', 'Profile')}</h2>
|
|
145
|
+
<p className="text-sm text-muted-foreground">
|
|
146
|
+
{t('auth.profile.subtitle', 'Change password')}
|
|
147
|
+
</p>
|
|
148
|
+
</div>
|
|
149
|
+
<Button type="submit" form={formId}>
|
|
150
|
+
<Save className="size-4 mr-2" />
|
|
151
|
+
{t('auth.profile.form.save', 'Save changes')}
|
|
152
|
+
</Button>
|
|
153
|
+
</header>
|
|
154
|
+
<CrudForm<ProfileFormValues>
|
|
155
|
+
key={formKey}
|
|
156
|
+
formId={formId}
|
|
157
|
+
schema={schema}
|
|
158
|
+
fields={fields}
|
|
159
|
+
initialValues={{
|
|
160
|
+
email,
|
|
161
|
+
password: '',
|
|
162
|
+
confirmPassword: '',
|
|
163
|
+
}}
|
|
164
|
+
submitLabel={t('auth.profile.form.save', 'Save changes')}
|
|
165
|
+
onSubmit={handleSubmit}
|
|
166
|
+
embedded
|
|
167
|
+
hideFooterActions
|
|
168
|
+
/>
|
|
169
|
+
</section>
|
|
170
|
+
)}
|
|
171
|
+
</PageBody>
|
|
172
|
+
</Page>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
@@ -117,9 +117,9 @@ export default function RolesListPage() {
|
|
|
117
117
|
onSearchChange={(v) => { setSearch(v); setPage(1) }}
|
|
118
118
|
rowActions={(row) => (
|
|
119
119
|
<RowActions items={[
|
|
120
|
-
{ label: t('common.edit', 'Edit'), href: `/backend/roles/${row.id}/edit` },
|
|
121
|
-
{ label: t('auth.roles.list.actions.showUsers', 'Show users'), href: `/backend/users?roleId=${encodeURIComponent(row.id)}` },
|
|
122
|
-
{ label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
|
|
120
|
+
{ id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/roles/${row.id}/edit` },
|
|
121
|
+
{ id: 'show-users', label: t('auth.roles.list.actions.showUsers', 'Show users'), href: `/backend/users?roleId=${encodeURIComponent(row.id)}` },
|
|
122
|
+
{ id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
|
|
123
123
|
]} />
|
|
124
124
|
)}
|
|
125
125
|
sortable
|
|
@@ -12,6 +12,7 @@ import { TenantSelect } from '@open-mercato/core/modules/directory/components/Te
|
|
|
12
12
|
import { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'
|
|
13
13
|
import { WidgetVisibilityEditor } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'
|
|
14
14
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
|
+
import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
15
16
|
|
|
16
17
|
type EditUserFormValues = {
|
|
17
18
|
email: string
|
|
@@ -108,6 +109,16 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
108
109
|
const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })
|
|
109
110
|
const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})
|
|
110
111
|
const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)
|
|
112
|
+
const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])
|
|
113
|
+
const passwordRequirements = React.useMemo(
|
|
114
|
+
() => formatPasswordRequirements(passwordPolicy, t),
|
|
115
|
+
[passwordPolicy, t],
|
|
116
|
+
)
|
|
117
|
+
const passwordDescription = React.useMemo(() => (
|
|
118
|
+
passwordRequirements
|
|
119
|
+
? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })
|
|
120
|
+
: undefined
|
|
121
|
+
), [passwordRequirements, t])
|
|
111
122
|
|
|
112
123
|
React.useEffect(() => {
|
|
113
124
|
if (!id) {
|
|
@@ -201,7 +212,12 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
201
212
|
const fields: CrudField[] = React.useMemo(() => {
|
|
202
213
|
const items: CrudField[] = [
|
|
203
214
|
{ id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },
|
|
204
|
-
{
|
|
215
|
+
{
|
|
216
|
+
id: 'password',
|
|
217
|
+
label: t('auth.users.form.field.password', 'Password'),
|
|
218
|
+
type: 'text',
|
|
219
|
+
description: passwordDescription,
|
|
220
|
+
},
|
|
205
221
|
]
|
|
206
222
|
if (actorIsSuperAdmin) {
|
|
207
223
|
items.push({
|
|
@@ -251,7 +267,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
251
267
|
})
|
|
252
268
|
items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })
|
|
253
269
|
return items
|
|
254
|
-
}, [actorIsSuperAdmin, loadRoleOptions, preloadedTenants, selectedOrgId, selectedTenantId, t])
|
|
270
|
+
}, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t])
|
|
255
271
|
|
|
256
272
|
const detailFieldIds = React.useMemo(() => {
|
|
257
273
|
const base: string[] = ['email', 'password', 'organizationId', 'roles']
|
|
@@ -11,6 +11,7 @@ import { TenantSelect } from '@open-mercato/core/modules/directory/components/Te
|
|
|
11
11
|
import { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'
|
|
12
12
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
13
13
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
14
|
+
import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
14
15
|
|
|
15
16
|
type CreateUserFormValues = {
|
|
16
17
|
email: string
|
|
@@ -84,6 +85,16 @@ export default function CreateUserPage() {
|
|
|
84
85
|
const [selectedWidgets, setSelectedWidgets] = React.useState<string[]>([])
|
|
85
86
|
const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)
|
|
86
87
|
const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)
|
|
88
|
+
const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])
|
|
89
|
+
const passwordRequirements = React.useMemo(
|
|
90
|
+
() => formatPasswordRequirements(passwordPolicy, t),
|
|
91
|
+
[passwordPolicy, t],
|
|
92
|
+
)
|
|
93
|
+
const passwordDescription = React.useMemo(() => (
|
|
94
|
+
passwordRequirements
|
|
95
|
+
? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })
|
|
96
|
+
: undefined
|
|
97
|
+
), [passwordRequirements, t])
|
|
87
98
|
|
|
88
99
|
React.useEffect(() => {
|
|
89
100
|
let cancelled = false
|
|
@@ -156,7 +167,13 @@ export default function CreateUserPage() {
|
|
|
156
167
|
const fields: CrudField[] = React.useMemo(() => {
|
|
157
168
|
const items: CrudField[] = [
|
|
158
169
|
{ id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },
|
|
159
|
-
{
|
|
170
|
+
{
|
|
171
|
+
id: 'password',
|
|
172
|
+
label: t('auth.users.form.field.password', 'Password'),
|
|
173
|
+
type: 'text',
|
|
174
|
+
required: true,
|
|
175
|
+
description: passwordDescription,
|
|
176
|
+
},
|
|
160
177
|
]
|
|
161
178
|
if (actorIsSuperAdmin) {
|
|
162
179
|
items.push({
|
|
@@ -203,7 +220,7 @@ export default function CreateUserPage() {
|
|
|
203
220
|
})
|
|
204
221
|
items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })
|
|
205
222
|
return items
|
|
206
|
-
}, [actorIsSuperAdmin, loadRoleOptions, selectedTenantId, t])
|
|
223
|
+
}, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, selectedTenantId, t])
|
|
207
224
|
|
|
208
225
|
const detailFieldIds = React.useMemo(() => {
|
|
209
226
|
const base: string[] = ['email', 'password', 'organizationId', 'roles']
|
|
@@ -383,9 +383,9 @@ export default function UsersListPage() {
|
|
|
383
383
|
perspective={{ tableId: 'auth.users.list' }}
|
|
384
384
|
rowActions={(row) => (
|
|
385
385
|
<RowActions items={[
|
|
386
|
-
{ label: t('common.edit', 'Edit'), href: `/backend/users/${row.id}/edit` },
|
|
387
|
-
{ label: t('auth.users.list.actions.showRoles', 'Show roles'), href: `/backend/roles?userId=${encodeURIComponent(row.id)}` },
|
|
388
|
-
{ label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
|
|
386
|
+
{ id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/users/${row.id}/edit` },
|
|
387
|
+
{ id: 'show-roles', label: t('auth.users.list.actions.showRoles', 'Show roles'), href: `/backend/roles?userId=${encodeURIComponent(row.id)}` },
|
|
388
|
+
{ id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
|
|
389
389
|
]} />
|
|
390
390
|
)}
|
|
391
391
|
pagination={{ page, pageSize: 50, total, totalPages, onPageChange: setPage }}
|
package/src/modules/auth/cli.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { decryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'
|
|
|
16
16
|
import { env } from 'process'
|
|
17
17
|
import type { KmsService, TenantDek } from '@open-mercato/shared/lib/encryption/kms'
|
|
18
18
|
import crypto from 'node:crypto'
|
|
19
|
+
import { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
19
20
|
|
|
20
21
|
const addUser: ModuleCli = {
|
|
21
22
|
command: 'add-user',
|
|
@@ -34,6 +35,7 @@ const addUser: ModuleCli = {
|
|
|
34
35
|
console.error('Usage: mercato auth add-user --email <email> --password <password> --organizationId <id> [--roles customer,employee]')
|
|
35
36
|
return
|
|
36
37
|
}
|
|
38
|
+
if (!ensurePasswordPolicy(password)) return
|
|
37
39
|
const { resolve } = await createRequestContainer()
|
|
38
40
|
const em = resolve('em') as any
|
|
39
41
|
const org =
|
|
@@ -102,6 +104,16 @@ function hashSecret(value: string | null | undefined): string | null {
|
|
|
102
104
|
return crypto.createHash('sha256').update(normalizeKeyInput(value)).digest('hex').slice(0, 12)
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
function ensurePasswordPolicy(password: string): boolean {
|
|
108
|
+
const policy = getPasswordPolicy()
|
|
109
|
+
const result = validatePassword(password, policy)
|
|
110
|
+
if (result.ok) return true
|
|
111
|
+
const requirements = formatPasswordRequirements(policy, (_key, fallback) => fallback)
|
|
112
|
+
const suffix = requirements ? `: ${requirements}` : ''
|
|
113
|
+
console.error(`Password does not meet the requirements${suffix}.`)
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
105
117
|
async function withEncryptionDebugDisabled<T>(fn: () => Promise<T>): Promise<T> {
|
|
106
118
|
const previous = process.env.TENANT_DATA_ENCRYPTION_DEBUG
|
|
107
119
|
process.env.TENANT_DATA_ENCRYPTION_DEBUG = 'no'
|
|
@@ -406,6 +418,7 @@ const setupApp: ModuleCli = {
|
|
|
406
418
|
console.error('Usage: mercato auth setup --orgName <name> --email <email> --password <password> [--roles superadmin,admin,employee]')
|
|
407
419
|
return
|
|
408
420
|
}
|
|
421
|
+
if (!ensurePasswordPolicy(password)) return
|
|
409
422
|
const { resolve } = await createRequestContainer()
|
|
410
423
|
const em = resolve<EntityManager>('em')
|
|
411
424
|
const roleNames = rolesCsv
|
|
@@ -595,6 +608,7 @@ const setPassword: ModuleCli = {
|
|
|
595
608
|
console.error('Usage: mercato auth set-password --email <email> --password <newPassword>')
|
|
596
609
|
return
|
|
597
610
|
}
|
|
611
|
+
if (!ensurePasswordPolicy(password)) return
|
|
598
612
|
|
|
599
613
|
const { resolve } = await createRequestContainer()
|
|
600
614
|
const em = resolve('em') as any
|
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
import { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'
|
|
28
28
|
import { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'
|
|
29
29
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
30
|
+
import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
|
|
31
|
+
import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
|
|
32
|
+
import notificationTypes from '@open-mercato/core/modules/auth/notifications'
|
|
33
|
+
import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
30
34
|
|
|
31
35
|
type SerializedUser = {
|
|
32
36
|
email: string
|
|
@@ -63,9 +67,11 @@ type UserSnapshots = {
|
|
|
63
67
|
undo: UserUndoSnapshot
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
const passwordSchema = buildPasswordSchema()
|
|
71
|
+
|
|
66
72
|
const createSchema = z.object({
|
|
67
73
|
email: z.string().email(),
|
|
68
|
-
password:
|
|
74
|
+
password: passwordSchema,
|
|
69
75
|
organizationId: z.string().uuid(),
|
|
70
76
|
roles: z.array(z.string()).optional(),
|
|
71
77
|
})
|
|
@@ -73,7 +79,7 @@ const createSchema = z.object({
|
|
|
73
79
|
const updateSchema = z.object({
|
|
74
80
|
id: z.string().uuid(),
|
|
75
81
|
email: z.string().email().optional(),
|
|
76
|
-
password:
|
|
82
|
+
password: passwordSchema.optional(),
|
|
77
83
|
organizationId: z.string().uuid().optional(),
|
|
78
84
|
roles: z.array(z.string()).optional(),
|
|
79
85
|
})
|
|
@@ -105,6 +111,46 @@ export const userCrudIndexer: CrudIndexerConfig = {
|
|
|
105
111
|
}),
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
async function notifyRoleChanges(
|
|
115
|
+
ctx: CommandRuntimeContext,
|
|
116
|
+
user: User,
|
|
117
|
+
assignedRoles: string[],
|
|
118
|
+
revokedRoles: string[],
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
const tenantId = user.tenantId ? String(user.tenantId) : null
|
|
121
|
+
if (!tenantId) return
|
|
122
|
+
const organizationId = user.organizationId ? String(user.organizationId) : null
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
126
|
+
if (assignedRoles.length) {
|
|
127
|
+
const assignedType = notificationTypes.find((type) => type.type === 'auth.role.assigned')
|
|
128
|
+
if (assignedType) {
|
|
129
|
+
const notificationInput = buildNotificationFromType(assignedType, {
|
|
130
|
+
recipientUserId: String(user.id),
|
|
131
|
+
sourceEntityType: 'auth:user',
|
|
132
|
+
sourceEntityId: String(user.id),
|
|
133
|
+
})
|
|
134
|
+
await notificationService.create(notificationInput, { tenantId, organizationId })
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (revokedRoles.length) {
|
|
139
|
+
const revokedType = notificationTypes.find((type) => type.type === 'auth.role.revoked')
|
|
140
|
+
if (revokedType) {
|
|
141
|
+
const notificationInput = buildNotificationFromType(revokedType, {
|
|
142
|
+
recipientUserId: String(user.id),
|
|
143
|
+
sourceEntityType: 'auth:user',
|
|
144
|
+
sourceEntityId: String(user.id),
|
|
145
|
+
})
|
|
146
|
+
await notificationService.create(notificationInput, { tenantId, organizationId })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error('[auth.users.roles] Failed to create notification:', err)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
108
154
|
const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
109
155
|
id: 'auth.users.create',
|
|
110
156
|
async execute(rawInput, ctx) {
|
|
@@ -147,8 +193,10 @@ const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
147
193
|
throw error
|
|
148
194
|
}
|
|
149
195
|
|
|
196
|
+
let assignedRoles: string[] = []
|
|
150
197
|
if (Array.isArray(parsed.roles) && parsed.roles.length) {
|
|
151
198
|
await syncUserRoles(em, user, parsed.roles, tenantId)
|
|
199
|
+
assignedRoles = await loadUserRoleNames(em, String(user.id))
|
|
152
200
|
}
|
|
153
201
|
|
|
154
202
|
await setCustomFieldsIfAny({
|
|
@@ -173,6 +221,10 @@ const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
173
221
|
indexer: userCrudIndexer,
|
|
174
222
|
})
|
|
175
223
|
|
|
224
|
+
if (assignedRoles.length) {
|
|
225
|
+
await notifyRoleChanges(ctx, user, assignedRoles, [])
|
|
226
|
+
}
|
|
227
|
+
|
|
176
228
|
return user
|
|
177
229
|
},
|
|
178
230
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -288,6 +340,9 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
288
340
|
async execute(rawInput, ctx) {
|
|
289
341
|
const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)
|
|
290
342
|
const em = (ctx.container.resolve('em') as EntityManager)
|
|
343
|
+
const rolesBefore = Array.isArray(parsed.roles)
|
|
344
|
+
? await loadUserRoleNames(em, parsed.id)
|
|
345
|
+
: null
|
|
291
346
|
|
|
292
347
|
if (parsed.email !== undefined) {
|
|
293
348
|
const emailHash = computeEmailHash(parsed.email)
|
|
@@ -377,6 +432,14 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
377
432
|
indexer: userCrudIndexer,
|
|
378
433
|
})
|
|
379
434
|
|
|
435
|
+
if (Array.isArray(parsed.roles) && rolesBefore) {
|
|
436
|
+
const rolesAfter = await loadUserRoleNames(em, String(user.id))
|
|
437
|
+
const { assigned, revoked } = diffRoleChanges(rolesBefore, rolesAfter)
|
|
438
|
+
if (assigned.length || revoked.length) {
|
|
439
|
+
await notifyRoleChanges(ctx, user, assigned, revoked)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
380
443
|
await invalidateUserCache(ctx, parsed.id)
|
|
381
444
|
|
|
382
445
|
return user
|
|
@@ -772,6 +835,14 @@ async function invalidateUserCache(ctx: CommandRuntimeContext, userId: string) {
|
|
|
772
835
|
}
|
|
773
836
|
}
|
|
774
837
|
|
|
838
|
+
function diffRoleChanges(before: string[], after: string[]) {
|
|
839
|
+
const beforeSet = new Set(before)
|
|
840
|
+
const afterSet = new Set(after)
|
|
841
|
+
const assigned = after.filter((role) => !beforeSet.has(role))
|
|
842
|
+
const revoked = before.filter((role) => !afterSet.has(role))
|
|
843
|
+
return { assigned, revoked }
|
|
844
|
+
}
|
|
845
|
+
|
|
775
846
|
function arrayEquals(left: string[] | undefined, right: string[]): boolean {
|
|
776
847
|
if (!left) return false
|
|
777
848
|
if (left.length !== right.length) return false
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
3
|
+
|
|
4
|
+
const passwordSchema = buildPasswordSchema()
|
|
2
5
|
|
|
3
6
|
// Core auth validators
|
|
4
7
|
export const userLoginSchema = z.object({
|
|
@@ -13,7 +16,7 @@ export const requestPasswordResetSchema = z.object({
|
|
|
13
16
|
|
|
14
17
|
export const confirmPasswordResetSchema = z.object({
|
|
15
18
|
token: z.string().min(10),
|
|
16
|
-
password:
|
|
19
|
+
password: passwordSchema,
|
|
17
20
|
})
|
|
18
21
|
|
|
19
22
|
export const sidebarPreferencesInputSchema = z.object({
|
|
@@ -29,7 +32,7 @@ export const sidebarPreferencesInputSchema = z.object({
|
|
|
29
32
|
// Optional helpers for CLI or admin forms
|
|
30
33
|
export const userCreateSchema = z.object({
|
|
31
34
|
email: z.string().email(),
|
|
32
|
-
password:
|
|
35
|
+
password: passwordSchema,
|
|
33
36
|
tenantId: z.string().uuid().optional(),
|
|
34
37
|
organizationId: z.string().uuid(),
|
|
35
38
|
rolesCsv: z.string().optional(),
|