@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/dictionaries/api/%5BdictionaryId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryIdParamsSchema,\n dictionaryUpdateSchema,\n upsertDictionarySchema,\n} from '../openapi'\nimport { dictionaryKeySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n// System dictionaries use namespaced keys (e.g. `sales.deal_loss_reason`,\n// `resources.activity-types`) that the strict create-key regex rejects. The\n// manager edit dialog disables the key field but still resubmits the existing\n// key, so the update parse must accept any stored key verbatim. The strict\n// user-key regex is only enforced below when the key actually changes.\nconst updateKeySchema = z.string().trim().min(1).max(100)\nconst updateSchema = upsertDictionarySchema\n .partial()\n .extend({ key: updateKeySchema.optional() })\n .refine((data) => Object.keys(data).length > 0, {\n message: 'Provide at least one field to update.',\n })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nfunction isProtectedCurrencyDictionary(dictionary: Dictionary) {\n const key = dictionary.key?.trim().toLowerCase() ?? ''\n return key === 'currency' || key === 'currencies'\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionary' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = updateSchema.parse(await req.json().catch(() => ({})))\n const dictionary = await loadDictionary(context, dictionaryId)\n\n enforceCommandOptimisticLock({\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n current: dictionary.updatedAt ?? null,\n request: req,\n })\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n if (payload.isActive === false) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n }\n\n if (payload.key) {\n const key = payload.key.trim().toLowerCase()\n if (key !== dictionary.key) {\n const strictKey = dictionaryKeySchema.safeParse(key)\n if (!strictKey.success) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.invalid_key', 'Use lowercase letters, numbers, hyphen, or underscore.') })\n }\n const organizationId = context.organizationId\n if (!organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const existing = await context.em.findOne(Dictionary, {\n key,\n organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n dictionary.key = key\n }\n }\n\n if (payload.name) {\n dictionary.name = payload.name.trim()\n }\n if (payload.description !== undefined) {\n dictionary.description = payload.description ? payload.description.trim() : null\n }\n if (payload.isActive !== undefined) {\n dictionary.isActive = Boolean(payload.isActive)\n if (!dictionary.isActive) {\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n } else {\n dictionary.deletedAt = null\n }\n }\n if (payload.entrySortMode !== undefined) {\n dictionary.entrySortMode = payload.entrySortMode\n }\n\n dictionary.updatedAt = new Date()\n await context.em.flush()\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: err.issues[0]?.message ?? 'Validation failed' }, { status: 400 })\n }\n console.error('[dictionaries/:id.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId)\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n\n dictionary.isActive = false\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n await context.em.flush()\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary' }, { status: 500 })\n }\n}\n\nconst dictionaryGetDoc: OpenApiMethodDoc = {\n summary: 'Get dictionary',\n description: 'Returns details for the specified dictionary, including inheritance flags.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary details.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary',\n description: 'Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: dictionaryUpdateSchema,\n description: 'Fields to update on the dictionary.',\n },\n responses: [\n { status: 200, description: 'Dictionary updated.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or protected dictionary', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary',\n description: 'Soft deletes the dictionary unless it is the protected currency dictionary.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary archived.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Protected dictionary cannot be deleted', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary resource',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryGetDoc,\n PATCH: dictionaryPatchDoc,\n DELETE: dictionaryDeleteDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,kBAAkB;AAC3B,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryIdParamsSchema,\n dictionaryUpdateSchema,\n upsertDictionarySchema,\n} from '../openapi'\nimport { dictionaryKeySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n// System dictionaries use namespaced keys (e.g. `sales.deal_loss_reason`,\n// `resources.activity-types`) that the strict create-key regex rejects. The\n// manager edit dialog disables the key field but still resubmits the existing\n// key, so the update parse must accept any stored key verbatim. The strict\n// user-key regex is only enforced below when the key actually changes.\nconst updateKeySchema = z.string().trim().min(1).max(100)\nconst updateSchema = upsertDictionarySchema\n .partial()\n .extend({ key: updateKeySchema.optional() })\n .refine((data) => Object.keys(data).length > 0, {\n message: 'Provide at least one field to update.',\n })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nfunction isProtectedCurrencyDictionary(dictionary: Dictionary) {\n const key = dictionary.key?.trim().toLowerCase() ?? ''\n return key === 'currency' || key === 'currencies'\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionary' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = updateSchema.parse(await req.json().catch(() => ({})))\n const dictionary = await loadDictionary(context, dictionaryId)\n\n enforceCommandOptimisticLock({\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n current: dictionary.updatedAt ?? null,\n request: req,\n })\n\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n if (payload.isActive === false) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n }\n\n if (payload.key) {\n const key = payload.key.trim().toLowerCase()\n if (key !== dictionary.key) {\n const strictKey = dictionaryKeySchema.safeParse(key)\n if (!strictKey.success) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.invalid_key', 'Use lowercase letters, numbers, hyphen, or underscore.') })\n }\n const organizationId = context.organizationId\n if (!organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const existing = await context.em.findOne(Dictionary, {\n key,\n organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n dictionary.key = key\n }\n }\n\n if (payload.name) {\n dictionary.name = payload.name.trim()\n }\n if (payload.description !== undefined) {\n dictionary.description = payload.description ? payload.description.trim() : null\n }\n if (payload.isActive !== undefined) {\n dictionary.isActive = Boolean(payload.isActive)\n if (!dictionary.isActive) {\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n } else {\n dictionary.deletedAt = null\n }\n }\n if (payload.entrySortMode !== undefined) {\n dictionary.entrySortMode = payload.entrySortMode\n }\n\n dictionary.updatedAt = new Date()\n await context.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: err.issues[0]?.message ?? 'Validation failed' }, { status: 400 })\n }\n console.error('[dictionaries/:id.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId)\n\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n\n dictionary.isActive = false\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n await context.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary' }, { status: 500 })\n }\n}\n\nconst dictionaryGetDoc: OpenApiMethodDoc = {\n summary: 'Get dictionary',\n description: 'Returns details for the specified dictionary, including inheritance flags.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary details.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary',\n description: 'Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: dictionaryUpdateSchema,\n description: 'Fields to update on the dictionary.',\n },\n responses: [\n { status: 200, description: 'Dictionary updated.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or protected dictionary', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary',\n description: 'Soft deletes the dictionary unless it is the protected currency dictionary.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary archived.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Protected dictionary cannot be deleted', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary resource',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryGetDoc,\n PATCH: dictionaryPatchDoc,\n DELETE: dictionaryDeleteDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC,gCAAgC;AAC1E,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oCAAoC;AAC7C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAEpC,MAAM,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAMjE,MAAM,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AACxD,MAAM,eAAe,uBAClB,QAAQ,EACR,OAAO,EAAE,KAAK,gBAAgB,SAAS,EAAE,CAAC,EAC1C,OAAO,CAAC,SAAS,OAAO,KAAK,IAAI,EAAE,SAAS,GAAG;AAAA,EAC9C,SAAS;AACX,CAAC;AAEI,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EACjE,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACrE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACxE;AAEA,SAAS,8BAA8B,YAAwB;AAC7D,QAAM,MAAM,WAAW,KAAK,KAAK,EAAE,YAAY,KAAK;AACpD,SAAO,QAAQ,cAAc,QAAQ;AACvC;AAEA,eAAe,eACb,SACA,IACA,UAAwC,CAAC,GACzC;AACA,QAAM,EAAE,iBAAiB,MAAM,IAAI;AACnC,MAAI,CAAC,kBAAkB,CAAC,QAAQ,gBAAgB;AAC9C,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,6CAA6C,kCAAkC,EAAE,CAAC;AAAA,EAC5I;AACA,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB,WAAW;AAAA,EACb;AACA,QAAM,SAAS,iBACX;AAAA,IACE,GAAG;AAAA,IACH,GAAI,QAAQ,wBAAwB,SAChC,EAAE,gBAAgB,EAAE,KAAK,QAAQ,wBAAwB,EAAE,IAC3D,CAAC;AAAA,EACP,IACA;AAAA,IACE,GAAG;AAAA,IACH,gBAAgB,QAAQ;AAAA,EAC1B;AACJ,QAAM,aAAa,MAAM,QAAQ,GAAG,QAAQ,YAAY,MAAM;AAC9D,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,iCAAiC,sBAAsB,EAAE,CAAC;AAAA,EACpH;AACA,SAAO;AACT;AAEA,eAAsB,IAAI,KAAc,KAA6C;AACnF,MAAI;AACF,UAAM,UAAU,MAAM,gCAAgC,GAAG;AACzD,UAAM,EAAE,aAAa,IAAI,aAAa,MAAM,EAAE,cAAc,IAAI,QAAQ,aAAa,CAAC;AACtF,UAAM,aAAa,MAAM,eAAe,SAAS,cAAc,EAAE,gBAAgB,KAAK,CAAC;AACvF,WAAO,aAAa,KAAK;AAAA,MACvB,IAAI,WAAW;AAAA,MACf,KAAK,WAAW;AAAA,MAChB,MAAM,WAAW;AAAA,MACjB,aAAa,WAAW;AAAA,MACxB,UAAU,WAAW;AAAA,MACrB,UAAU,WAAW;AAAA,MACrB,mBAAmB,WAAW;AAAA,MAC9B,eAAe,+BAA+B,WAAW,aAAa;AAAA,MACtE,gBAAgB,WAAW;AAAA,MAC3B,aAAa,QAAQ,iBAAiB,WAAW,mBAAmB,QAAQ,iBAAiB;AAAA,MAC7F,WAAW,WAAW;AAAA,MACtB,WAAW,WAAW;AAAA,IACxB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,YAAQ,MAAM,2CAA2C,GAAG;AAC5D,WAAO,aAAa,KAAK,EAAE,OAAO,4BAA4B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AACF;AAEA,eAAsB,MAAM,KAAc,KAA6C;AACrF,MAAI;AACF,UAAM,UAAU,MAAM,gCAAgC,GAAG;AACzD,UAAM,EAAE,aAAa,IAAI,aAAa,MAAM,EAAE,cAAc,IAAI,QAAQ,aAAa,CAAC;AACtF,UAAM,UAAU,aAAa,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC;AACrE,UAAM,aAAa,MAAM,eAAe,SAAS,YAAY;AAE7D,iCAA6B;AAAA,MAC3B,cAAc;AAAA,MACd,YAAY,WAAW;AAAA,MACvB,SAAS,WAAW,aAAa;AAAA,MACjC,SAAS;AAAA,IACX,CAAC;AAED,UAAM,cAAc,yBAAyB,QAAQ,IAAI;AACzD,UAAM,cAAc,MAAM,0BAA0B,QAAQ,WAAW;AAAA,MACrE,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,YAAY,WAAW;AAAA,MACvB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB;AAAA,IACnB,CAAC;AACD,QAAI,eAAe,CAAC,YAAY,IAAI;AAClC,aAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,IAC3E;AAEA,QAAI,8BAA8B,UAAU,GAAG;AAC7C,UAAI,QAAQ,OAAO,QAAQ,IAAI,KAAK,EAAE,YAAY,MAAM,WAAW,KAAK;AACtE,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,0CAA0C,wDAAwD,EAAE,CAAC;AAAA,MAC/J;AACA,UAAI,QAAQ,aAAa,OAAO;AAC9B,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,0CAA0C,wDAAwD,EAAE,CAAC;AAAA,MAC/J;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK;AACf,YAAM,MAAM,QAAQ,IAAI,KAAK,EAAE,YAAY;AAC3C,UAAI,QAAQ,WAAW,KAAK;AAC1B,cAAM,YAAY,oBAAoB,UAAU,GAAG;AACnD,YAAI,CAAC,UAAU,SAAS;AACtB,gBAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,mCAAmC,wDAAwD,EAAE,CAAC;AAAA,QACxJ;AACA,cAAM,iBAAiB,QAAQ;AAC/B,YAAI,CAAC,gBAAgB;AACnB,gBAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,6CAA6C,kCAAkC,EAAE,CAAC;AAAA,QAC5I;AACA,cAAM,WAAW,MAAM,QAAQ,GAAG,QAAQ,YAAY;AAAA,UACpD;AAAA,UACA;AAAA,UACA,UAAU,QAAQ;AAAA,UAClB,WAAW;AAAA,QACb,CAAC;AACD,YAAI,UAAU;AACZ,gBAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,iCAAiC,2CAA2C,EAAE,CAAC;AAAA,QACzI;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAEA,QAAI,QAAQ,MAAM;AAChB,iBAAW,OAAO,QAAQ,KAAK,KAAK;AAAA,IACtC;AACA,QAAI,QAAQ,gBAAgB,QAAW;AACrC,iBAAW,cAAc,QAAQ,cAAc,QAAQ,YAAY,KAAK,IAAI;AAAA,IAC9E;AACA,QAAI,QAAQ,aAAa,QAAW;AAClC,iBAAW,WAAW,QAAQ,QAAQ,QAAQ;AAC9C,UAAI,CAAC,WAAW,UAAU;AACxB,mBAAW,YAAY,WAAW,aAAa,oBAAI,KAAK;AAAA,MAC1D,OAAO;AACL,mBAAW,YAAY;AAAA,MACzB;AAAA,IACF;AACA,QAAI,QAAQ,kBAAkB,QAAW;AACvC,iBAAW,gBAAgB,QAAQ;AAAA,IACrC;AAEA,eAAW,YAAY,oBAAI,KAAK;AAChC,UAAM,QAAQ,GAAG,MAAM;AAEvB,QAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,YAAM,iCAAiC,QAAQ,WAAW;AAAA,QACxD,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,QACxB,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY,WAAW;AAAA,QACvB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,UAAU,YAAY,YAAY;AAAA,MACpC,CAAC;AAAA,IACH;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,IAAI,WAAW;AAAA,MACf,KAAK,WAAW;AAAA,MAChB,MAAM,WAAW;AAAA,MACjB,aAAa,WAAW;AAAA,MACxB,UAAU,WAAW;AAAA,MACrB,UAAU,WAAW;AAAA,MACrB,mBAAmB,WAAW;AAAA,MAC9B,eAAe,+BAA+B,WAAW,aAAa;AAAA,MACtE,WAAW,WAAW;AAAA,MACtB,WAAW,WAAW;AAAA,IACxB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,OAAO,CAAC,GAAG,WAAW,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpG;AACA,YAAQ,MAAM,6CAA6C,GAAG;AAC9D,WAAO,aAAa,KAAK,EAAE,OAAO,8BAA8B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpF;AACF;AAEA,eAAsB,OAAO,KAAc,KAA6C;AACtF,MAAI;AACF,UAAM,UAAU,MAAM,gCAAgC,GAAG;AACzD,UAAM,EAAE,aAAa,IAAI,aAAa,MAAM,EAAE,cAAc,IAAI,QAAQ,aAAa,CAAC;AACtF,UAAM,aAAa,MAAM,eAAe,SAAS,YAAY;AAE7D,UAAM,cAAc,yBAAyB,QAAQ,IAAI;AACzD,UAAM,cAAc,MAAM,0BAA0B,QAAQ,WAAW;AAAA,MACrE,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,YAAY,WAAW;AAAA,MACvB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB;AAAA,IACnB,CAAC;AACD,QAAI,eAAe,CAAC,YAAY,IAAI;AAClC,aAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,IAC3E;AAEA,QAAI,8BAA8B,UAAU,GAAG;AAC7C,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,0CAA0C,wDAAwD,EAAE,CAAC;AAAA,IAC/J;AAEA,eAAW,WAAW;AACtB,eAAW,YAAY,WAAW,aAAa,oBAAI,KAAK;AACxD,UAAM,QAAQ,GAAG,MAAM;AAEvB,QAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,YAAM,iCAAiC,QAAQ,WAAW;AAAA,QACxD,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,QACxB,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY,WAAW;AAAA,QACvB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,UAAU,YAAY,YAAY;AAAA,MACpC,CAAC;AAAA,IACH;AAEA,WAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvC,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,YAAQ,MAAM,8CAA8C,GAAG;AAC/D,WAAO,aAAa,KAAK,EAAE,OAAO,8BAA8B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpF;AACF;AAEA,MAAM,mBAAqC;AAAA,EACzC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,eAAe;AAAA,EACtB,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,uBAAuB,QAAQ,uBAAuB;AAAA,EACpF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,sBAAsB,QAAQ,wBAAwB;AAAA,IAClF,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,wBAAwB;AAAA,IACvF,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,wBAAwB;AAAA,IACpF,EAAE,QAAQ,KAAK,aAAa,6BAA6B,QAAQ,wBAAwB;AAAA,EAC3F;AACF;AAEA,MAAM,qBAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,eAAe;AAAA,EACtB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,uBAAuB,QAAQ,uBAAuB;AAAA,EACpF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,6CAA6C,QAAQ,wBAAwB;AAAA,IACzG,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,wBAAwB;AAAA,IACvF,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,wBAAwB;AAAA,IACpF,EAAE,QAAQ,KAAK,aAAa,iCAAiC,QAAQ,wBAAwB;AAAA,IAC7F,EAAE,QAAQ,KAAK,aAAa,+BAA+B,QAAQ,wBAAwB;AAAA,EAC7F;AACF;AAEA,MAAM,sBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,eAAe;AAAA,EACtB,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,qBAAqB;AAAA,EACnF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,0CAA0C,QAAQ,wBAAwB;AAAA,IACtG,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,wBAAwB;AAAA,IACvF,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,wBAAwB;AAAA,IACpF,EAAE,QAAQ,KAAK,aAAa,+BAA+B,QAAQ,wBAAwB;AAAA,EAC7F;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AAAA,IACP,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { Dictionary } from "@open-mercato/core/modules/dictionaries/data/entities";
|
|
3
|
-
import { resolveDictionariesRouteContext } from "@open-mercato/core/modules/dictionaries/api/context";
|
|
3
|
+
import { resolveDictionariesRouteContext, resolveDictionaryActorId } from "@open-mercato/core/modules/dictionaries/api/context";
|
|
4
4
|
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
|
+
import {
|
|
6
|
+
runCrudMutationGuardAfterSuccess,
|
|
7
|
+
validateCrudMutationGuard
|
|
8
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
5
9
|
import {
|
|
6
10
|
DEFAULT_DICTIONARY_ENTRY_SORT_MODE,
|
|
7
11
|
resolveDictionaryEntrySortMode
|
|
@@ -89,8 +93,36 @@ async function POST(req) {
|
|
|
89
93
|
createdAt: /* @__PURE__ */ new Date(),
|
|
90
94
|
updatedAt: /* @__PURE__ */ new Date()
|
|
91
95
|
});
|
|
96
|
+
const guardUserId = resolveDictionaryActorId(context.auth);
|
|
97
|
+
const guardResult = await validateCrudMutationGuard(context.container, {
|
|
98
|
+
tenantId: context.tenantId,
|
|
99
|
+
organizationId: context.organizationId,
|
|
100
|
+
userId: guardUserId,
|
|
101
|
+
resourceKind: "dictionaries.dictionary",
|
|
102
|
+
resourceId: dictionary.id,
|
|
103
|
+
operation: "create",
|
|
104
|
+
requestMethod: req.method,
|
|
105
|
+
requestHeaders: req.headers,
|
|
106
|
+
mutationPayload: payload
|
|
107
|
+
});
|
|
108
|
+
if (guardResult && !guardResult.ok) {
|
|
109
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
110
|
+
}
|
|
92
111
|
context.em.persist(dictionary);
|
|
93
112
|
await context.em.flush();
|
|
113
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
114
|
+
await runCrudMutationGuardAfterSuccess(context.container, {
|
|
115
|
+
tenantId: context.tenantId,
|
|
116
|
+
organizationId: context.organizationId,
|
|
117
|
+
userId: guardUserId,
|
|
118
|
+
resourceKind: "dictionaries.dictionary",
|
|
119
|
+
resourceId: dictionary.id,
|
|
120
|
+
operation: "create",
|
|
121
|
+
requestMethod: req.method,
|
|
122
|
+
requestHeaders: req.headers,
|
|
123
|
+
metadata: guardResult.metadata ?? null
|
|
124
|
+
});
|
|
125
|
+
}
|
|
94
126
|
return NextResponse.json({
|
|
95
127
|
id: dictionary.id,
|
|
96
128
|
key: dictionary.key,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dictionaries/api/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryListQuerySchema,\n dictionaryListResponseSchema,\n upsertDictionarySchema,\n} from './openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const url = new URL(req.url)\n const includeInactive = parseBooleanToken(url.searchParams.get('includeInactive')) === true\n\n const organizationFilter = context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}\n\n const items = await context.em.find(\n Dictionary,\n {\n ...organizationFilter,\n tenantId: context.tenantId,\n deletedAt: null,\n ...(includeInactive ? {} : { isActive: true }),\n },\n { orderBy: { name: 'asc' } },\n )\n\n return NextResponse.json({\n items: items.map((dictionary) => ({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })),\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionaries' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const payload = upsertDictionarySchema.parse(await req.json().catch(() => ({})))\n const key = payload.key.trim().toLowerCase()\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n\n const existing = await context.em.findOne(Dictionary, {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n key,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n\n const dictionary = context.em.create(Dictionary, {\n key,\n name: payload.name.trim(),\n description: payload.description?.trim() || null,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n isSystem: payload.isSystem ?? false,\n isActive: payload.isActive ?? true,\n managerVisibility: 'default',\n entrySortMode: payload.entrySortMode ?? DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n context.em.persist(dictionary)\n await context.em.flush()\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.POST] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to create dictionary' }, { status: 500 })\n }\n}\n\nconst dictionariesGetDoc: OpenApiMethodDoc = {\n summary: 'List dictionaries',\n description: 'Returns dictionaries accessible to the current organization, optionally including inactive records.',\n tags: [dictionariesTag],\n query: dictionaryListQuerySchema,\n responses: [\n { status: 200, description: 'Dictionary collection.', schema: dictionaryListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionaries', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionariesPostDoc: OpenApiMethodDoc = {\n summary: 'Create dictionary',\n description: 'Registers a dictionary scoped to the current organization.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: upsertDictionarySchema,\n description: 'Dictionary definition including unique key and display name.',\n },\n responses: [\n { status: 201, description: 'Dictionary created.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to create dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionaries collection',\n methods: {\n GET: dictionariesGetDoc,\n POST: dictionariesPostDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryListQuerySchema,\n dictionaryListResponseSchema,\n upsertDictionarySchema,\n} from './openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const url = new URL(req.url)\n const includeInactive = parseBooleanToken(url.searchParams.get('includeInactive')) === true\n\n const organizationFilter = context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}\n\n const items = await context.em.find(\n Dictionary,\n {\n ...organizationFilter,\n tenantId: context.tenantId,\n deletedAt: null,\n ...(includeInactive ? {} : { isActive: true }),\n },\n { orderBy: { name: 'asc' } },\n )\n\n return NextResponse.json({\n items: items.map((dictionary) => ({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })),\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionaries' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const payload = upsertDictionarySchema.parse(await req.json().catch(() => ({})))\n const key = payload.key.trim().toLowerCase()\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n\n const existing = await context.em.findOne(Dictionary, {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n key,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n\n const dictionary = context.em.create(Dictionary, {\n key,\n name: payload.name.trim(),\n description: payload.description?.trim() || null,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n isSystem: payload.isSystem ?? false,\n isActive: payload.isActive ?? true,\n managerVisibility: 'default',\n entrySortMode: payload.entrySortMode ?? DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n context.em.persist(dictionary)\n await context.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.POST] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to create dictionary' }, { status: 500 })\n }\n}\n\nconst dictionariesGetDoc: OpenApiMethodDoc = {\n summary: 'List dictionaries',\n description: 'Returns dictionaries accessible to the current organization, optionally including inactive records.',\n tags: [dictionariesTag],\n query: dictionaryListQuerySchema,\n responses: [\n { status: 200, description: 'Dictionary collection.', schema: dictionaryListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionaries', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionariesPostDoc: OpenApiMethodDoc = {\n summary: 'Create dictionary',\n description: 'Registers a dictionary scoped to the current organization.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: upsertDictionarySchema,\n description: 'Dictionary definition including unique key and display name.',\n },\n responses: [\n { status: 201, description: 'Dictionary created.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to create dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionaries collection',\n methods: {\n GET: dictionariesGetDoc,\n POST: dictionariesPostDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { QueryOrder } from "@mikro-orm/postgresql";
|
|
1
2
|
import { FeatureToggle, FeatureToggleOverride } from "../data/entities.js";
|
|
2
3
|
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
4
|
+
const SORTABLE_FIELDS = /* @__PURE__ */ new Set(["identifier", "name", "category"]);
|
|
3
5
|
async function getOverrides(em, tenant, query) {
|
|
4
6
|
const filters = { deletedAt: null };
|
|
5
7
|
if (query.category) {
|
|
@@ -11,63 +13,45 @@ async function getOverrides(em, tenant, query) {
|
|
|
11
13
|
if (query.identifier) {
|
|
12
14
|
filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` };
|
|
13
15
|
}
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
+
const page = query.page ?? 1;
|
|
17
|
+
const pageSize = query.pageSize ?? 25;
|
|
18
|
+
const offset = (page - 1) * pageSize;
|
|
19
|
+
const direction = query.sortDir === "desc" ? QueryOrder.DESC : QueryOrder.ASC;
|
|
20
|
+
const orderBy = {};
|
|
21
|
+
if (query.sortField && SORTABLE_FIELDS.has(query.sortField)) {
|
|
22
|
+
orderBy[query.sortField] = direction;
|
|
23
|
+
}
|
|
24
|
+
orderBy.id = QueryOrder.ASC;
|
|
25
|
+
const totalItems = await em.count(FeatureToggle, filters);
|
|
26
|
+
const globalToggles = await em.find(FeatureToggle, filters, {
|
|
27
|
+
orderBy,
|
|
28
|
+
limit: pageSize,
|
|
29
|
+
offset
|
|
30
|
+
});
|
|
31
|
+
const overrides = globalToggles.length ? await em.find(FeatureToggleOverride, {
|
|
16
32
|
tenantId: tenant.id,
|
|
17
33
|
toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } }
|
|
18
|
-
});
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (override) {
|
|
23
|
-
response.push({
|
|
24
|
-
id: override?.id ?? "",
|
|
25
|
-
toggleId: toggle.id,
|
|
26
|
-
tenantName: tenant.name,
|
|
27
|
-
tenantId: tenant.id,
|
|
28
|
-
identifier: toggle.identifier,
|
|
29
|
-
name: toggle.name,
|
|
30
|
-
category: toggle.category ?? "",
|
|
31
|
-
isOverride: true
|
|
32
|
-
});
|
|
33
|
-
} else {
|
|
34
|
-
response.push({
|
|
35
|
-
id: "",
|
|
36
|
-
toggleId: toggle.id,
|
|
37
|
-
tenantName: tenant.name,
|
|
38
|
-
tenantId: tenant.id,
|
|
39
|
-
identifier: toggle.identifier,
|
|
40
|
-
name: toggle.name,
|
|
41
|
-
category: toggle.category ?? "",
|
|
42
|
-
isOverride: false
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
if (query.sortField && query.sortDir) {
|
|
47
|
-
const sortField = query.sortField;
|
|
48
|
-
const sortDir = query.sortDir;
|
|
49
|
-
response.sort((a, b) => {
|
|
50
|
-
let aValue = a[sortField];
|
|
51
|
-
let bValue = b[sortField];
|
|
52
|
-
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
53
|
-
const comparison = aValue.localeCompare(bValue);
|
|
54
|
-
return sortDir === "asc" ? comparison : -comparison;
|
|
55
|
-
}
|
|
56
|
-
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
57
|
-
return sortDir === "asc" ? aValue - bValue : bValue - aValue;
|
|
58
|
-
}
|
|
59
|
-
return 0;
|
|
60
|
-
});
|
|
34
|
+
}) : [];
|
|
35
|
+
const overridesByToggleId = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const override of overrides) {
|
|
37
|
+
overridesByToggleId.set(override.toggle.id, override);
|
|
61
38
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
39
|
+
const items = globalToggles.map((toggle) => {
|
|
40
|
+
const override = overridesByToggleId.get(toggle.id);
|
|
41
|
+
return {
|
|
42
|
+
id: override?.id ?? "",
|
|
43
|
+
toggleId: toggle.id,
|
|
44
|
+
tenantName: tenant.name,
|
|
45
|
+
tenantId: tenant.id,
|
|
46
|
+
identifier: toggle.identifier,
|
|
47
|
+
name: toggle.name,
|
|
48
|
+
category: toggle.category ?? "",
|
|
49
|
+
isOverride: Boolean(override)
|
|
50
|
+
};
|
|
51
|
+
});
|
|
65
52
|
const totalPages = Math.ceil(totalItems / pageSize);
|
|
66
|
-
const startIndex = (page - 1) * pageSize;
|
|
67
|
-
const endIndex = startIndex + pageSize;
|
|
68
|
-
const paginatedItems = response.slice(startIndex, endIndex);
|
|
69
53
|
return {
|
|
70
|
-
items
|
|
54
|
+
items,
|
|
71
55
|
total: totalItems,
|
|
72
56
|
totalPages,
|
|
73
57
|
page,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/feature_toggles/lib/queries.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { OverrideListResponse } from '../data/validators'\n\nexport type FeatureToggleOverrideResponse = {\n id: string\n toggleId: string\n overrideState: 'enabled' | 'disabled' | 'inherit'\n tenantName: string\n tenantId: string\n identifier: string\n name: string\n category: string\n defaultState: boolean\n}\n\nexport type GetOverridesQuery = {\n category?: string\n name?: string\n identifier?: string\n defaultState?: 'enabled' | 'disabled'\n overrideState?: 'enabled' | 'disabled' | 'inherit'\n sortField?: 'identifier' | 'name' | 'category' | 'defaultState' | 'overrideState'\n sortDir?: 'asc' | 'desc'\n page?: number\n pageSize?: number\n}\n\ntype GetOverridesResult = {\n items: OverrideListResponse[]\n total: number\n totalPages: number\n page: number\n pageSize: number\n raw: {\n toggles: FeatureToggle[]\n overrides: FeatureToggleOverride[]\n }\n}\n\nexport async function getOverrides(\n em: EntityManager,\n tenant: Tenant,\n query: GetOverridesQuery\n): Promise<GetOverridesResult> {\n const filters: FilterQuery<FeatureToggle> = { deletedAt: null }\n if (query.category) {\n filters.category = { $ilike: `%${escapeLikePattern(query.category)}%` }\n }\n if (query.name) {\n filters.name = { $ilike: `%${escapeLikePattern(query.name)}%` }\n }\n if (query.identifier) {\n filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }\n }\n\n const globalToggles = await em.find(FeatureToggle, filters)\n\n const overrides = await em.find(FeatureToggleOverride, {\n
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { EntityManager, FilterQuery, QueryOrder } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { OverrideListResponse } from '../data/validators'\n\nexport type FeatureToggleOverrideResponse = {\n id: string\n toggleId: string\n overrideState: 'enabled' | 'disabled' | 'inherit'\n tenantName: string\n tenantId: string\n identifier: string\n name: string\n category: string\n defaultState: boolean\n}\n\nexport type GetOverridesQuery = {\n category?: string\n name?: string\n identifier?: string\n defaultState?: 'enabled' | 'disabled'\n overrideState?: 'enabled' | 'disabled' | 'inherit'\n sortField?: 'identifier' | 'name' | 'category' | 'defaultState' | 'overrideState'\n sortDir?: 'asc' | 'desc'\n page?: number\n pageSize?: number\n}\n\nconst SORTABLE_FIELDS = new Set<keyof FeatureToggle>(['identifier', 'name', 'category'])\n\ntype GetOverridesResult = {\n items: OverrideListResponse[]\n total: number\n totalPages: number\n page: number\n pageSize: number\n raw: {\n toggles: FeatureToggle[]\n overrides: FeatureToggleOverride[]\n }\n}\n\nexport async function getOverrides(\n em: EntityManager,\n tenant: Tenant,\n query: GetOverridesQuery\n): Promise<GetOverridesResult> {\n const filters: FilterQuery<FeatureToggle> = { deletedAt: null }\n if (query.category) {\n filters.category = { $ilike: `%${escapeLikePattern(query.category)}%` }\n }\n if (query.name) {\n filters.name = { $ilike: `%${escapeLikePattern(query.name)}%` }\n }\n if (query.identifier) {\n filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }\n }\n\n const page = query.page ?? 1\n const pageSize = query.pageSize ?? 25\n const offset = (page - 1) * pageSize\n\n const direction = query.sortDir === 'desc' ? QueryOrder.DESC : QueryOrder.ASC\n const orderBy: Partial<Record<keyof FeatureToggle, QueryOrder>> = {}\n if (query.sortField && SORTABLE_FIELDS.has(query.sortField as keyof FeatureToggle)) {\n orderBy[query.sortField as keyof FeatureToggle] = direction\n }\n orderBy.id = QueryOrder.ASC\n\n const totalItems = await em.count(FeatureToggle, filters)\n\n const globalToggles = await em.find(FeatureToggle, filters, {\n orderBy,\n limit: pageSize,\n offset,\n })\n\n const overrides = globalToggles.length\n ? await em.find(FeatureToggleOverride, {\n tenantId: tenant.id,\n toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },\n })\n : []\n\n const overridesByToggleId = new Map<string, FeatureToggleOverride>()\n for (const override of overrides) {\n overridesByToggleId.set(override.toggle.id, override)\n }\n\n const items: OverrideListResponse[] = globalToggles.map((toggle) => {\n const override = overridesByToggleId.get(toggle.id)\n return {\n id: override?.id ?? '',\n toggleId: toggle.id,\n tenantName: tenant.name,\n tenantId: tenant.id,\n identifier: toggle.identifier,\n name: toggle.name,\n category: toggle.category ?? '',\n isOverride: Boolean(override),\n }\n })\n\n const totalPages = Math.ceil(totalItems / pageSize)\n\n return {\n items,\n total: totalItems,\n totalPages,\n page,\n pageSize,\n raw: {\n toggles: globalToggles,\n overrides,\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAqC,kBAAkB;AACvD,SAAS,eAAe,6BAA6B;AAErD,SAAS,yBAAyB;AA2BlC,MAAM,kBAAkB,oBAAI,IAAyB,CAAC,cAAc,QAAQ,UAAU,CAAC;AAcvF,eAAsB,aAClB,IACA,QACA,OAC2B;AAC3B,QAAM,UAAsC,EAAE,WAAW,KAAK;AAC9D,MAAI,MAAM,UAAU;AAChB,YAAQ,WAAW,EAAE,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,CAAC,IAAI;AAAA,EAC1E;AACA,MAAI,MAAM,MAAM;AACZ,YAAQ,OAAO,EAAE,QAAQ,IAAI,kBAAkB,MAAM,IAAI,CAAC,IAAI;AAAA,EAClE;AACA,MAAI,MAAM,YAAY;AAClB,YAAQ,aAAa,EAAE,QAAQ,IAAI,kBAAkB,MAAM,UAAU,CAAC,IAAI;AAAA,EAC9E;AAEA,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,UAAU,OAAO,KAAK;AAE5B,QAAM,YAAY,MAAM,YAAY,SAAS,WAAW,OAAO,WAAW;AAC1E,QAAM,UAA4D,CAAC;AACnE,MAAI,MAAM,aAAa,gBAAgB,IAAI,MAAM,SAAgC,GAAG;AAChF,YAAQ,MAAM,SAAgC,IAAI;AAAA,EACtD;AACA,UAAQ,KAAK,WAAW;AAExB,QAAM,aAAa,MAAM,GAAG,MAAM,eAAe,OAAO;AAExD,QAAM,gBAAgB,MAAM,GAAG,KAAK,eAAe,SAAS;AAAA,IACxD;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACJ,CAAC;AAED,QAAM,YAAY,cAAc,SAC1B,MAAM,GAAG,KAAK,uBAAuB;AAAA,IACjC,UAAU,OAAO;AAAA,IACjB,QAAQ,EAAE,IAAI,EAAE,KAAK,cAAc,IAAI,CAAC,WAAW,OAAO,EAAE,EAAE,EAAE;AAAA,EACpE,CAAC,IACD,CAAC;AAEP,QAAM,sBAAsB,oBAAI,IAAmC;AACnE,aAAW,YAAY,WAAW;AAC9B,wBAAoB,IAAI,SAAS,OAAO,IAAI,QAAQ;AAAA,EACxD;AAEA,QAAM,QAAgC,cAAc,IAAI,CAAC,WAAW;AAChE,UAAM,WAAW,oBAAoB,IAAI,OAAO,EAAE;AAClD,WAAO;AAAA,MACH,IAAI,UAAU,MAAM;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,MACb,UAAU,OAAO,YAAY;AAAA,MAC7B,YAAY,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACJ,CAAC;AAED,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ;AAElD,SAAO;AAAA,IACH;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK;AAAA,MACD,SAAS;AAAA,MACT;AAAA,IACJ;AAAA,EACJ;AACJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,13 +13,23 @@ import {
|
|
|
13
13
|
} from "../lib/actions.js";
|
|
14
14
|
import { getMessageType } from "../lib/message-types-registry.js";
|
|
15
15
|
import { assertOrganizationAccess } from "./shared.js";
|
|
16
|
+
const actionStateSnapshotSchema = z.object({
|
|
17
|
+
actionTaken: z.string().nullable(),
|
|
18
|
+
actionTakenByUserId: z.string().nullable(),
|
|
19
|
+
actionTakenAt: z.string().nullable(),
|
|
20
|
+
actionResult: z.record(z.string(), z.unknown()).nullable()
|
|
21
|
+
});
|
|
16
22
|
const recordTerminalActionSchema = z.object({
|
|
17
23
|
messageId: z.string().uuid(),
|
|
18
24
|
actionId: z.string().min(1),
|
|
19
25
|
result: z.record(z.string(), z.unknown()),
|
|
20
26
|
tenantId: z.string().uuid(),
|
|
21
27
|
organizationId: z.string().uuid().nullable(),
|
|
22
|
-
userId: z.string().uuid()
|
|
28
|
+
userId: z.string().uuid(),
|
|
29
|
+
// Optional pre-claim snapshot supplied by `messages.actions.execute` so the
|
|
30
|
+
// undo log records the true (un-taken) state even though the terminal action
|
|
31
|
+
// was atomically claimed before this finalizer runs.
|
|
32
|
+
previousState: actionStateSnapshotSchema.optional()
|
|
23
33
|
});
|
|
24
34
|
const executeActionSchema = z.object({
|
|
25
35
|
messageId: z.string().uuid(),
|
|
@@ -100,12 +110,29 @@ const recordTerminalActionCommand = {
|
|
|
100
110
|
const input = recordTerminalActionSchema.parse(rawInput);
|
|
101
111
|
const em = ctx.container.resolve("em").fork();
|
|
102
112
|
const message = await requireActionTarget(em, input);
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
const claimedByCaller = message.actionTaken === input.actionId && message.actionTakenByUserId === input.userId;
|
|
114
|
+
if (!claimedByCaller) {
|
|
115
|
+
if (message.actionTaken) {
|
|
116
|
+
throw new Error("Action already taken");
|
|
117
|
+
}
|
|
118
|
+
const claimedRows = await em.nativeUpdate(
|
|
119
|
+
Message,
|
|
120
|
+
{ id: input.messageId, tenantId: input.tenantId, actionTaken: null, deletedAt: null },
|
|
121
|
+
{
|
|
122
|
+
actionTaken: input.actionId,
|
|
123
|
+
actionTakenByUserId: input.userId,
|
|
124
|
+
actionTakenAt: /* @__PURE__ */ new Date()
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
if (claimedRows === 0) {
|
|
128
|
+
throw new Error("Action already taken");
|
|
129
|
+
}
|
|
130
|
+
message.actionTaken = input.actionId;
|
|
131
|
+
message.actionTakenByUserId = input.userId;
|
|
132
|
+
}
|
|
133
|
+
if (!message.actionTakenAt) {
|
|
134
|
+
message.actionTakenAt = /* @__PURE__ */ new Date();
|
|
105
135
|
}
|
|
106
|
-
message.actionTaken = input.actionId;
|
|
107
|
-
message.actionTakenByUserId = input.userId;
|
|
108
|
-
message.actionTakenAt = /* @__PURE__ */ new Date();
|
|
109
136
|
message.actionResult = input.result;
|
|
110
137
|
await em.flush();
|
|
111
138
|
return { ok: true };
|
|
@@ -118,6 +145,7 @@ const recordTerminalActionCommand = {
|
|
|
118
145
|
},
|
|
119
146
|
buildLog: async ({ input, snapshots }) => {
|
|
120
147
|
const parsed = recordTerminalActionSchema.parse(input);
|
|
148
|
+
const before = parsed.previousState ?? snapshots.before ?? null;
|
|
121
149
|
return {
|
|
122
150
|
actionLabel: "Execute message terminal action",
|
|
123
151
|
resourceKind: "messages.message",
|
|
@@ -126,11 +154,11 @@ const recordTerminalActionCommand = {
|
|
|
126
154
|
organizationId: parsed.organizationId,
|
|
127
155
|
payload: {
|
|
128
156
|
undo: {
|
|
129
|
-
before
|
|
157
|
+
before,
|
|
130
158
|
after: snapshots.after ?? null
|
|
131
159
|
}
|
|
132
160
|
},
|
|
133
|
-
snapshotBefore:
|
|
161
|
+
snapshotBefore: before,
|
|
134
162
|
snapshotAfter: snapshots.after ?? null
|
|
135
163
|
};
|
|
136
164
|
},
|
|
@@ -179,6 +207,46 @@ const executeActionCommand = {
|
|
|
179
207
|
}
|
|
180
208
|
}
|
|
181
209
|
}
|
|
210
|
+
const previousState = snapshotActionState(message);
|
|
211
|
+
let claimedTerminal = false;
|
|
212
|
+
const releaseTerminalClaim = async () => {
|
|
213
|
+
if (!claimedTerminal) return;
|
|
214
|
+
claimedTerminal = false;
|
|
215
|
+
await em.nativeUpdate(
|
|
216
|
+
Message,
|
|
217
|
+
{
|
|
218
|
+
id: message.id,
|
|
219
|
+
tenantId: input.tenantId,
|
|
220
|
+
actionTaken: action.id,
|
|
221
|
+
actionTakenByUserId: input.userId
|
|
222
|
+
},
|
|
223
|
+
{ actionTaken: null, actionTakenByUserId: null, actionTakenAt: null }
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
if (shouldRecordActionTaken) {
|
|
227
|
+
const claimedRows = await em.nativeUpdate(
|
|
228
|
+
Message,
|
|
229
|
+
{ id: message.id, tenantId: input.tenantId, actionTaken: null, deletedAt: null },
|
|
230
|
+
{
|
|
231
|
+
actionTaken: action.id,
|
|
232
|
+
actionTakenByUserId: input.userId,
|
|
233
|
+
actionTakenAt: /* @__PURE__ */ new Date()
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
if (claimedRows === 0) {
|
|
237
|
+
const current = await findOneWithDecryption(
|
|
238
|
+
em,
|
|
239
|
+
Message,
|
|
240
|
+
{ id: message.id, tenantId: input.tenantId, deletedAt: null },
|
|
241
|
+
void 0,
|
|
242
|
+
{ tenantId: input.tenantId, organizationId: input.organizationId }
|
|
243
|
+
);
|
|
244
|
+
throw Object.assign(new Error("Action already taken"), {
|
|
245
|
+
actionTaken: current?.actionTaken ?? message.actionTaken ?? action.id
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
claimedTerminal = true;
|
|
249
|
+
}
|
|
182
250
|
const commandBus = ctx.container.resolve("commandBus");
|
|
183
251
|
let result = {};
|
|
184
252
|
let operationLogEntry = null;
|
|
@@ -223,6 +291,7 @@ const executeActionCommand = {
|
|
|
223
291
|
operationLogEntry = commandResult.logEntry ?? null;
|
|
224
292
|
} catch (err) {
|
|
225
293
|
console.error("[messages] executeActionCommand sub-command failed", err);
|
|
294
|
+
await releaseTerminalClaim();
|
|
226
295
|
throw new Error("Action failed");
|
|
227
296
|
}
|
|
228
297
|
} else if (action.href) {
|
|
@@ -232,10 +301,12 @@ const executeActionCommand = {
|
|
|
232
301
|
userId: input.userId
|
|
233
302
|
});
|
|
234
303
|
if (!safeRedirect) {
|
|
304
|
+
await releaseTerminalClaim();
|
|
235
305
|
throw new Error("Action has an unsafe redirect target");
|
|
236
306
|
}
|
|
237
307
|
result = { redirect: safeRedirect };
|
|
238
308
|
} else {
|
|
309
|
+
await releaseTerminalClaim();
|
|
239
310
|
throw new Error("Action has no executable target");
|
|
240
311
|
}
|
|
241
312
|
if (shouldRecordActionTaken) {
|
|
@@ -246,7 +317,8 @@ const executeActionCommand = {
|
|
|
246
317
|
result,
|
|
247
318
|
tenantId: input.tenantId,
|
|
248
319
|
organizationId: input.organizationId,
|
|
249
|
-
userId: input.userId
|
|
320
|
+
userId: input.userId,
|
|
321
|
+
previousState
|
|
250
322
|
},
|
|
251
323
|
ctx: {
|
|
252
324
|
container: ctx.container,
|