@open-mercato/core 0.6.6-develop.6089.1.a7ed560528 → 0.6.6-develop.6121.1.5797a901b3
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/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/catalog_product/index.js +52 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_variant/index.js +4 -0
- package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +28 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +103 -33
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/api/variants/route.js +4 -0
- package/dist/modules/catalog/api/variants/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +251 -14
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +72 -1
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
- package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
- package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/components/products/productForm.js +164 -2
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/variantForm.js +2 -0
- package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +93 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/types.js +28 -0
- package/dist/modules/catalog/data/types.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +100 -5
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/lib/gtin.js +61 -0
- package/dist/modules/catalog/lib/gtin.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
- package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
- package/dist/modules/catalog/translations.js +1 -1
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/api/cache/route.js +60 -10
- package/dist/modules/configs/api/cache/route.js.map +3 -3
- package/dist/modules/configs/components/CachePanel.js +52 -29
- package/dist/modules/configs/components/CachePanel.js.map +2 -2
- package/dist/modules/currencies/api/currencies/options/route.js +18 -4
- package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
- package/dist/modules/currencies/services/rateFetchingService.js +35 -18
- package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customers/backend/calendar/page.js +10 -0
- package/dist/modules/customers/backend/calendar/page.js.map +7 -0
- package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
- package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
- package/dist/modules/customers/cli.js +1 -0
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
- package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
- package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
- package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
- package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
- package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
- package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
- package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
- package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
- package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
- package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
- package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
- package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
- package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
- package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
- package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
- package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
- package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
- package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +30 -0
- package/dist/modules/customers/components/calendar/types.js.map +7 -0
- package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
- package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
- package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
- package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
- package/dist/modules/customers/lib/calendar/categories.js +28 -0
- package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
- package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
- package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
- package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
- package/dist/modules/customers/lib/calendar/grid.js +48 -0
- package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
- package/dist/modules/customers/lib/calendar/layout.js +39 -0
- package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
- package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
- package/dist/modules/customers/lib/calendar/preferences.js +71 -0
- package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
- package/dist/modules/customers/lib/calendar/range.js +46 -0
- package/dist/modules/customers/lib/calendar/range.js.map +7 -0
- package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
- package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +5 -5
- package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/route.js +33 -1
- package/dist/modules/dictionaries/api/route.js.map +2 -2
- package/dist/modules/feature_toggles/lib/queries.js +36 -52
- package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
- package/dist/modules/messages/commands/actions.js +81 -9
- package/dist/modules/messages/commands/actions.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
- package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
- package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +5 -3
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/planner/api/access.js +9 -0
- package/dist/modules/planner/api/access.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +22 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/api/availability-weekly.js +22 -1
- package/dist/modules/planner/api/availability-weekly.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
- package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
- package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
- package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
- package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
- package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
- package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/self/route.js +40 -0
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/api/context.js +7 -0
- package/dist/modules/translations/api/context.js.map +2 -2
- package/dist/modules/translations/api/put/locales.js +33 -1
- package/dist/modules/translations/api/put/locales.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
- package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
- package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
- package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
- package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
- package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
- package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
- package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/dist/modules/workflows/lib/graph-utils.js +8 -0
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +26 -0
- package/generated/entities/catalog_product_variant/index.ts +2 -0
- package/generated/entity-fields-registry.ts +28 -0
- package/package.json +7 -7
- package/src/modules/catalog/api/products/route.ts +123 -39
- package/src/modules/catalog/api/variants/route.ts +4 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
- package/src/modules/catalog/commands/products.ts +307 -2
- package/src/modules/catalog/commands/variants.ts +90 -1
- package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
- package/src/modules/catalog/components/products/productForm.ts +262 -0
- package/src/modules/catalog/components/products/variantForm.ts +4 -0
- package/src/modules/catalog/data/entities.ts +97 -0
- package/src/modules/catalog/data/types.ts +36 -0
- package/src/modules/catalog/data/validators.ts +159 -3
- package/src/modules/catalog/i18n/de.json +64 -0
- package/src/modules/catalog/i18n/en.json +64 -0
- package/src/modules/catalog/i18n/es.json +64 -0
- package/src/modules/catalog/i18n/pl.json +64 -0
- package/src/modules/catalog/lib/gtin.ts +66 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
- package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
- package/src/modules/catalog/translations.ts +1 -1
- package/src/modules/configs/api/cache/route.ts +72 -10
- package/src/modules/configs/components/CachePanel.tsx +59 -29
- package/src/modules/currencies/api/currencies/options/route.ts +22 -4
- package/src/modules/currencies/services/rateFetchingService.ts +63 -26
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
- package/src/modules/customer_accounts/i18n/de.json +1 -1
- package/src/modules/customer_accounts/i18n/en.json +1 -1
- package/src/modules/customer_accounts/i18n/es.json +1 -1
- package/src/modules/customer_accounts/i18n/pl.json +1 -1
- package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
- package/src/modules/customers/backend/calendar/page.tsx +12 -0
- package/src/modules/customers/cli.ts +1 -0
- package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
- package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
- package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
- package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
- package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
- package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
- package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
- package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
- package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
- package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
- package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
- package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
- package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
- package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
- package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
- package/src/modules/customers/components/calendar/types.ts +163 -0
- package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
- package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
- package/src/modules/customers/i18n/de.json +195 -0
- package/src/modules/customers/i18n/en.json +195 -0
- package/src/modules/customers/i18n/es.json +195 -0
- package/src/modules/customers/i18n/pl.json +195 -0
- package/src/modules/customers/lib/calendar/categories.ts +26 -0
- package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
- package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
- package/src/modules/customers/lib/calendar/grid.ts +50 -0
- package/src/modules/customers/lib/calendar/layout.ts +47 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
- package/src/modules/customers/lib/calendar/preferences.ts +95 -0
- package/src/modules/customers/lib/calendar/range.ts +45 -0
- package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
- package/src/modules/dashboards/services/widgetDataService.ts +8 -6
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
- package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
- package/src/modules/dictionaries/api/route.ts +36 -1
- package/src/modules/feature_toggles/lib/queries.ts +41 -60
- package/src/modules/messages/commands/actions.ts +100 -7
- package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
- package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
- package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
- package/src/modules/perspectives/services/perspectiveService.ts +9 -5
- package/src/modules/planner/api/access.ts +9 -0
- package/src/modules/planner/api/availability-date-specific.ts +22 -1
- package/src/modules/planner/api/availability-weekly.ts +22 -1
- package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
- package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
- package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
- package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
- package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
- package/src/modules/staff/api/team-members/self/route.ts +43 -0
- package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
- package/src/modules/translations/api/context.ts +7 -0
- package/src/modules/translations/api/put/locales.ts +35 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
- package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
- package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
- package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
- package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
- package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
- package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
- package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
- package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
- package/src/modules/workflows/lib/graph-utils.ts +21 -1
|
@@ -15,6 +15,13 @@ import {
|
|
|
15
15
|
import { getMessageType } from '../lib/message-types-registry'
|
|
16
16
|
import { assertOrganizationAccess, type MessageScopeInput } from './shared'
|
|
17
17
|
|
|
18
|
+
const actionStateSnapshotSchema = z.object({
|
|
19
|
+
actionTaken: z.string().nullable(),
|
|
20
|
+
actionTakenByUserId: z.string().nullable(),
|
|
21
|
+
actionTakenAt: z.string().nullable(),
|
|
22
|
+
actionResult: z.record(z.string(), z.unknown()).nullable(),
|
|
23
|
+
})
|
|
24
|
+
|
|
18
25
|
const recordTerminalActionSchema = z.object({
|
|
19
26
|
messageId: z.string().uuid(),
|
|
20
27
|
actionId: z.string().min(1),
|
|
@@ -22,6 +29,10 @@ const recordTerminalActionSchema = z.object({
|
|
|
22
29
|
tenantId: z.string().uuid(),
|
|
23
30
|
organizationId: z.string().uuid().nullable(),
|
|
24
31
|
userId: z.string().uuid(),
|
|
32
|
+
// Optional pre-claim snapshot supplied by `messages.actions.execute` so the
|
|
33
|
+
// undo log records the true (un-taken) state even though the terminal action
|
|
34
|
+
// was atomically claimed before this finalizer runs.
|
|
35
|
+
previousState: actionStateSnapshotSchema.optional(),
|
|
25
36
|
})
|
|
26
37
|
|
|
27
38
|
type RecordTerminalActionInput = z.infer<typeof recordTerminalActionSchema>
|
|
@@ -121,12 +132,35 @@ const recordTerminalActionCommand: CommandHandler<unknown, { ok: true }> = {
|
|
|
121
132
|
const input = recordTerminalActionSchema.parse(rawInput)
|
|
122
133
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
123
134
|
const message = await requireActionTarget(em, input)
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
const claimedByCaller =
|
|
136
|
+
message.actionTaken === input.actionId && message.actionTakenByUserId === input.userId
|
|
137
|
+
if (!claimedByCaller) {
|
|
138
|
+
if (message.actionTaken) {
|
|
139
|
+
throw new Error('Action already taken')
|
|
140
|
+
}
|
|
141
|
+
// Atomic compare-and-set: only one concurrent request transitions the
|
|
142
|
+
// message out of the un-taken state. nativeUpdate runs as a single
|
|
143
|
+
// `UPDATE ... WHERE action_taken IS NULL` so the loser matches 0 rows.
|
|
144
|
+
const claimedRows = await em.nativeUpdate(
|
|
145
|
+
Message,
|
|
146
|
+
{ id: input.messageId, tenantId: input.tenantId, actionTaken: null, deletedAt: null },
|
|
147
|
+
{
|
|
148
|
+
actionTaken: input.actionId,
|
|
149
|
+
actionTakenByUserId: input.userId,
|
|
150
|
+
actionTakenAt: new Date(),
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
if (claimedRows === 0) {
|
|
154
|
+
throw new Error('Action already taken')
|
|
155
|
+
}
|
|
156
|
+
message.actionTaken = input.actionId
|
|
157
|
+
message.actionTakenByUserId = input.userId
|
|
158
|
+
}
|
|
159
|
+
if (!message.actionTakenAt) {
|
|
160
|
+
message.actionTakenAt = new Date()
|
|
126
161
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
message.actionTakenAt = new Date()
|
|
162
|
+
// action_result is an encrypted column, so it must be written through the
|
|
163
|
+
// flush path (the encryption subscriber) rather than nativeUpdate.
|
|
130
164
|
message.actionResult = input.result
|
|
131
165
|
await em.flush()
|
|
132
166
|
return { ok: true }
|
|
@@ -139,6 +173,13 @@ const recordTerminalActionCommand: CommandHandler<unknown, { ok: true }> = {
|
|
|
139
173
|
},
|
|
140
174
|
buildLog: async ({ input, snapshots }) => {
|
|
141
175
|
const parsed = recordTerminalActionSchema.parse(input)
|
|
176
|
+
// When the action was pre-claimed by `messages.actions.execute`, the
|
|
177
|
+
// prepare-captured snapshot already reflects the claimed state, so prefer
|
|
178
|
+
// the explicit pre-claim snapshot for the undo baseline.
|
|
179
|
+
const before =
|
|
180
|
+
(parsed.previousState as ActionStateSnapshot | undefined) ??
|
|
181
|
+
(snapshots.before as ActionStateSnapshot | undefined) ??
|
|
182
|
+
null
|
|
142
183
|
return {
|
|
143
184
|
actionLabel: 'Execute message terminal action',
|
|
144
185
|
resourceKind: 'messages.message',
|
|
@@ -147,11 +188,11 @@ const recordTerminalActionCommand: CommandHandler<unknown, { ok: true }> = {
|
|
|
147
188
|
organizationId: parsed.organizationId,
|
|
148
189
|
payload: {
|
|
149
190
|
undo: {
|
|
150
|
-
before
|
|
191
|
+
before,
|
|
151
192
|
after: (snapshots.after as ActionStateSnapshot | undefined) ?? null,
|
|
152
193
|
} satisfies UndoPayload<ActionStateSnapshot>,
|
|
153
194
|
},
|
|
154
|
-
snapshotBefore:
|
|
195
|
+
snapshotBefore: before,
|
|
155
196
|
snapshotAfter: snapshots.after ?? null,
|
|
156
197
|
}
|
|
157
198
|
},
|
|
@@ -209,6 +250,52 @@ const executeActionCommand: CommandHandler<
|
|
|
209
250
|
}
|
|
210
251
|
}
|
|
211
252
|
|
|
253
|
+
// Capture the un-taken state for the undo log before reserving the action.
|
|
254
|
+
const previousState = snapshotActionState(message)
|
|
255
|
+
let claimedTerminal = false
|
|
256
|
+
const releaseTerminalClaim = async () => {
|
|
257
|
+
if (!claimedTerminal) return
|
|
258
|
+
claimedTerminal = false
|
|
259
|
+
await em.nativeUpdate(
|
|
260
|
+
Message,
|
|
261
|
+
{
|
|
262
|
+
id: message.id,
|
|
263
|
+
tenantId: input.tenantId,
|
|
264
|
+
actionTaken: action.id,
|
|
265
|
+
actionTakenByUserId: input.userId,
|
|
266
|
+
},
|
|
267
|
+
{ actionTaken: null, actionTakenByUserId: null, actionTakenAt: null },
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (shouldRecordActionTaken) {
|
|
272
|
+
// Atomically reserve the terminal action BEFORE running the target
|
|
273
|
+
// command so concurrent requests cannot both execute it. The losing
|
|
274
|
+
// request matches 0 rows and surfaces the existing 409 response.
|
|
275
|
+
const claimedRows = await em.nativeUpdate(
|
|
276
|
+
Message,
|
|
277
|
+
{ id: message.id, tenantId: input.tenantId, actionTaken: null, deletedAt: null },
|
|
278
|
+
{
|
|
279
|
+
actionTaken: action.id,
|
|
280
|
+
actionTakenByUserId: input.userId,
|
|
281
|
+
actionTakenAt: new Date(),
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
if (claimedRows === 0) {
|
|
285
|
+
const current = await findOneWithDecryption(
|
|
286
|
+
em,
|
|
287
|
+
Message,
|
|
288
|
+
{ id: message.id, tenantId: input.tenantId, deletedAt: null },
|
|
289
|
+
undefined,
|
|
290
|
+
{ tenantId: input.tenantId, organizationId: input.organizationId },
|
|
291
|
+
)
|
|
292
|
+
throw Object.assign(new Error('Action already taken'), {
|
|
293
|
+
actionTaken: current?.actionTaken ?? message.actionTaken ?? action.id,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
claimedTerminal = true
|
|
297
|
+
}
|
|
298
|
+
|
|
212
299
|
const commandBus = ctx.container.resolve('commandBus') as {
|
|
213
300
|
execute: (
|
|
214
301
|
commandId: string,
|
|
@@ -263,6 +350,9 @@ const executeActionCommand: CommandHandler<
|
|
|
263
350
|
operationLogEntry = commandResult.logEntry ?? null
|
|
264
351
|
} catch (err) {
|
|
265
352
|
console.error('[messages] executeActionCommand sub-command failed', err)
|
|
353
|
+
// The target command never completed — release the reservation so the
|
|
354
|
+
// action stays retryable, matching the pre-claim failure semantics.
|
|
355
|
+
await releaseTerminalClaim()
|
|
266
356
|
throw new Error('Action failed')
|
|
267
357
|
}
|
|
268
358
|
} else if (action.href) {
|
|
@@ -272,10 +362,12 @@ const executeActionCommand: CommandHandler<
|
|
|
272
362
|
userId: input.userId,
|
|
273
363
|
})
|
|
274
364
|
if (!safeRedirect) {
|
|
365
|
+
await releaseTerminalClaim()
|
|
275
366
|
throw new Error('Action has an unsafe redirect target')
|
|
276
367
|
}
|
|
277
368
|
result = { redirect: safeRedirect }
|
|
278
369
|
} else {
|
|
370
|
+
await releaseTerminalClaim()
|
|
279
371
|
throw new Error('Action has no executable target')
|
|
280
372
|
}
|
|
281
373
|
|
|
@@ -288,6 +380,7 @@ const executeActionCommand: CommandHandler<
|
|
|
288
380
|
tenantId: input.tenantId,
|
|
289
381
|
organizationId: input.organizationId,
|
|
290
382
|
userId: input.userId,
|
|
383
|
+
previousState,
|
|
291
384
|
},
|
|
292
385
|
ctx: {
|
|
293
386
|
container: ctx.container,
|
|
@@ -2,6 +2,10 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import {
|
|
6
|
+
runCrudMutationGuardAfterSuccess,
|
|
7
|
+
validateCrudMutationGuard,
|
|
8
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
5
9
|
import { deleteUserPerspective } from '@open-mercato/core/modules/perspectives/services/perspectiveService'
|
|
6
10
|
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
7
11
|
import { perspectivesTag, perspectivesErrorSchema, perspectivesSuccessSchema } from '../../openapi'
|
|
@@ -40,7 +44,22 @@ export async function DELETE(req: Request, ctx: { params: { tableId: string; per
|
|
|
40
44
|
}
|
|
41
45
|
})()
|
|
42
46
|
|
|
43
|
-
await
|
|
47
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
48
|
+
tenantId: auth.tenantId ?? '',
|
|
49
|
+
organizationId: auth.orgId ?? null,
|
|
50
|
+
userId: auth.sub,
|
|
51
|
+
resourceKind: 'perspectives.perspective',
|
|
52
|
+
resourceId: perspectiveId,
|
|
53
|
+
operation: 'delete',
|
|
54
|
+
requestMethod: req.method,
|
|
55
|
+
requestHeaders: req.headers,
|
|
56
|
+
mutationPayload: { tableId, perspectiveId },
|
|
57
|
+
})
|
|
58
|
+
if (guardResult && !guardResult.ok) {
|
|
59
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const deleted = await deleteUserPerspective(em, cache, {
|
|
44
63
|
scope: {
|
|
45
64
|
userId: auth.sub,
|
|
46
65
|
tenantId: auth.tenantId ?? null,
|
|
@@ -50,6 +69,20 @@ export async function DELETE(req: Request, ctx: { params: { tableId: string; per
|
|
|
50
69
|
perspectiveId,
|
|
51
70
|
})
|
|
52
71
|
|
|
72
|
+
if (deleted && guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
73
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
74
|
+
tenantId: auth.tenantId ?? '',
|
|
75
|
+
organizationId: auth.orgId ?? null,
|
|
76
|
+
userId: auth.sub,
|
|
77
|
+
resourceKind: 'perspectives.perspective',
|
|
78
|
+
resourceId: perspectiveId,
|
|
79
|
+
operation: 'delete',
|
|
80
|
+
requestMethod: req.method,
|
|
81
|
+
requestHeaders: req.headers,
|
|
82
|
+
metadata: guardResult.metadata ?? null,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
53
86
|
return NextResponse.json({ success: true })
|
|
54
87
|
}
|
|
55
88
|
|
|
@@ -2,6 +2,10 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import {
|
|
6
|
+
runCrudMutationGuardAfterSuccess,
|
|
7
|
+
validateCrudMutationGuard,
|
|
8
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
5
9
|
import { clearRolePerspectives } from '@open-mercato/core/modules/perspectives/services/perspectiveService'
|
|
6
10
|
import { Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
7
11
|
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -48,13 +52,42 @@ export async function DELETE(req: Request, ctx: { params: { tableId: string; rol
|
|
|
48
52
|
const role = await em.findOne(Role, { id: roleId, deletedAt: null, ...(scope as any) } as any)
|
|
49
53
|
if (!role) return NextResponse.json({ error: 'Role not found' }, { status: 404 })
|
|
50
54
|
|
|
51
|
-
await
|
|
55
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
56
|
+
tenantId: auth.tenantId ?? '',
|
|
57
|
+
organizationId: auth.orgId ?? null,
|
|
58
|
+
userId: auth.sub,
|
|
59
|
+
resourceKind: 'perspectives.role_perspective',
|
|
60
|
+
resourceId: roleId,
|
|
61
|
+
operation: 'delete',
|
|
62
|
+
requestMethod: req.method,
|
|
63
|
+
requestHeaders: req.headers,
|
|
64
|
+
mutationPayload: { tableId, roleId },
|
|
65
|
+
})
|
|
66
|
+
if (guardResult && !guardResult.ok) {
|
|
67
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const clearedCount = await clearRolePerspectives(em, cache, {
|
|
52
71
|
tableId,
|
|
53
72
|
tenantId: auth.tenantId ?? null,
|
|
54
73
|
organizationId: auth.orgId ?? null,
|
|
55
74
|
roleIds: [roleId],
|
|
56
75
|
})
|
|
57
76
|
|
|
77
|
+
if (clearedCount > 0 && guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
78
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
79
|
+
tenantId: auth.tenantId ?? '',
|
|
80
|
+
organizationId: auth.orgId ?? null,
|
|
81
|
+
userId: auth.sub,
|
|
82
|
+
resourceKind: 'perspectives.role_perspective',
|
|
83
|
+
resourceId: roleId,
|
|
84
|
+
operation: 'delete',
|
|
85
|
+
requestMethod: req.method,
|
|
86
|
+
requestHeaders: req.headers,
|
|
87
|
+
metadata: guardResult.metadata ?? null,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
return NextResponse.json({ success: true })
|
|
59
92
|
}
|
|
60
93
|
|
|
@@ -4,6 +4,10 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
5
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
6
6
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
|
+
import {
|
|
8
|
+
runCrudMutationGuardAfterSuccess,
|
|
9
|
+
validateCrudMutationGuard,
|
|
10
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
7
11
|
import { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'
|
|
8
12
|
import {
|
|
9
13
|
loadPerspectivesState,
|
|
@@ -168,6 +172,7 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
|
|
|
168
172
|
const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)
|
|
169
173
|
const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)
|
|
170
174
|
const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0
|
|
175
|
+
const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))
|
|
171
176
|
|
|
172
177
|
if (hasRoleOps) {
|
|
173
178
|
const canApplyToRoles = await rbac.userHasAllFeatures?.(
|
|
@@ -183,7 +188,6 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
|
|
|
183
188
|
const roleScope = auth.tenantId
|
|
184
189
|
? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }
|
|
185
190
|
: { tenantId: null }
|
|
186
|
-
const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))
|
|
187
191
|
const roles = await em.find(Role, {
|
|
188
192
|
id: { $in: targetRoleIds as any },
|
|
189
193
|
...(roleScope as any),
|
|
@@ -197,44 +201,86 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
|
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
|
|
204
|
+
const guardResourceId = parsed.data.perspectiveId ?? tableId
|
|
205
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
206
|
+
tenantId: auth.tenantId ?? '',
|
|
207
|
+
organizationId: auth.orgId ?? null,
|
|
208
|
+
userId: auth.sub,
|
|
209
|
+
resourceKind: 'perspectives.perspective',
|
|
210
|
+
resourceId: guardResourceId,
|
|
211
|
+
operation: 'custom',
|
|
212
|
+
requestMethod: req.method,
|
|
213
|
+
requestHeaders: req.headers,
|
|
214
|
+
mutationPayload: { ...parsed.data, tableId },
|
|
215
|
+
})
|
|
216
|
+
if (guardResult && !guardResult.ok) {
|
|
217
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let roleGuardResult: Awaited<ReturnType<typeof validateCrudMutationGuard>> | null = null
|
|
221
|
+
if (hasRoleOps) {
|
|
222
|
+
roleGuardResult = await validateCrudMutationGuard(container, {
|
|
223
|
+
tenantId: auth.tenantId ?? '',
|
|
224
|
+
organizationId: auth.orgId ?? null,
|
|
225
|
+
userId: auth.sub,
|
|
226
|
+
resourceKind: 'perspectives.role_perspective',
|
|
227
|
+
resourceId: targetRoleIds.join(','),
|
|
228
|
+
operation: 'custom',
|
|
229
|
+
requestMethod: req.method,
|
|
230
|
+
requestHeaders: req.headers,
|
|
231
|
+
mutationPayload: {
|
|
232
|
+
tableId,
|
|
233
|
+
applyToRoles,
|
|
234
|
+
clearRoleIds,
|
|
235
|
+
name: parsed.data.name,
|
|
236
|
+
settings: parsed.data.settings,
|
|
237
|
+
setRoleDefault: parsed.data.setRoleDefault ?? false,
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
if (roleGuardResult && !roleGuardResult.ok) {
|
|
242
|
+
return NextResponse.json(roleGuardResult.body, { status: roleGuardResult.status })
|
|
243
|
+
}
|
|
244
|
+
|
|
200
245
|
let saved: Awaited<ReturnType<typeof saveUserPerspective>> | null = null
|
|
201
246
|
let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null
|
|
247
|
+
let clearedRolePerspectiveCount = 0
|
|
202
248
|
|
|
203
249
|
try {
|
|
204
250
|
await withAtomicFlush(em, [
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
tableId,
|
|
209
|
-
input: parsed.data,
|
|
210
|
-
request: req,
|
|
211
|
-
})
|
|
212
|
-
},
|
|
213
|
-
async () => {
|
|
214
|
-
if (applyToRoles.length) {
|
|
215
|
-
updatedRolePerspectives = await saveRolePerspectives(em, cache, {
|
|
216
|
-
tableId,
|
|
217
|
-
tenantId: auth.tenantId ?? null,
|
|
218
|
-
organizationId: auth.orgId ?? null,
|
|
219
|
-
input: {
|
|
220
|
-
roleIds: applyToRoles,
|
|
221
|
-
name: parsed.data.name,
|
|
222
|
-
settings: parsed.data.settings,
|
|
223
|
-
setDefault: parsed.data.setRoleDefault ?? false,
|
|
224
|
-
},
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
async () => {
|
|
229
|
-
if (clearRoleIds.length) {
|
|
230
|
-
await clearRolePerspectives(em, cache, {
|
|
251
|
+
async () => {
|
|
252
|
+
saved = await saveUserPerspective(em, cache, {
|
|
253
|
+
scope,
|
|
231
254
|
tableId,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
roleIds: clearRoleIds,
|
|
255
|
+
input: parsed.data,
|
|
256
|
+
request: req,
|
|
235
257
|
})
|
|
236
|
-
}
|
|
237
|
-
|
|
258
|
+
},
|
|
259
|
+
async () => {
|
|
260
|
+
if (applyToRoles.length) {
|
|
261
|
+
updatedRolePerspectives = await saveRolePerspectives(em, cache, {
|
|
262
|
+
tableId,
|
|
263
|
+
tenantId: auth.tenantId ?? null,
|
|
264
|
+
organizationId: auth.orgId ?? null,
|
|
265
|
+
input: {
|
|
266
|
+
roleIds: applyToRoles,
|
|
267
|
+
name: parsed.data.name,
|
|
268
|
+
settings: parsed.data.settings,
|
|
269
|
+
setDefault: parsed.data.setRoleDefault ?? false,
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
async () => {
|
|
275
|
+
if (clearRoleIds.length) {
|
|
276
|
+
clearedRolePerspectiveCount = await clearRolePerspectives(em, cache, {
|
|
277
|
+
tableId,
|
|
278
|
+
tenantId: auth.tenantId ?? null,
|
|
279
|
+
organizationId: auth.orgId ?? null,
|
|
280
|
+
roleIds: clearRoleIds,
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
},
|
|
238
284
|
], { transaction: true })
|
|
239
285
|
} catch (err) {
|
|
240
286
|
if (isCrudHttpError(err)) {
|
|
@@ -243,6 +289,35 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
|
|
|
243
289
|
throw err
|
|
244
290
|
}
|
|
245
291
|
|
|
292
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
293
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
294
|
+
tenantId: auth.tenantId ?? '',
|
|
295
|
+
organizationId: auth.orgId ?? null,
|
|
296
|
+
userId: auth.sub,
|
|
297
|
+
resourceKind: 'perspectives.perspective',
|
|
298
|
+
resourceId: guardResourceId,
|
|
299
|
+
operation: 'custom',
|
|
300
|
+
requestMethod: req.method,
|
|
301
|
+
requestHeaders: req.headers,
|
|
302
|
+
metadata: guardResult.metadata ?? null,
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const didWriteRolePerspectives = applyToRoles.length > 0 || clearedRolePerspectiveCount > 0
|
|
307
|
+
if (didWriteRolePerspectives && roleGuardResult?.ok && roleGuardResult.shouldRunAfterSuccess) {
|
|
308
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
309
|
+
tenantId: auth.tenantId ?? '',
|
|
310
|
+
organizationId: auth.orgId ?? null,
|
|
311
|
+
userId: auth.sub,
|
|
312
|
+
resourceKind: 'perspectives.role_perspective',
|
|
313
|
+
resourceId: targetRoleIds.join(','),
|
|
314
|
+
operation: 'custom',
|
|
315
|
+
requestMethod: req.method,
|
|
316
|
+
requestHeaders: req.headers,
|
|
317
|
+
metadata: roleGuardResult.metadata ?? null,
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
246
321
|
return NextResponse.json({
|
|
247
322
|
perspective: saved,
|
|
248
323
|
rolePerspectives: updatedRolePerspectives ?? [],
|
|
@@ -309,7 +309,7 @@ export async function deleteUserPerspective(
|
|
|
309
309
|
em: EntityManager,
|
|
310
310
|
cache: CacheStrategy | null | undefined,
|
|
311
311
|
options: { scope: PerspectiveScope; tableId: string; perspectiveId: string },
|
|
312
|
-
): Promise<
|
|
312
|
+
): Promise<boolean> {
|
|
313
313
|
const { scope, tableId, perspectiveId } = options
|
|
314
314
|
const tenantId = scope.tenantId ?? null
|
|
315
315
|
const organizationId = scope.organizationId ?? null
|
|
@@ -322,7 +322,7 @@ export async function deleteUserPerspective(
|
|
|
322
322
|
tableId,
|
|
323
323
|
deletedAt: null,
|
|
324
324
|
})
|
|
325
|
-
if (!existing) return
|
|
325
|
+
if (!existing) return false
|
|
326
326
|
|
|
327
327
|
existing.deletedAt = new Date()
|
|
328
328
|
existing.isDefault = false
|
|
@@ -331,6 +331,8 @@ export async function deleteUserPerspective(
|
|
|
331
331
|
if (cache?.deleteByTags) {
|
|
332
332
|
await cache.deleteByTags([userTag(scope, tableId)])
|
|
333
333
|
}
|
|
334
|
+
|
|
335
|
+
return true
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
export async function saveRolePerspectives(
|
|
@@ -430,13 +432,13 @@ export async function clearRolePerspectives(
|
|
|
430
432
|
organizationId?: string | null
|
|
431
433
|
roleIds: string[]
|
|
432
434
|
},
|
|
433
|
-
): Promise<
|
|
435
|
+
): Promise<number> {
|
|
434
436
|
const { tableId, roleIds } = options
|
|
435
437
|
const tenantId = options.tenantId ?? null
|
|
436
438
|
const organizationId = options.organizationId ?? null
|
|
437
|
-
if (!roleIds.length) return
|
|
439
|
+
if (!roleIds.length) return 0
|
|
438
440
|
|
|
439
|
-
await em.nativeUpdate(
|
|
441
|
+
const affected = await em.nativeUpdate(
|
|
440
442
|
RolePerspective,
|
|
441
443
|
{
|
|
442
444
|
roleId: { $in: roleIds as any },
|
|
@@ -452,4 +454,6 @@ export async function clearRolePerspectives(
|
|
|
452
454
|
const tags = roleIds.map((roleId) => roleTag(roleId, tableId, tenantId))
|
|
453
455
|
await cache.deleteByTags(tags)
|
|
454
456
|
}
|
|
457
|
+
|
|
458
|
+
return affected
|
|
455
459
|
}
|
|
@@ -79,3 +79,12 @@ export async function assertAvailabilityWriteAccess(
|
|
|
79
79
|
}
|
|
80
80
|
return access
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
export function resolveAvailabilityActorId(auth: AuthContext): string {
|
|
84
|
+
if (auth) {
|
|
85
|
+
if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub
|
|
86
|
+
if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId
|
|
87
|
+
if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId
|
|
88
|
+
}
|
|
89
|
+
return 'system'
|
|
90
|
+
}
|
|
@@ -11,9 +11,13 @@ import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
|
|
|
11
11
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
12
12
|
import { plannerAvailabilityDateSpecificReplaceSchema } from '../data/validators'
|
|
13
13
|
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
14
|
+
import {
|
|
15
|
+
runCrudMutationGuardAfterSuccess,
|
|
16
|
+
validateCrudMutationGuard,
|
|
17
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
14
18
|
import { PlannerAvailabilityRule } from '../data/entities'
|
|
15
19
|
import { parseAvailabilityRuleWindow } from '../lib/availabilitySchedule'
|
|
16
|
-
import { assertAvailabilityWriteAccess } from './access'
|
|
20
|
+
import { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from './access'
|
|
17
21
|
|
|
18
22
|
export const metadata = {
|
|
19
23
|
POST: { requireAuth: true },
|
|
@@ -93,8 +97,25 @@ export async function POST(req: Request) {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
}
|
|
100
|
+
const guardInput = {
|
|
101
|
+
tenantId: input.tenantId,
|
|
102
|
+
organizationId: input.organizationId,
|
|
103
|
+
userId: resolveAvailabilityActorId(ctx.auth),
|
|
104
|
+
resourceKind: 'planner.availability',
|
|
105
|
+
resourceId: input.subjectId,
|
|
106
|
+
operation: 'custom' as const,
|
|
107
|
+
requestMethod: req.method,
|
|
108
|
+
requestHeaders: req.headers,
|
|
109
|
+
}
|
|
110
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input })
|
|
111
|
+
if (guardResult && !guardResult.ok) {
|
|
112
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
113
|
+
}
|
|
96
114
|
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
97
115
|
const { logEntry } = await commandBus.execute('planner.availability.date-specific.replace', { input, ctx })
|
|
116
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
117
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null })
|
|
118
|
+
}
|
|
98
119
|
const response = NextResponse.json({ ok: true })
|
|
99
120
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
100
121
|
response.headers.set(
|
|
@@ -9,7 +9,11 @@ import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/er
|
|
|
9
9
|
import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
|
|
10
10
|
import { plannerAvailabilityWeeklyReplaceSchema } from '../data/validators'
|
|
11
11
|
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
runCrudMutationGuardAfterSuccess,
|
|
14
|
+
validateCrudMutationGuard,
|
|
15
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
16
|
+
import { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from './access'
|
|
13
17
|
|
|
14
18
|
export const metadata = {
|
|
15
19
|
POST: { requireAuth: true },
|
|
@@ -55,8 +59,25 @@ export async function POST(req: Request) {
|
|
|
55
59
|
const payload = await req.json().catch(() => ({}))
|
|
56
60
|
const input = parseScopedCommandInput(plannerAvailabilityWeeklyReplaceSchema, payload, ctx, translate)
|
|
57
61
|
await assertAvailabilityWriteAccess(ctx, { subjectType: input.subjectType, subjectId: input.subjectId }, translate)
|
|
62
|
+
const guardInput = {
|
|
63
|
+
tenantId: input.tenantId,
|
|
64
|
+
organizationId: input.organizationId,
|
|
65
|
+
userId: resolveAvailabilityActorId(ctx.auth),
|
|
66
|
+
resourceKind: 'planner.availability',
|
|
67
|
+
resourceId: input.subjectId,
|
|
68
|
+
operation: 'custom' as const,
|
|
69
|
+
requestMethod: req.method,
|
|
70
|
+
requestHeaders: req.headers,
|
|
71
|
+
}
|
|
72
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input })
|
|
73
|
+
if (guardResult && !guardResult.ok) {
|
|
74
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
75
|
+
}
|
|
58
76
|
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
59
77
|
const { logEntry } = await commandBus.execute('planner.availability.weekly.replace', { input, ctx })
|
|
78
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
79
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null })
|
|
80
|
+
}
|
|
60
81
|
const response = NextResponse.json({ ok: true })
|
|
61
82
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
62
83
|
response.headers.set(
|
|
@@ -9,6 +9,10 @@ import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/er
|
|
|
9
9
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
10
|
import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
|
|
11
11
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
+
import {
|
|
13
|
+
runCrudMutationGuardAfterSuccess,
|
|
14
|
+
validateCrudMutationGuard,
|
|
15
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
12
16
|
import {
|
|
13
17
|
resourcesResourceTagAssignmentSchema,
|
|
14
18
|
type ResourcesResourceTagAssignmentInput,
|
|
@@ -37,16 +41,54 @@ async function buildContext(
|
|
|
37
41
|
return { ctx, translate }
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
function resolveActorId(ctx: CommandRuntimeContext): string {
|
|
45
|
+
const auth = ctx.auth
|
|
46
|
+
if (auth && typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub
|
|
47
|
+
if (auth && typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId
|
|
48
|
+
if (auth && typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId
|
|
49
|
+
return 'system'
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
export async function POST(req: Request) {
|
|
41
53
|
try {
|
|
42
54
|
const { ctx, translate } = await buildContext(req)
|
|
43
55
|
const body = await req.json().catch(() => ({}))
|
|
44
56
|
const input = parseScopedCommandInput(resourcesResourceTagAssignmentSchema, body, ctx, translate)
|
|
57
|
+
const actorId = resolveActorId(ctx)
|
|
58
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
59
|
+
tenantId: input.tenantId,
|
|
60
|
+
organizationId: input.organizationId,
|
|
61
|
+
userId: actorId,
|
|
62
|
+
resourceKind: 'resources.resourceTagAssignment',
|
|
63
|
+
resourceId: input.resourceId,
|
|
64
|
+
operation: 'custom',
|
|
65
|
+
requestMethod: req.method,
|
|
66
|
+
requestHeaders: req.headers,
|
|
67
|
+
mutationPayload: input,
|
|
68
|
+
})
|
|
69
|
+
if (guardResult && !guardResult.ok) {
|
|
70
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
71
|
+
}
|
|
72
|
+
|
|
45
73
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
46
74
|
const { result, logEntry } = await commandBus.execute<ResourcesResourceTagAssignmentInput, { assignmentId: string }>(
|
|
47
75
|
'resources.resourceTags.assign',
|
|
48
76
|
{ input, ctx },
|
|
49
77
|
)
|
|
78
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
79
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
80
|
+
tenantId: input.tenantId,
|
|
81
|
+
organizationId: input.organizationId,
|
|
82
|
+
userId: actorId,
|
|
83
|
+
resourceKind: 'resources.resourceTagAssignment',
|
|
84
|
+
resourceId: input.resourceId,
|
|
85
|
+
operation: 'custom',
|
|
86
|
+
requestMethod: req.method,
|
|
87
|
+
requestHeaders: req.headers,
|
|
88
|
+
metadata: guardResult.metadata ?? null,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
50
92
|
const response = NextResponse.json({ id: result?.assignmentId ?? null }, { status: 201 })
|
|
51
93
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
52
94
|
response.headers.set(
|