@open-mercato/core 0.6.5-develop.4534.1.b459babe6d → 0.6.5-develop.4559.1.839e136509
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/AGENTS.md +5 -0
- package/dist/generated/entities/role/index.js +3 -1
- package/dist/generated/entities/role/index.js.map +2 -2
- package/dist/generated/entities/user/index.js +3 -1
- package/dist/generated/entities/user/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/helpers/integration/dbFixtures.js +2 -1
- package/dist/helpers/integration/dbFixtures.js.map +2 -2
- package/dist/helpers/integration/optimisticLockUi.js +104 -0
- package/dist/helpers/integration/optimisticLockUi.js.map +7 -0
- package/dist/helpers/integration/salesFixtures.js +17 -0
- package/dist/helpers/integration/salesFixtures.js.map +2 -2
- package/dist/modules/api_keys/backend/api-keys/page.js +9 -5
- package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +17 -9
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +32 -13
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/roles/route.js +3 -1
- package/dist/modules/auth/api/roles/route.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +71 -3
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +42 -19
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +3 -1
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +24 -4
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/roles/page.js +8 -4
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +27 -5
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +6 -2
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +3 -1
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/auth/data/entities.js +6 -0
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/services/sidebarPreferencesService.js +32 -4
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +28 -0
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/business_rules/api/sets/route.js +28 -0
- package/dist/modules/business_rules/api/sets/route.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.js +11 -4
- package/dist/modules/business_rules/backend/rules/[id]/page.js.map +3 -3
- package/dist/modules/business_rules/backend/rules/page.js +20 -11
- package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.js +11 -4
- package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/page.js +20 -11
- package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
- package/dist/modules/catalog/api/categories/route.js +2 -0
- package/dist/modules/catalog/api/categories/route.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +2 -1
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -0
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +94 -40
- 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 +37 -8
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/optionSchemaClient.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +32 -31
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/PriceKindSettings.js +12 -5
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductMediaManager.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +5 -3
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/variantForm.js +2 -1
- package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +23 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +5 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/set-primary-channel.js +2 -1
- package/dist/modules/communication_channels/commands/set-primary-channel.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +6 -3
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +18 -11
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +1 -0
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +10 -6
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +7 -5
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +26 -19
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +28 -5
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles.js +4 -2
- package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +28 -5
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users.js +2 -0
- package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +16 -8
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +8 -4
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +8 -4
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +29 -18
- 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 +18 -11
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customers/api/companies/route.js +13 -2
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +2 -0
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +11 -2
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +1 -0
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +34 -21
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/[id]/page.js +45 -27
- package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +22 -5
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js +30 -8
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -0
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +16 -6
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +62 -39
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/[id]/page.js +41 -26
- package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +50 -23
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/addresses.js +16 -14
- package/dist/modules/customers/commands/addresses.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +1 -1
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +41 -4
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/people.js +1 -1
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/customers/commands/personCompanyLinks.js +8 -5
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +13 -11
- package/dist/modules/customers/commands/pipeline-stages.js.map +3 -3
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +20 -13
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySortSettings.js +4 -0
- package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +38 -23
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityTimeline.js +1 -1
- package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
- package/dist/modules/customers/components/detail/AddressesSection.js +4 -0
- package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +28 -22
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +36 -24
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EmailCardActions.js +5 -0
- package/dist/modules/customers/components/detail/EmailCardActions.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +7 -0
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/ManageTagsDialog.js +34 -22
- package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +41 -29
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +14 -8
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +14 -6
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js +29 -13
- package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/useInteractions.js +77 -35
- package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +25 -17
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/guards.js +66 -0
- package/dist/modules/customers/data/guards.js.map +7 -0
- package/dist/modules/customers/di.js +37 -0
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +11 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +4 -4
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +9 -1
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +17 -8
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +43 -22
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-schedule-service.js +9 -0
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +8 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js +17 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +31 -10
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +28 -15
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/directory/api/organizations/route.js +3 -0
- package/dist/modules/directory/api/organizations/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -0
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +9 -5
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +7 -3
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +8 -4
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/directory/commands/organizations.js +7 -2
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/entities/api/records.js +66 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +1 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +8 -4
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/lib/helpers.js +17 -0
- package/dist/modules/entities/lib/helpers.js.map +2 -2
- package/dist/modules/feature_toggles/api/global/[id]/override/route.js +2 -1
- package/dist/modules/feature_toggles/api/global/[id]/override/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +15 -0
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js +15 -14
- package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js +20 -12
- package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +6 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/formConfig.js +2 -1
- package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
- package/dist/modules/feature_toggles/components/overrideFormConfig.js +5 -1
- package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
- package/dist/modules/feature_toggles/data/validators.js +7 -4
- package/dist/modules/feature_toggles/data/validators.js.map +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js +17 -2
- package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +13 -8
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +9 -4
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +18 -11
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/page.js +12 -8
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +13 -10
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +39 -30
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +7 -0
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +6 -14
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +3 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +4 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js +2 -0
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +36 -11
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/components/AvailabilitySchedule.js +9 -5
- package/dist/modules/planner/components/AvailabilitySchedule.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +19 -0
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -0
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js +4 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +14 -3
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js +8 -4
- package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
- package/dist/modules/resources/components/ResourceCrudForm.js +2 -0
- package/dist/modules/resources/components/ResourceCrudForm.js.map +2 -2
- package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -0
- package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +7 -2
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +3 -1
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +13 -4
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +16 -4
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +68 -22
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/create/page.js.map +2 -2
- package/dist/modules/sales/commands/documentAddresses.js +181 -2
- package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +29 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +12 -2
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +15 -0
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +4 -1
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js +19 -11
- package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
- package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
- package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js +12 -4
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/ShippingMethodsSettings.js +12 -4
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/StatusSettings.js +18 -11
- package/dist/modules/sales/components/StatusSettings.js.map +2 -2
- package/dist/modules/sales/components/TaxRatesSettings.js +12 -4
- package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
- package/dist/modules/sales/components/channels/ChannelOfferForm.js +47 -16
- package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +8 -4
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +44 -25
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +43 -23
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +22 -13
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +23 -10
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentDialog.js +29 -14
- package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +20 -10
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnDialog.js +26 -17
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnsSection.js +3 -1
- package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -5
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +21 -7
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +19 -10
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/optimisticLock.js +27 -0
- package/dist/modules/sales/components/documents/optimisticLock.js.map +7 -0
- package/dist/modules/sales/di.js +18 -0
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/staff/api/job-histories.js +11 -2
- package/dist/modules/staff/api/job-histories.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/route.js +11 -4
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +13 -8
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +2 -1
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +7 -4
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/page.js +4 -2
- package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +1 -0
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js +4 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +5 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js +12 -3
- package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.js +4 -1
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +12 -3
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/staff/commands/job-histories.js +40 -3
- package/dist/modules/staff/commands/job-histories.js.map +2 -2
- package/dist/modules/staff/components/LeaveRequestForm.js +1 -0
- package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
- package/dist/modules/staff/components/TeamForm.js +1 -0
- package/dist/modules/staff/components/TeamForm.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -0
- package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
- package/dist/modules/staff/components/TeamRoleForm.js +1 -0
- package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
- package/dist/modules/staff/components/detail/JobHistorySection.js +20 -7
- package/dist/modules/staff/components/detail/JobHistorySection.js.map +2 -2
- package/dist/modules/staff/data/validators.js +7 -1
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/lib/leaveRequestHelpers.js +2 -1
- package/dist/modules/staff/lib/leaveRequestHelpers.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +12 -8
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js +106 -0
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +11 -3
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/page.js +19 -8
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +29 -16
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/formConfig.js +4 -1
- package/dist/modules/workflows/components/formConfig.js.map +2 -2
- package/dist/modules/workflows/di.js +12 -0
- package/dist/modules/workflows/di.js.map +2 -2
- package/generated/entities/role/index.ts +1 -0
- package/generated/entities/user/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/jest.setup.ts +17 -0
- package/package.json +8 -7
- package/src/helpers/integration/communicationChannelsFixtures.ts +6 -0
- package/src/helpers/integration/dbFixtures.ts +1 -1
- package/src/helpers/integration/optimisticLockUi.ts +172 -0
- package/src/helpers/integration/salesFixtures.ts +29 -0
- package/src/modules/api_keys/backend/api-keys/page.tsx +10 -5
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +19 -9
- package/src/modules/auth/api/roles/acl/route.ts +37 -11
- package/src/modules/auth/api/roles/route.ts +2 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +73 -0
- package/src/modules/auth/api/users/acl/route.ts +46 -18
- package/src/modules/auth/api/users/route.ts +2 -0
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +29 -4
- package/src/modules/auth/backend/roles/page.tsx +9 -4
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +37 -4
- package/src/modules/auth/backend/users/page.tsx +7 -2
- package/src/modules/auth/components/AclEditor.tsx +10 -1
- package/src/modules/auth/data/entities.ts +7 -1
- package/src/modules/auth/services/sidebarPreferencesService.ts +38 -4
- package/src/modules/business_rules/api/rules/route.ts +30 -0
- package/src/modules/business_rules/api/sets/route.ts +30 -0
- package/src/modules/business_rules/backend/rules/[id]/page.tsx +16 -4
- package/src/modules/business_rules/backend/rules/page.tsx +20 -11
- package/src/modules/business_rules/backend/sets/[id]/page.tsx +16 -4
- package/src/modules/business_rules/backend/sets/page.tsx +20 -11
- package/src/modules/catalog/api/categories/route.ts +3 -0
- package/src/modules/catalog/api/products/route.ts +4 -0
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +5 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +112 -35
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +56 -7
- package/src/modules/catalog/backend/catalog/products/optionSchemaClient.ts +2 -0
- package/src/modules/catalog/commands/variants.ts +32 -32
- package/src/modules/catalog/components/PriceKindSettings.tsx +20 -7
- package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -0
- package/src/modules/catalog/components/products/ProductMediaManager.tsx +2 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -4
- package/src/modules/catalog/components/products/productForm.ts +3 -0
- package/src/modules/catalog/components/products/variantForm.ts +9 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +28 -1
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +5 -0
- package/src/modules/communication_channels/commands/set-primary-channel.ts +10 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +13 -6
- package/src/modules/currencies/backend/currencies/page.tsx +18 -11
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +3 -0
- package/src/modules/currencies/backend/exchange-rates/page.tsx +10 -6
- package/src/modules/currencies/commands/currencies.ts +10 -5
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +31 -21
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +35 -5
- package/src/modules/customer_accounts/api/admin/roles.ts +2 -0
- package/src/modules/customer_accounts/api/admin/users/[id].ts +38 -5
- package/src/modules/customer_accounts/api/admin/users.ts +2 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -20
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +9 -4
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +11 -4
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +28 -17
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +19 -11
- package/src/modules/customers/AGENTS.md +2 -2
- package/src/modules/customers/api/companies/route.ts +14 -1
- package/src/modules/customers/api/deals/route.ts +3 -0
- package/src/modules/customers/api/people/route.ts +12 -1
- package/src/modules/customers/api/todos/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/deals/page.tsx +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +36 -21
- package/src/modules/customers/backend/customers/companies/[id]/page.tsx +52 -27
- package/src/modules/customers/backend/customers/companies/page.tsx +2 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -5
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.ts +39 -7
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -0
- package/src/modules/customers/backend/customers/deals/page.tsx +18 -6
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +64 -39
- package/src/modules/customers/backend/customers/people/[id]/page.tsx +46 -26
- package/src/modules/customers/backend/customers/people/page.tsx +2 -0
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +84 -24
- package/src/modules/customers/commands/addresses.ts +16 -14
- package/src/modules/customers/commands/companies.ts +3 -1
- package/src/modules/customers/commands/interactions.ts +50 -4
- package/src/modules/customers/commands/people.ts +2 -1
- package/src/modules/customers/commands/personCompanyLinks.ts +8 -5
- package/src/modules/customers/commands/pipeline-stages.ts +16 -16
- package/src/modules/customers/components/AddressFormatSettings.tsx +1 -0
- package/src/modules/customers/components/DictionarySettings.tsx +18 -13
- package/src/modules/customers/components/DictionarySortSettings.tsx +4 -0
- package/src/modules/customers/components/PipelineSettings.tsx +42 -21
- package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -3
- package/src/modules/customers/components/detail/AddressesSection.tsx +4 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +2 -0
- package/src/modules/customers/components/detail/DealsSection.tsx +4 -0
- package/src/modules/customers/components/detail/EmailCardActions.tsx +5 -0
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +7 -0
- package/src/modules/customers/components/detail/ManageTagsDialog.tsx +4 -0
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +4 -0
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +2 -0
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +23 -7
- package/src/modules/customers/components/detail/hooks/useInteractionMutations.ts +25 -15
- package/src/modules/customers/components/detail/hooks/useInteractions.ts +76 -35
- package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +30 -17
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +2 -0
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/formConfig.tsx +2 -0
- package/src/modules/customers/data/guards.ts +67 -0
- package/src/modules/customers/di.ts +66 -0
- package/src/modules/customers/i18n/de.json +2 -0
- package/src/modules/customers/i18n/en.json +2 -0
- package/src/modules/customers/i18n/es.json +2 -0
- package/src/modules/customers/i18n/pl.json +2 -0
- package/src/modules/customers/lib/todoCompatibility.ts +14 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +2 -0
- package/src/modules/data_sync/api/options.ts +7 -4
- package/src/modules/data_sync/api/schedules/route.ts +9 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +18 -5
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +46 -19
- package/src/modules/data_sync/lib/sync-schedule-service.ts +11 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +8 -1
- package/src/modules/dictionaries/api/[dictionaryId]/route.ts +23 -0
- package/src/modules/dictionaries/components/DictionariesManager.tsx +32 -9
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +30 -14
- package/src/modules/dictionaries/i18n/de.json +1 -0
- package/src/modules/dictionaries/i18n/en.json +1 -0
- package/src/modules/dictionaries/i18n/es.json +1 -0
- package/src/modules/dictionaries/i18n/pl.json +1 -0
- package/src/modules/directory/api/organizations/route.ts +3 -0
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +8 -0
- package/src/modules/directory/backend/directory/organizations/page.tsx +10 -5
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +16 -5
- package/src/modules/directory/backend/directory/tenants/page.tsx +8 -4
- package/src/modules/directory/commands/organizations.ts +7 -4
- package/src/modules/entities/api/records.ts +99 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +7 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +8 -4
- package/src/modules/entities/lib/helpers.ts +17 -0
- package/src/modules/feature_toggles/api/global/[id]/override/route.ts +1 -0
- package/src/modules/feature_toggles/api/overrides/route.ts +19 -0
- package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +19 -13
- package/src/modules/feature_toggles/components/FeatureToggleOverrideCard.tsx +22 -12
- package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +7 -2
- package/src/modules/feature_toggles/components/formConfig.tsx +2 -1
- package/src/modules/feature_toggles/components/overrideFormConfig.tsx +10 -1
- package/src/modules/feature_toggles/data/validators.ts +11 -3
- package/src/modules/inbox_ops/api/settings/route.ts +18 -0
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +15 -10
- package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +9 -4
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +20 -11
- package/src/modules/integrations/backend/integrations/page.tsx +13 -8
- package/src/modules/messages/commands/messages.ts +27 -15
- package/src/modules/perspectives/api/[tableId]/route.ts +11 -2
- package/src/modules/perspectives/services/perspectiveService.ts +13 -1
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +16 -14
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +6 -3
- package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +3 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +58 -15
- package/src/modules/planner/components/AvailabilitySchedule.tsx +22 -7
- package/src/modules/query_index/lib/engine.ts +34 -0
- package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +7 -1
- package/src/modules/resources/backend/resources/resource-types/page.tsx +6 -3
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +23 -3
- package/src/modules/resources/backend/resources/resources/page.tsx +15 -4
- package/src/modules/resources/components/ResourceCrudForm.tsx +3 -0
- package/src/modules/resources/components/ResourceTypeCrudForm.tsx +2 -0
- package/src/modules/sales/api/documents/factory.ts +13 -1
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +6 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +10 -4
- package/src/modules/sales/backend/sales/channels/page.tsx +19 -4
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +73 -20
- package/src/modules/sales/backend/sales/documents/create/page.tsx +2 -0
- package/src/modules/sales/commands/documentAddresses.ts +226 -4
- package/src/modules/sales/commands/documents.ts +28 -0
- package/src/modules/sales/commands/returns.ts +12 -3
- package/src/modules/sales/commands/shared.ts +36 -0
- package/src/modules/sales/commands/shipments.ts +17 -1
- package/src/modules/sales/components/AdjustmentKindSettings.tsx +20 -11
- package/src/modules/sales/components/DocumentNumberSettings.tsx +1 -0
- package/src/modules/sales/components/OrderEditingSettings.tsx +1 -0
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +12 -4
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +12 -4
- package/src/modules/sales/components/StatusSettings.tsx +20 -11
- package/src/modules/sales/components/TaxRatesSettings.tsx +12 -5
- package/src/modules/sales/components/channels/ChannelOfferForm.tsx +67 -14
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +7 -4
- package/src/modules/sales/components/documents/AddressesSection.tsx +35 -25
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +50 -25
- package/src/modules/sales/components/documents/ItemsSection.tsx +24 -13
- package/src/modules/sales/components/documents/LineItemDialog.tsx +26 -9
- package/src/modules/sales/components/documents/PaymentDialog.tsx +33 -14
- package/src/modules/sales/components/documents/PaymentsSection.tsx +22 -10
- package/src/modules/sales/components/documents/ReturnDialog.tsx +28 -17
- package/src/modules/sales/components/documents/ReturnsSection.tsx +4 -1
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +11 -4
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +23 -8
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +20 -10
- package/src/modules/sales/components/documents/optimisticLock.ts +34 -0
- package/src/modules/sales/components/documents/shipmentTypes.ts +1 -0
- package/src/modules/sales/di.ts +35 -0
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/pl.json +3 -0
- package/src/modules/staff/api/job-histories.ts +12 -2
- package/src/modules/staff/api/timesheets/time-entries/route.ts +16 -4
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +12 -7
- package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +2 -0
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +16 -5
- package/src/modules/staff/backend/staff/team-members/page.tsx +6 -2
- package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +8 -0
- package/src/modules/staff/backend/staff/team-roles/page.tsx +6 -2
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +13 -3
- package/src/modules/staff/backend/staff/teams/page.tsx +9 -3
- package/src/modules/staff/backend/staff/timesheets/page.tsx +10 -1
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -0
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +9 -3
- package/src/modules/staff/commands/job-histories.ts +42 -3
- package/src/modules/staff/components/LeaveRequestForm.tsx +2 -0
- package/src/modules/staff/components/TeamForm.tsx +2 -0
- package/src/modules/staff/components/TeamMemberForm.tsx +2 -0
- package/src/modules/staff/components/TeamRoleForm.tsx +2 -0
- package/src/modules/staff/components/detail/JobHistorySection.tsx +28 -6
- package/src/modules/staff/data/validators.ts +6 -0
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/lib/leaveRequestHelpers.ts +4 -0
- package/src/modules/translations/components/TranslationManager.tsx +13 -8
- package/src/modules/workflows/api/definitions/[id]/route.ts +112 -0
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +20 -4
- package/src/modules/workflows/backend/definitions/page.tsx +20 -9
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +29 -16
- package/src/modules/workflows/components/formConfig.tsx +5 -0
- package/src/modules/workflows/di.ts +20 -0
- package/src/modules/workflows/i18n/de.json +1 -0
- package/src/modules/workflows/i18n/en.json +1 -0
- package/src/modules/workflows/i18n/es.json +1 -0
- package/src/modules/workflows/i18n/pl.json +1 -0
|
@@ -8,13 +8,17 @@ import {
|
|
|
8
8
|
sidebarPreferencesScopeSchema
|
|
9
9
|
} from "../../../data/validators.js";
|
|
10
10
|
import {
|
|
11
|
+
loadRoleSidebarPreferenceUpdatedAt,
|
|
11
12
|
loadRoleSidebarPreferences,
|
|
12
13
|
loadSidebarPreference,
|
|
14
|
+
loadSidebarPreferenceUpdatedAt,
|
|
13
15
|
saveRoleSidebarPreference,
|
|
14
16
|
saveSidebarPreference
|
|
15
17
|
} from "../../../services/sidebarPreferencesService.js";
|
|
16
18
|
import { SIDEBAR_PREFERENCES_VERSION } from "@open-mercato/shared/modules/navigation/sidebarPreferences";
|
|
17
19
|
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
20
|
+
import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
21
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
18
22
|
import { Role, RoleSidebarPreference } from "../../../data/entities.js";
|
|
19
23
|
import { z } from "zod";
|
|
20
24
|
const metadata = {
|
|
@@ -40,7 +44,8 @@ const sidebarPreferencesResponseSchema = z.object({
|
|
|
40
44
|
settings: sidebarSettingsSchema,
|
|
41
45
|
canApplyToRoles: z.boolean(),
|
|
42
46
|
roles: z.array(sidebarRoleEntrySchema),
|
|
43
|
-
scope: sidebarPreferencesScopeSchema
|
|
47
|
+
scope: sidebarPreferencesScopeSchema,
|
|
48
|
+
updatedAt: z.string().datetime().nullable()
|
|
44
49
|
});
|
|
45
50
|
const sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({
|
|
46
51
|
appliedRoles: z.array(z.string().uuid()),
|
|
@@ -127,6 +132,11 @@ async function GET(req) {
|
|
|
127
132
|
});
|
|
128
133
|
const pref = rolePrefs.get(role.id) ?? null;
|
|
129
134
|
const rolesPayload2 = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale });
|
|
135
|
+
const roleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {
|
|
136
|
+
roleId: role.id,
|
|
137
|
+
tenantId: auth.tenantId ?? null,
|
|
138
|
+
locale
|
|
139
|
+
});
|
|
130
140
|
return NextResponse.json({
|
|
131
141
|
locale,
|
|
132
142
|
settings: pref ? {
|
|
@@ -139,7 +149,8 @@ async function GET(req) {
|
|
|
139
149
|
} : emptySettings(),
|
|
140
150
|
canApplyToRoles,
|
|
141
151
|
roles: rolesPayload2,
|
|
142
|
-
scope: { type: "role", roleId: role.id }
|
|
152
|
+
scope: { type: "role", roleId: role.id },
|
|
153
|
+
updatedAt: roleVersion?.updatedAt ? roleVersion.updatedAt.toISOString() : null
|
|
143
154
|
});
|
|
144
155
|
}
|
|
145
156
|
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub;
|
|
@@ -150,6 +161,12 @@ async function GET(req) {
|
|
|
150
161
|
locale
|
|
151
162
|
}) : null;
|
|
152
163
|
const rolesPayload = canApplyToRoles ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale }) : [];
|
|
164
|
+
const userVersion = effectiveUserId ? await loadSidebarPreferenceUpdatedAt(em, {
|
|
165
|
+
userId: effectiveUserId,
|
|
166
|
+
tenantId: auth.tenantId ?? null,
|
|
167
|
+
organizationId: auth.orgId ?? null,
|
|
168
|
+
locale
|
|
169
|
+
}) : null;
|
|
153
170
|
return NextResponse.json({
|
|
154
171
|
locale,
|
|
155
172
|
settings: {
|
|
@@ -162,7 +179,8 @@ async function GET(req) {
|
|
|
162
179
|
},
|
|
163
180
|
canApplyToRoles,
|
|
164
181
|
roles: rolesPayload,
|
|
165
|
-
scope: { type: "user" }
|
|
182
|
+
scope: { type: "user" },
|
|
183
|
+
updatedAt: userVersion?.updatedAt ? userVersion.updatedAt.toISOString() : null
|
|
166
184
|
});
|
|
167
185
|
}
|
|
168
186
|
async function PUT(req) {
|
|
@@ -257,11 +275,34 @@ async function PUT(req) {
|
|
|
257
275
|
if (!role) {
|
|
258
276
|
return NextResponse.json({ error: "Role not found" }, { status: 404 });
|
|
259
277
|
}
|
|
278
|
+
const existingRolePref = await loadRoleSidebarPreferenceUpdatedAt(em, {
|
|
279
|
+
roleId: role.id,
|
|
280
|
+
tenantId: auth.tenantId ?? null,
|
|
281
|
+
locale
|
|
282
|
+
});
|
|
283
|
+
if (existingRolePref) {
|
|
284
|
+
try {
|
|
285
|
+
enforceCommandOptimisticLock({
|
|
286
|
+
resourceKind: "auth.role_sidebar_preference",
|
|
287
|
+
resourceId: existingRolePref.id,
|
|
288
|
+
current: existingRolePref.updatedAt ?? null,
|
|
289
|
+
request: req
|
|
290
|
+
});
|
|
291
|
+
} catch (err) {
|
|
292
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
260
296
|
const saved = await saveRoleSidebarPreference(em, {
|
|
261
297
|
roleId: role.id,
|
|
262
298
|
tenantId: auth.tenantId ?? null,
|
|
263
299
|
locale
|
|
264
300
|
}, payload);
|
|
301
|
+
const savedRoleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {
|
|
302
|
+
roleId: role.id,
|
|
303
|
+
tenantId: auth.tenantId ?? null,
|
|
304
|
+
locale
|
|
305
|
+
});
|
|
265
306
|
if (cache?.deleteByTags) {
|
|
266
307
|
try {
|
|
267
308
|
await cache.deleteByTags([`nav:sidebar:role:${role.id}`]);
|
|
@@ -282,6 +323,7 @@ async function PUT(req) {
|
|
|
282
323
|
canApplyToRoles,
|
|
283
324
|
roles: rolesPayload2,
|
|
284
325
|
scope: { type: "role", roleId: role.id },
|
|
326
|
+
updatedAt: savedRoleVersion?.updatedAt ? savedRoleVersion.updatedAt.toISOString() : null,
|
|
285
327
|
appliedRoles: [],
|
|
286
328
|
clearedRoles: []
|
|
287
329
|
});
|
|
@@ -293,6 +335,25 @@ async function PUT(req) {
|
|
|
293
335
|
if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {
|
|
294
336
|
return NextResponse.json({ error: "Forbidden", requiredFeatures: [FEATURE_MANAGE] }, { status: 403 });
|
|
295
337
|
}
|
|
338
|
+
const existingUserPref = await loadSidebarPreferenceUpdatedAt(em, {
|
|
339
|
+
userId: effectiveUserId,
|
|
340
|
+
tenantId: auth.tenantId ?? null,
|
|
341
|
+
organizationId: auth.orgId ?? null,
|
|
342
|
+
locale
|
|
343
|
+
});
|
|
344
|
+
if (existingUserPref) {
|
|
345
|
+
try {
|
|
346
|
+
enforceCommandOptimisticLock({
|
|
347
|
+
resourceKind: "auth.sidebar_preference",
|
|
348
|
+
resourceId: existingUserPref.id,
|
|
349
|
+
current: existingUserPref.updatedAt ?? null,
|
|
350
|
+
request: req
|
|
351
|
+
});
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
296
357
|
const settings = await saveSidebarPreference(em, {
|
|
297
358
|
userId: effectiveUserId,
|
|
298
359
|
tenantId: auth.tenantId ?? null,
|
|
@@ -367,12 +428,19 @@ async function PUT(req) {
|
|
|
367
428
|
hasPreference: rolePrefs.has(role.id)
|
|
368
429
|
}));
|
|
369
430
|
}
|
|
431
|
+
const savedUserVersion = await loadSidebarPreferenceUpdatedAt(em, {
|
|
432
|
+
userId: effectiveUserId,
|
|
433
|
+
tenantId: auth.tenantId ?? null,
|
|
434
|
+
organizationId: auth.orgId ?? null,
|
|
435
|
+
locale
|
|
436
|
+
});
|
|
370
437
|
return NextResponse.json({
|
|
371
438
|
locale,
|
|
372
439
|
settings,
|
|
373
440
|
canApplyToRoles,
|
|
374
441
|
roles: rolesPayload,
|
|
375
442
|
scope: { type: "user" },
|
|
443
|
+
updatedAt: savedUserVersion?.updatedAt ? savedUserVersion.updatedAt.toISOString() : null,
|
|
376
444
|
appliedRoles: updatedRoleIds,
|
|
377
445
|
clearedRoles: filteredClearRoleIds
|
|
378
446
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/auth/api/sidebar/preferences/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n sidebarPreferencesInputSchema,\n sidebarPreferencesScopeSchema,\n} from '../../../data/validators'\nimport {\n loadRoleSidebarPreferences,\n loadSidebarPreference,\n saveRoleSidebarPreference,\n saveSidebarPreference,\n} from '../../../services/sidebarPreferencesService'\nimport { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { Role, RoleSidebarPreference } from '../../../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\n\nexport const metadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },\n}\n\nconst sidebarSettingsSchema = z.object({\n version: z.number().int().positive(),\n groupOrder: z.array(z.string()),\n groupLabels: z.record(z.string(), z.string()),\n itemLabels: z.record(z.string(), z.string()),\n hiddenItems: z.array(z.string()),\n itemOrder: z.record(z.string(), z.array(z.string())),\n})\n\nconst sidebarRoleEntrySchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n hasPreference: z.boolean(),\n})\n\nconst sidebarPreferencesResponseSchema = z.object({\n locale: z.string(),\n settings: sidebarSettingsSchema,\n canApplyToRoles: z.boolean(),\n roles: z.array(sidebarRoleEntrySchema),\n scope: sidebarPreferencesScopeSchema,\n})\n\nconst sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({\n appliedRoles: z.array(z.string().uuid()),\n clearedRoles: z.array(z.string().uuid()),\n})\n\nconst sidebarPreferencesDeleteResponseSchema = z.object({\n ok: z.literal(true),\n scope: sidebarPreferencesScopeSchema,\n})\n\nconst sidebarErrorSchema = z.object({\n error: z.string(),\n})\n\nconst FEATURE_MANAGE = 'auth.sidebar.manage'\n\ntype EmptySettings = {\n version: number\n groupOrder: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItems: string[]\n itemOrder: Record<string, string[]>\n}\n\nfunction emptySettings(): EmptySettings {\n return {\n version: SIDEBAR_PREFERENCES_VERSION,\n groupOrder: [],\n groupLabels: {},\n itemLabels: {},\n hiddenItems: [],\n itemOrder: {},\n }\n}\n\nasync function loadRolesPayload(\n em: EntityManager,\n options: { tenantId: string | null; locale: string },\n): Promise<Array<{ id: string; name: string; hasPreference: boolean }>> {\n const roleScope: FilterQuery<Role> = options.tenantId\n ? { $or: [{ tenantId: options.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roles = await findWithDecryption(\n em,\n Role,\n roleScope,\n { orderBy: { name: 'asc' } },\n { tenantId: options.tenantId, organizationId: null },\n )\n if (roles.length === 0) return []\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: roles.map((r: Role) => r.id),\n tenantId: options.tenantId,\n locale: options.locale,\n })\n return roles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n}\n\nasync function findRoleInScope(\n em: EntityManager,\n options: { roleId: string; tenantId: string | null },\n): Promise<Role | null> {\n const role = await findOneWithDecryption(\n em,\n Role,\n { id: options.roleId },\n undefined,\n { tenantId: options.tenantId, organizationId: null },\n )\n if (!role) return null\n // Cross-tenant guard: a role belongs to either the auth tenant or the global (null tenant) pool.\n // Reject the lookup otherwise so a multi-tenant deployment can't leak across tenants.\n if (role.tenantId && options.tenantId && role.tenantId !== options.tenantId) return null\n if (role.tenantId && !options.tenantId) return null\n return role\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleIdParam = url.searchParams.get('roleId')\n\n const { locale } = await resolveTranslations()\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const rbac = resolve('rbacService') as any\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n // Role-scoped read: requires `auth.sidebar.manage`.\n if (roleIdParam) {\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: [role.id],\n tenantId: auth.tenantId ?? null,\n locale,\n })\n const pref = rolePrefs.get(role.id) ?? null\n const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n return NextResponse.json({\n locale,\n settings: pref\n ? {\n version: pref.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: pref.groupOrder ?? [],\n groupLabels: pref.groupLabels ?? {},\n itemLabels: pref.itemLabels ?? {},\n hiddenItems: pref.hiddenItems ?? [],\n itemOrder: pref.itemOrder ?? {},\n }\n : emptySettings(),\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'role', roleId: role.id },\n })\n }\n\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n const settings = effectiveUserId\n ? await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n const rolesPayload = canApplyToRoles\n ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n : []\n\n return NextResponse.json({\n locale,\n settings: {\n version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: settings?.groupOrder ?? [],\n groupLabels: settings?.groupLabels ?? {},\n itemLabels: settings?.itemLabels ?? {},\n hiddenItems: settings?.hiddenItems ?? [],\n itemOrder: settings?.itemOrder ?? {},\n },\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'user' },\n })\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n if (!effectiveUserId) {\n return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = sidebarPreferencesInputSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const sanitizeRecord = (record?: Record<string, string>) => {\n if (!record) return {}\n const result: Record<string, string> = {}\n for (const [key, value] of Object.entries(record)) {\n const trimmedKey = key.trim()\n const trimmedValue = value.trim()\n if (!trimmedKey || !trimmedValue) continue\n result[trimmedKey] = trimmedValue\n }\n return result\n }\n\n const groupOrderSource = parsed.data.groupOrder ?? []\n const seen = new Set<string>()\n const groupOrder: string[] = []\n for (const id of groupOrderSource) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n groupOrder.push(trimmed)\n }\n\n const payload = {\n version: parsed.data.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder,\n groupLabels: sanitizeRecord(parsed.data.groupLabels),\n itemLabels: sanitizeRecord(parsed.data.itemLabels),\n hiddenItems: (() => {\n const source = parsed.data.hiddenItems ?? []\n const seenHidden = new Set<string>()\n const values: string[] = []\n for (const href of source) {\n const trimmed = href.trim()\n if (!trimmed || seenHidden.has(trimmed)) continue\n seenHidden.add(trimmed)\n values.push(trimmed)\n }\n return values\n })(),\n itemOrder: (() => {\n const source = parsed.data.itemOrder ?? {}\n const out: Record<string, string[]> = {}\n for (const [groupKey, list] of Object.entries(source)) {\n const trimmedGroup = groupKey.trim()\n if (!trimmedGroup) continue\n const seenItem = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n const trimmedItem = itemKey.trim()\n if (!trimmedItem || seenItem.has(trimmedItem)) continue\n seenItem.add(trimmedItem)\n values.push(trimmedItem)\n }\n if (values.length > 0) out[trimmedGroup] = values\n }\n return out\n })(),\n }\n\n const { locale } = await resolveTranslations()\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const scope = parsed.data.scope ?? { type: 'user' as const }\n\n // Role-scoped write: requires `auth.sidebar.manage` and a role visible to this tenant.\n // applyToRoles/clearRoleIds are forbidden in role scope (validator already rejects them).\n if (scope.type === 'role') {\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n const role = await findRoleInScope(em, { roleId: scope.roleId, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n const saved = await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags([`nav:sidebar:role:${role.id}`])\n } catch {}\n }\n const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n return NextResponse.json({\n locale,\n settings: {\n version: saved?.version ?? payload.version,\n groupOrder: saved?.groupOrder ?? payload.groupOrder,\n groupLabels: saved?.groupLabels ?? payload.groupLabels,\n itemLabels: saved?.itemLabels ?? payload.itemLabels,\n hiddenItems: saved?.hiddenItems ?? payload.hiddenItems,\n itemOrder: saved?.itemOrder ?? payload.itemOrder,\n },\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'role', roleId: role.id },\n appliedRoles: [],\n clearedRoles: [],\n })\n }\n\n const applyToRolesSource = parsed.data.applyToRoles ?? []\n const applyToRoles = Array.from(new Set(applyToRolesSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n const clearRoleIdsSource = parsed.data.clearRoleIds ?? []\n const clearRoleIds = Array.from(new Set(clearRoleIdsSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n\n if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n\n const settings = await saveSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n }, payload)\n\n const roleScope: FilterQuery<Role> = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await findWithDecryption(\n em,\n Role,\n roleScope,\n { orderBy: { name: 'asc' } },\n { tenantId: auth.tenantId ?? null, organizationId: null },\n )\n : []\n const roleMap = new Map<string, Role>(availableRoles.map((role: Role) => [String(role.id), role]))\n\n if (applyToRoles.length > 0) {\n const missing = applyToRoles.filter((id) => !roleMap.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n }\n\n const updatedRoleIds: string[] = []\n const filteredClearRoleIds: string[] = []\n await withAtomicFlush(em, [\n async () => {\n for (const roleId of applyToRoles) {\n const role = roleMap.get(roleId)!\n await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n updatedRoleIds.push(role.id)\n }\n\n const clearTargets = clearRoleIds.filter((id) => !updatedRoleIds.includes(id) && !applyToRoles.includes(id))\n filteredClearRoleIds.push(...clearTargets)\n\n if (filteredClearRoleIds.length > 0) {\n // Cross-locale: role preferences are unique per (role, tenantId); keep the delete\n // filter aligned with save/load helpers so a clear from one locale does not leave\n // a row created under another locale orphaned.\n await em.nativeDelete(RoleSidebarPreference, {\n role: { $in: filteredClearRoleIds },\n tenantId: auth.tenantId ?? null,\n })\n }\n },\n ], { transaction: true })\n\n if (filteredClearRoleIds.length > 0 && cache?.deleteByTags) {\n try {\n await cache.deleteByTags(filteredClearRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`))\n } catch {}\n }\n\n if (cache?.deleteByTags) {\n const tags = [\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${auth.tenantId ?? 'null'}:${auth.orgId ?? 'null'}:${locale}`,\n ...updatedRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`),\n ]\n try {\n await cache.deleteByTags(tags)\n } catch {}\n }\n\n let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []\n if (canApplyToRoles) {\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: availableRoles.map((role: Role) => role.id),\n tenantId: auth.tenantId ?? null,\n locale,\n })\n rolesPayload = availableRoles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n }\n\n return NextResponse.json({\n locale,\n settings,\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'user' },\n appliedRoles: updatedRoleIds,\n clearedRoles: filteredClearRoleIds,\n })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleIdParam = url.searchParams.get('roleId')\n if (!roleIdParam) {\n return NextResponse.json({ error: 'roleId query parameter is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n\n const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n // Cross-locale: keep the delete filter aligned with save/load helpers (no locale).\n await em.nativeDelete(RoleSidebarPreference, {\n role: role.id,\n tenantId: auth.tenantId ?? null,\n })\n\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags([`nav:sidebar:role:${role.id}`])\n } catch {}\n }\n\n return NextResponse.json({ ok: true, scope: { type: 'role', roleId: role.id } })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Sidebar preferences',\n methods: {\n GET: {\n summary: 'Get sidebar preferences',\n description: 'Returns sidebar customization for the current user (default) or the specified role (`?roleId=\u2026`, requires `auth.sidebar.manage`).',\n responses: [\n { status: 200, description: 'Current sidebar configuration', schema: sidebarPreferencesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-scope read', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar preferences',\n description: 'Updates sidebar configuration. With `scope.type === \"user\"` (default) writes the calling user\\'s personal preferences and may optionally apply the same settings to selected roles via `applyToRoles[]`. With `scope.type === \"role\"` writes the named role variant directly (requires `auth.sidebar.manage`); `applyToRoles[]` and `clearRoleIds[]` are rejected in this mode.',\n requestBody: {\n contentType: 'application/json',\n schema: sidebarPreferencesInputSchema,\n },\n responses: [\n { status: 200, description: 'Preferences saved', schema: sidebarPreferencesUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-wide updates', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete a role sidebar variant',\n description: 'Removes the role variant for the current tenant + locale. Idempotent. Requires `auth.sidebar.manage`.',\n responses: [\n { status: 200, description: 'Variant deleted (or never existed)', schema: sidebarPreferencesDeleteResponseSchema },\n { status: 400, description: 'Missing roleId query parameter', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n sidebarPreferencesInputSchema,\n sidebarPreferencesScopeSchema,\n} from '../../../data/validators'\nimport {\n loadRoleSidebarPreferenceUpdatedAt,\n loadRoleSidebarPreferences,\n loadSidebarPreference,\n loadSidebarPreferenceUpdatedAt,\n saveRoleSidebarPreference,\n saveSidebarPreference,\n} from '../../../services/sidebarPreferencesService'\nimport { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { Role, RoleSidebarPreference } from '../../../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\n\nexport const metadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },\n}\n\nconst sidebarSettingsSchema = z.object({\n version: z.number().int().positive(),\n groupOrder: z.array(z.string()),\n groupLabels: z.record(z.string(), z.string()),\n itemLabels: z.record(z.string(), z.string()),\n hiddenItems: z.array(z.string()),\n itemOrder: z.record(z.string(), z.array(z.string())),\n})\n\nconst sidebarRoleEntrySchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n hasPreference: z.boolean(),\n})\n\nconst sidebarPreferencesResponseSchema = z.object({\n locale: z.string(),\n settings: sidebarSettingsSchema,\n canApplyToRoles: z.boolean(),\n roles: z.array(sidebarRoleEntrySchema),\n scope: sidebarPreferencesScopeSchema,\n updatedAt: z.string().datetime().nullable(),\n})\n\nconst sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({\n appliedRoles: z.array(z.string().uuid()),\n clearedRoles: z.array(z.string().uuid()),\n})\n\nconst sidebarPreferencesDeleteResponseSchema = z.object({\n ok: z.literal(true),\n scope: sidebarPreferencesScopeSchema,\n})\n\nconst sidebarErrorSchema = z.object({\n error: z.string(),\n})\n\nconst FEATURE_MANAGE = 'auth.sidebar.manage'\n\ntype EmptySettings = {\n version: number\n groupOrder: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItems: string[]\n itemOrder: Record<string, string[]>\n}\n\nfunction emptySettings(): EmptySettings {\n return {\n version: SIDEBAR_PREFERENCES_VERSION,\n groupOrder: [],\n groupLabels: {},\n itemLabels: {},\n hiddenItems: [],\n itemOrder: {},\n }\n}\n\nasync function loadRolesPayload(\n em: EntityManager,\n options: { tenantId: string | null; locale: string },\n): Promise<Array<{ id: string; name: string; hasPreference: boolean }>> {\n const roleScope: FilterQuery<Role> = options.tenantId\n ? { $or: [{ tenantId: options.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roles = await findWithDecryption(\n em,\n Role,\n roleScope,\n { orderBy: { name: 'asc' } },\n { tenantId: options.tenantId, organizationId: null },\n )\n if (roles.length === 0) return []\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: roles.map((r: Role) => r.id),\n tenantId: options.tenantId,\n locale: options.locale,\n })\n return roles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n}\n\nasync function findRoleInScope(\n em: EntityManager,\n options: { roleId: string; tenantId: string | null },\n): Promise<Role | null> {\n const role = await findOneWithDecryption(\n em,\n Role,\n { id: options.roleId },\n undefined,\n { tenantId: options.tenantId, organizationId: null },\n )\n if (!role) return null\n // Cross-tenant guard: a role belongs to either the auth tenant or the global (null tenant) pool.\n // Reject the lookup otherwise so a multi-tenant deployment can't leak across tenants.\n if (role.tenantId && options.tenantId && role.tenantId !== options.tenantId) return null\n if (role.tenantId && !options.tenantId) return null\n return role\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleIdParam = url.searchParams.get('roleId')\n\n const { locale } = await resolveTranslations()\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const rbac = resolve('rbacService') as any\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n // Role-scoped read: requires `auth.sidebar.manage`.\n if (roleIdParam) {\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: [role.id],\n tenantId: auth.tenantId ?? null,\n locale,\n })\n const pref = rolePrefs.get(role.id) ?? null\n const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n const roleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n })\n return NextResponse.json({\n locale,\n settings: pref\n ? {\n version: pref.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: pref.groupOrder ?? [],\n groupLabels: pref.groupLabels ?? {},\n itemLabels: pref.itemLabels ?? {},\n hiddenItems: pref.hiddenItems ?? [],\n itemOrder: pref.itemOrder ?? {},\n }\n : emptySettings(),\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'role', roleId: role.id },\n updatedAt: roleVersion?.updatedAt ? roleVersion.updatedAt.toISOString() : null,\n })\n }\n\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n const settings = effectiveUserId\n ? await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n const rolesPayload = canApplyToRoles\n ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n : []\n\n const userVersion = effectiveUserId\n ? await loadSidebarPreferenceUpdatedAt(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n return NextResponse.json({\n locale,\n settings: {\n version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: settings?.groupOrder ?? [],\n groupLabels: settings?.groupLabels ?? {},\n itemLabels: settings?.itemLabels ?? {},\n hiddenItems: settings?.hiddenItems ?? [],\n itemOrder: settings?.itemOrder ?? {},\n },\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'user' },\n updatedAt: userVersion?.updatedAt ? userVersion.updatedAt.toISOString() : null,\n })\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n if (!effectiveUserId) {\n return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = sidebarPreferencesInputSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const sanitizeRecord = (record?: Record<string, string>) => {\n if (!record) return {}\n const result: Record<string, string> = {}\n for (const [key, value] of Object.entries(record)) {\n const trimmedKey = key.trim()\n const trimmedValue = value.trim()\n if (!trimmedKey || !trimmedValue) continue\n result[trimmedKey] = trimmedValue\n }\n return result\n }\n\n const groupOrderSource = parsed.data.groupOrder ?? []\n const seen = new Set<string>()\n const groupOrder: string[] = []\n for (const id of groupOrderSource) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n groupOrder.push(trimmed)\n }\n\n const payload = {\n version: parsed.data.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder,\n groupLabels: sanitizeRecord(parsed.data.groupLabels),\n itemLabels: sanitizeRecord(parsed.data.itemLabels),\n hiddenItems: (() => {\n const source = parsed.data.hiddenItems ?? []\n const seenHidden = new Set<string>()\n const values: string[] = []\n for (const href of source) {\n const trimmed = href.trim()\n if (!trimmed || seenHidden.has(trimmed)) continue\n seenHidden.add(trimmed)\n values.push(trimmed)\n }\n return values\n })(),\n itemOrder: (() => {\n const source = parsed.data.itemOrder ?? {}\n const out: Record<string, string[]> = {}\n for (const [groupKey, list] of Object.entries(source)) {\n const trimmedGroup = groupKey.trim()\n if (!trimmedGroup) continue\n const seenItem = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n const trimmedItem = itemKey.trim()\n if (!trimmedItem || seenItem.has(trimmedItem)) continue\n seenItem.add(trimmedItem)\n values.push(trimmedItem)\n }\n if (values.length > 0) out[trimmedGroup] = values\n }\n return out\n })(),\n }\n\n const { locale } = await resolveTranslations()\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const scope = parsed.data.scope ?? { type: 'user' as const }\n\n // Role-scoped write: requires `auth.sidebar.manage` and a role visible to this tenant.\n // applyToRoles/clearRoleIds are forbidden in role scope (validator already rejects them).\n if (scope.type === 'role') {\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n const role = await findRoleInScope(em, { roleId: scope.roleId, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n const existingRolePref = await loadRoleSidebarPreferenceUpdatedAt(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n })\n if (existingRolePref) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: 'auth.role_sidebar_preference',\n resourceId: existingRolePref.id,\n current: existingRolePref.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n const saved = await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n const savedRoleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n })\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags([`nav:sidebar:role:${role.id}`])\n } catch {}\n }\n const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n return NextResponse.json({\n locale,\n settings: {\n version: saved?.version ?? payload.version,\n groupOrder: saved?.groupOrder ?? payload.groupOrder,\n groupLabels: saved?.groupLabels ?? payload.groupLabels,\n itemLabels: saved?.itemLabels ?? payload.itemLabels,\n hiddenItems: saved?.hiddenItems ?? payload.hiddenItems,\n itemOrder: saved?.itemOrder ?? payload.itemOrder,\n },\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'role', roleId: role.id },\n updatedAt: savedRoleVersion?.updatedAt ? savedRoleVersion.updatedAt.toISOString() : null,\n appliedRoles: [],\n clearedRoles: [],\n })\n }\n\n const applyToRolesSource = parsed.data.applyToRoles ?? []\n const applyToRoles = Array.from(new Set(applyToRolesSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n const clearRoleIdsSource = parsed.data.clearRoleIds ?? []\n const clearRoleIds = Array.from(new Set(clearRoleIdsSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n\n if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n\n const existingUserPref = await loadSidebarPreferenceUpdatedAt(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n if (existingUserPref) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: 'auth.sidebar_preference',\n resourceId: existingUserPref.id,\n current: existingUserPref.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n const settings = await saveSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n }, payload)\n\n const roleScope: FilterQuery<Role> = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await findWithDecryption(\n em,\n Role,\n roleScope,\n { orderBy: { name: 'asc' } },\n { tenantId: auth.tenantId ?? null, organizationId: null },\n )\n : []\n const roleMap = new Map<string, Role>(availableRoles.map((role: Role) => [String(role.id), role]))\n\n if (applyToRoles.length > 0) {\n const missing = applyToRoles.filter((id) => !roleMap.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n }\n\n const updatedRoleIds: string[] = []\n const filteredClearRoleIds: string[] = []\n await withAtomicFlush(em, [\n async () => {\n for (const roleId of applyToRoles) {\n const role = roleMap.get(roleId)!\n await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n updatedRoleIds.push(role.id)\n }\n\n const clearTargets = clearRoleIds.filter((id) => !updatedRoleIds.includes(id) && !applyToRoles.includes(id))\n filteredClearRoleIds.push(...clearTargets)\n\n if (filteredClearRoleIds.length > 0) {\n // Cross-locale: role preferences are unique per (role, tenantId); keep the delete\n // filter aligned with save/load helpers so a clear from one locale does not leave\n // a row created under another locale orphaned.\n await em.nativeDelete(RoleSidebarPreference, {\n role: { $in: filteredClearRoleIds },\n tenantId: auth.tenantId ?? null,\n })\n }\n },\n ], { transaction: true })\n\n if (filteredClearRoleIds.length > 0 && cache?.deleteByTags) {\n try {\n await cache.deleteByTags(filteredClearRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`))\n } catch {}\n }\n\n if (cache?.deleteByTags) {\n const tags = [\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${auth.tenantId ?? 'null'}:${auth.orgId ?? 'null'}:${locale}`,\n ...updatedRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`),\n ]\n try {\n await cache.deleteByTags(tags)\n } catch {}\n }\n\n let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []\n if (canApplyToRoles) {\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: availableRoles.map((role: Role) => role.id),\n tenantId: auth.tenantId ?? null,\n locale,\n })\n rolesPayload = availableRoles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n }\n\n const savedUserVersion = await loadSidebarPreferenceUpdatedAt(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n\n return NextResponse.json({\n locale,\n settings,\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'user' },\n updatedAt: savedUserVersion?.updatedAt ? savedUserVersion.updatedAt.toISOString() : null,\n appliedRoles: updatedRoleIds,\n clearedRoles: filteredClearRoleIds,\n })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleIdParam = url.searchParams.get('roleId')\n if (!roleIdParam) {\n return NextResponse.json({ error: 'roleId query parameter is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n\n const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n // Cross-locale: keep the delete filter aligned with save/load helpers (no locale).\n await em.nativeDelete(RoleSidebarPreference, {\n role: role.id,\n tenantId: auth.tenantId ?? null,\n })\n\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags([`nav:sidebar:role:${role.id}`])\n } catch {}\n }\n\n return NextResponse.json({ ok: true, scope: { type: 'role', roleId: role.id } })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Sidebar preferences',\n methods: {\n GET: {\n summary: 'Get sidebar preferences',\n description: 'Returns sidebar customization for the current user (default) or the specified role (`?roleId=\u2026`, requires `auth.sidebar.manage`).',\n responses: [\n { status: 200, description: 'Current sidebar configuration', schema: sidebarPreferencesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-scope read', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar preferences',\n description: 'Updates sidebar configuration. With `scope.type === \"user\"` (default) writes the calling user\\'s personal preferences and may optionally apply the same settings to selected roles via `applyToRoles[]`. With `scope.type === \"role\"` writes the named role variant directly (requires `auth.sidebar.manage`); `applyToRoles[]` and `clearRoleIds[]` are rejected in this mode.',\n requestBody: {\n contentType: 'application/json',\n schema: sidebarPreferencesInputSchema,\n },\n responses: [\n { status: 200, description: 'Preferences saved', schema: sidebarPreferencesUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-wide updates', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete a role sidebar variant',\n description: 'Removes the role variant for the current tenant + locale. Idempotent. Requires `auth.sidebar.manage`.',\n responses: [\n { status: 200, description: 'Variant deleted (or never existed)', schema: sidebarPreferencesDeleteResponseSchema },\n { status: 400, description: 'Missing roleId query parameter', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["rolesPayload"]
|
|
7
7
|
}
|
|
@@ -4,8 +4,9 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { logCrudAccess } from "@open-mercato/shared/lib/crud/factory";
|
|
6
6
|
import { forbidden, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
-
import {
|
|
7
|
+
import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
8
8
|
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
9
|
+
import { UserAcl } from "@open-mercato/core/modules/auth/data/entities";
|
|
9
10
|
import { assertActorCanModifySuperAdminUserTarget } from "@open-mercato/core/modules/auth/lib/grantChecks";
|
|
10
11
|
const getSchema = z.object({ userId: z.string().uuid() });
|
|
11
12
|
const putSchema = z.object({
|
|
@@ -22,7 +23,8 @@ const userAclResponseSchema = z.object({
|
|
|
22
23
|
hasCustomAcl: z.boolean(),
|
|
23
24
|
isSuperAdmin: z.boolean(),
|
|
24
25
|
features: z.array(z.string()),
|
|
25
|
-
organizations: z.array(z.string()).nullable()
|
|
26
|
+
organizations: z.array(z.string()).nullable(),
|
|
27
|
+
updatedAt: z.string().nullable()
|
|
26
28
|
});
|
|
27
29
|
const userAclUpdateResponseSchema = z.object({
|
|
28
30
|
ok: z.literal(true),
|
|
@@ -60,8 +62,9 @@ async function GET(req) {
|
|
|
60
62
|
hasCustomAcl: true,
|
|
61
63
|
isSuperAdmin: !!acl.isSuperAdmin,
|
|
62
64
|
features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],
|
|
63
|
-
organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null
|
|
64
|
-
|
|
65
|
+
organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,
|
|
66
|
+
updatedAt: acl.updatedAt instanceof Date ? acl.updatedAt.toISOString() : null
|
|
67
|
+
} : { hasCustomAcl: false, isSuperAdmin: false, features: [], organizations: null, updatedAt: null };
|
|
65
68
|
await logCrudAccess({
|
|
66
69
|
container,
|
|
67
70
|
auth,
|
|
@@ -106,6 +109,19 @@ async function PUT(req) {
|
|
|
106
109
|
const requestedFeatures = normalizeFeatureList(parsed.data.features);
|
|
107
110
|
const organizations = Array.isArray(parsed.data.organizations) ? parsed.data.organizations : null;
|
|
108
111
|
let acl = await em.findOne(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
|
|
112
|
+
if (acl) {
|
|
113
|
+
try {
|
|
114
|
+
enforceCommandOptimisticLock({
|
|
115
|
+
resourceKind: "auth.user_acl",
|
|
116
|
+
resourceId: acl.id,
|
|
117
|
+
current: acl.updatedAt ?? null,
|
|
118
|
+
request: req
|
|
119
|
+
});
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
109
125
|
const existingIsSuperAdmin = acl ? !!acl.isSuperAdmin : false;
|
|
110
126
|
const existingFeatures = acl && Array.isArray(acl.featuresJson) ? normalizeFeatureList(acl.featuresJson) : [];
|
|
111
127
|
const effectiveFeatures = actorIsSuperAdmin ? requestedFeatures : sanitizeTenantFeatures(requestedFeatures);
|
|
@@ -122,22 +138,29 @@ async function PUT(req) {
|
|
|
122
138
|
}
|
|
123
139
|
}
|
|
124
140
|
const hasCustomAcl = effectiveIsSuperAdmin || effectiveFeatures.length > 0;
|
|
125
|
-
|
|
126
|
-
()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} else {
|
|
130
|
-
if (!acl) {
|
|
131
|
-
acl = em.create(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
|
|
132
|
-
}
|
|
133
|
-
const aclRecord = acl;
|
|
134
|
-
aclRecord.isSuperAdmin = effectiveIsSuperAdmin;
|
|
135
|
-
aclRecord.featuresJson = effectiveFeatures;
|
|
136
|
-
aclRecord.organizationsJson = organizations;
|
|
137
|
-
em.persist(acl);
|
|
138
|
-
}
|
|
141
|
+
if (!hasCustomAcl) {
|
|
142
|
+
if (acl) {
|
|
143
|
+
const aclToRemove = acl;
|
|
144
|
+
await withAtomicFlush(em, [() => em.remove(aclToRemove)], { transaction: true });
|
|
139
145
|
}
|
|
140
|
-
|
|
146
|
+
} else {
|
|
147
|
+
if (!acl) {
|
|
148
|
+
acl = em.create(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
|
|
149
|
+
}
|
|
150
|
+
const aclRecord = acl;
|
|
151
|
+
await withAtomicFlush(
|
|
152
|
+
em,
|
|
153
|
+
[
|
|
154
|
+
() => {
|
|
155
|
+
aclRecord.isSuperAdmin = effectiveIsSuperAdmin;
|
|
156
|
+
aclRecord.featuresJson = effectiveFeatures;
|
|
157
|
+
aclRecord.organizationsJson = organizations;
|
|
158
|
+
em.persist(aclRecord);
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
{ transaction: true }
|
|
162
|
+
);
|
|
163
|
+
}
|
|
141
164
|
await rbacService.invalidateUserCache(parsed.data.userId);
|
|
142
165
|
try {
|
|
143
166
|
const cache = container.resolve("cache");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/auth/api/users/acl/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { forbidden, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { UserAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { assertActorCanModifySuperAdminUserTarget } from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\nconst getSchema = z.object({ userId: z.string().uuid() })\nconst putSchema = z.object({\n userId: z.string().uuid(),\n isSuperAdmin: z.boolean().optional(),\n features: z.array(z.string()).optional(),\n organizations: z.array(z.string()).nullable().optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n}\n\nconst userAclResponseSchema = z.object({\n hasCustomAcl: z.boolean(),\n isSuperAdmin: z.boolean(),\n features: z.array(z.string()),\n organizations: z.array(z.string()).nullable(),\n})\n\nconst userAclUpdateResponseSchema = z.object({\n ok: z.literal(true),\n sanitized: z.boolean(),\n})\n\nconst userAclErrorSchema = z.object({ error: z.string() })\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsed = getSchema.safeParse({ userId: url.searchParams.get('userId') })\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbacService = container.resolve('rbacService') as any\n const actorAcl = auth.sub\n ? await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n : null\n if (!actorAcl?.isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminUserTarget({\n em: em as EntityManager,\n rbacService: rbacService as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId: parsed.data.userId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n const acl = await em.findOne(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })\n const response = acl\n ? {\n hasCustomAcl: true,\n isSuperAdmin: !!acl.isSuperAdmin,\n features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],\n organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,\n }\n : { hasCustomAcl: false, isSuperAdmin: false, features: [], organizations: null }\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [{ id: parsed.data.userId, ...response }],\n idField: 'id',\n resourceKind: 'auth.user_acl',\n organizationId: auth.orgId ?? null,\n tenantId: auth.tenantId ?? null,\n query: { userId: parsed.data.userId },\n accessType: 'read:item',\n })\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({}))\n const parsed = putSchema.safeParse(body)\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbacService = container.resolve('rbacService') as any\n\n const actorAcl = auth.sub\n ? await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n : null\n const actorIsSuperAdmin = !!actorAcl?.isSuperAdmin\n\n if (!actorIsSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminUserTarget({\n em: em as EntityManager,\n rbacService: rbacService as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId: parsed.data.userId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n const requestedFeatures = normalizeFeatureList(parsed.data.features)\n const organizations = Array.isArray(parsed.data.organizations) ? parsed.data.organizations : null\n\n let acl = await em.findOne(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })\n const existingIsSuperAdmin = acl ? !!acl.isSuperAdmin : false\n const existingFeatures = acl && Array.isArray(acl.featuresJson) ? normalizeFeatureList(acl.featuresJson) : []\n\n const effectiveFeatures = actorIsSuperAdmin\n ? requestedFeatures\n : sanitizeTenantFeatures(requestedFeatures)\n\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? false\n let effectiveIsSuperAdmin = requestedIsSuperAdmin\n\n if (!actorIsSuperAdmin) {\n if (requestedIsSuperAdmin && !existingIsSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n if (existingIsSuperAdmin && requestedIsSuperAdmin === false) {\n effectiveIsSuperAdmin = false\n } else {\n effectiveIsSuperAdmin = existingIsSuperAdmin\n }\n }\n\n const hasCustomAcl = effectiveIsSuperAdmin || effectiveFeatures.length > 0\n\n await withAtomicFlush(em, [\n () => {\n if (!hasCustomAcl) {\n if (acl) em.remove(acl)\n } else {\n if (!acl) {\n acl = em.create(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })\n }\n const aclRecord = acl as any\n aclRecord.isSuperAdmin = effectiveIsSuperAdmin\n aclRecord.featuresJson = effectiveFeatures\n aclRecord.organizationsJson = organizations\n em.persist(acl)\n }\n },\n ], { transaction: true })\n\n // Invalidate cache for this user\n await rbacService.invalidateUserCache(parsed.data.userId)\n try {\n const cache = container.resolve('cache') as any\n if (cache) await cache.deleteByTags([`rbac:user:${parsed.data.userId}`])\n } catch {}\n\n return NextResponse.json({\n ok: true,\n sanitized: !actorIsSuperAdmin && (hasRestrictedChanges(requestedFeatures, effectiveFeatures, existingFeatures) || requestedIsSuperAdmin !== effectiveIsSuperAdmin),\n })\n}\n\nfunction normalizeFeatureList(features: unknown): string[] {\n if (!Array.isArray(features)) return []\n const dedup = new Set<string>()\n for (const value of features) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction sanitizeTenantFeatures(features: string[]): string[] {\n return features.filter((feature) => !isTenantRestrictedFeature(feature))\n}\n\nfunction isTenantRestrictedFeature(feature: string): boolean {\n if (feature === '*' || feature === 'directory.*') return true\n if (feature.startsWith('directory.tenants')) return true\n return false\n}\n\nfunction hasRestrictedChanges(requested: string[], effective: string[], existing: string[]): boolean {\n if (requested.length === effective.length) return false\n const effectiveSet = new Set(effective)\n const existingSet = new Set(existing)\n // If the effective set matches existing, we only trimmed restricted duplicates and should not report\n if (effectiveSet.size === existingSet.size) {\n let identical = true\n for (const value of effectiveSet) {\n if (!existingSet.has(value)) {\n identical = false\n break\n }\n }\n if (identical) return false\n }\n return true\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'User ACL management',\n methods: {\n GET: {\n summary: 'Fetch user ACL',\n description: 'Returns custom ACL overrides for a user within the current tenant, if any.',\n query: getSchema,\n responses: [\n { status: 200, description: 'User ACL entry', schema: userAclResponseSchema },\n { status: 400, description: 'Invalid user id', schema: userAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: userAclErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update user ACL',\n description: 'Configures per-user ACL overrides, including super admin access, feature list, and organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: putSchema,\n },\n responses: [\n { status: 200, description: 'User ACL updated', schema: userAclUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: userAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: userAclErrorSchema },\n { status: 403, description: 'Insufficient privileges to modify ACL', schema: userAclErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,WAAW,uBAAuB;AAC3C,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { forbidden, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { UserAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport { assertActorCanModifySuperAdminUserTarget } from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\nconst getSchema = z.object({ userId: z.string().uuid() })\nconst putSchema = z.object({\n userId: z.string().uuid(),\n isSuperAdmin: z.boolean().optional(),\n features: z.array(z.string()).optional(),\n organizations: z.array(z.string()).nullable().optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n}\n\nconst userAclResponseSchema = z.object({\n hasCustomAcl: z.boolean(),\n isSuperAdmin: z.boolean(),\n features: z.array(z.string()),\n organizations: z.array(z.string()).nullable(),\n updatedAt: z.string().nullable(),\n})\n\nconst userAclUpdateResponseSchema = z.object({\n ok: z.literal(true),\n sanitized: z.boolean(),\n})\n\nconst userAclErrorSchema = z.object({ error: z.string() })\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsed = getSchema.safeParse({ userId: url.searchParams.get('userId') })\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbacService = container.resolve('rbacService') as any\n const actorAcl = auth.sub\n ? await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n : null\n if (!actorAcl?.isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminUserTarget({\n em: em as EntityManager,\n rbacService: rbacService as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId: parsed.data.userId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n const acl = await em.findOne(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })\n const response = acl\n ? {\n hasCustomAcl: true,\n isSuperAdmin: !!acl.isSuperAdmin,\n features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],\n organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,\n updatedAt: acl.updatedAt instanceof Date ? acl.updatedAt.toISOString() : null,\n }\n : { hasCustomAcl: false, isSuperAdmin: false, features: [], organizations: null, updatedAt: null }\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [{ id: parsed.data.userId, ...response }],\n idField: 'id',\n resourceKind: 'auth.user_acl',\n organizationId: auth.orgId ?? null,\n tenantId: auth.tenantId ?? null,\n query: { userId: parsed.data.userId },\n accessType: 'read:item',\n })\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({}))\n const parsed = putSchema.safeParse(body)\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbacService = container.resolve('rbacService') as any\n\n const actorAcl = auth.sub\n ? await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n : null\n const actorIsSuperAdmin = !!actorAcl?.isSuperAdmin\n\n if (!actorIsSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminUserTarget({\n em: em as EntityManager,\n rbacService: rbacService as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId: parsed.data.userId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n const requestedFeatures = normalizeFeatureList(parsed.data.features)\n const organizations = Array.isArray(parsed.data.organizations) ? parsed.data.organizations : null\n\n let acl = await em.findOne(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })\n // Optimistic lock: refuse a stale per-user ACL overwrite so concurrent edits\n // cannot silently clobber each other (#2055). Strictly additive \u2014 a no-op when\n // the client sends no expected-version header; skipped when no ACL row exists.\n if (acl) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: 'auth.user_acl',\n resourceId: acl.id,\n current: acl.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n const existingIsSuperAdmin = acl ? !!acl.isSuperAdmin : false\n const existingFeatures = acl && Array.isArray(acl.featuresJson) ? normalizeFeatureList(acl.featuresJson) : []\n\n const effectiveFeatures = actorIsSuperAdmin\n ? requestedFeatures\n : sanitizeTenantFeatures(requestedFeatures)\n\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? false\n let effectiveIsSuperAdmin = requestedIsSuperAdmin\n\n if (!actorIsSuperAdmin) {\n if (requestedIsSuperAdmin && !existingIsSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n if (existingIsSuperAdmin && requestedIsSuperAdmin === false) {\n effectiveIsSuperAdmin = false\n } else {\n effectiveIsSuperAdmin = existingIsSuperAdmin\n }\n }\n\n const hasCustomAcl = effectiveIsSuperAdmin || effectiveFeatures.length > 0\n\n // Persist the ACL mutation inside a transaction so the per-user permission\n // write (or removal) commits atomically (proper ACL-edit transaction handling).\n if (!hasCustomAcl) {\n if (acl) {\n const aclToRemove = acl\n await withAtomicFlush(em, [() => em.remove(aclToRemove)], { transaction: true })\n }\n } else {\n if (!acl) {\n acl = em.create(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })\n }\n const aclRecord = acl as any\n await withAtomicFlush(\n em,\n [\n () => {\n aclRecord.isSuperAdmin = effectiveIsSuperAdmin\n aclRecord.featuresJson = effectiveFeatures\n aclRecord.organizationsJson = organizations\n em.persist(aclRecord)\n },\n ],\n { transaction: true },\n )\n }\n\n // Invalidate cache for this user\n await rbacService.invalidateUserCache(parsed.data.userId)\n try {\n const cache = container.resolve('cache') as any\n if (cache) await cache.deleteByTags([`rbac:user:${parsed.data.userId}`])\n } catch {}\n\n return NextResponse.json({\n ok: true,\n sanitized: !actorIsSuperAdmin && (hasRestrictedChanges(requestedFeatures, effectiveFeatures, existingFeatures) || requestedIsSuperAdmin !== effectiveIsSuperAdmin),\n })\n}\n\nfunction normalizeFeatureList(features: unknown): string[] {\n if (!Array.isArray(features)) return []\n const dedup = new Set<string>()\n for (const value of features) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction sanitizeTenantFeatures(features: string[]): string[] {\n return features.filter((feature) => !isTenantRestrictedFeature(feature))\n}\n\nfunction isTenantRestrictedFeature(feature: string): boolean {\n if (feature === '*' || feature === 'directory.*') return true\n if (feature.startsWith('directory.tenants')) return true\n return false\n}\n\nfunction hasRestrictedChanges(requested: string[], effective: string[], existing: string[]): boolean {\n if (requested.length === effective.length) return false\n const effectiveSet = new Set(effective)\n const existingSet = new Set(existing)\n // If the effective set matches existing, we only trimmed restricted duplicates and should not report\n if (effectiveSet.size === existingSet.size) {\n let identical = true\n for (const value of effectiveSet) {\n if (!existingSet.has(value)) {\n identical = false\n break\n }\n }\n if (identical) return false\n }\n return true\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'User ACL management',\n methods: {\n GET: {\n summary: 'Fetch user ACL',\n description: 'Returns custom ACL overrides for a user within the current tenant, if any.',\n query: getSchema,\n responses: [\n { status: 200, description: 'User ACL entry', schema: userAclResponseSchema },\n { status: 400, description: 'Invalid user id', schema: userAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: userAclErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update user ACL',\n description: 'Configures per-user ACL overrides, including super admin access, feature list, and organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: putSchema,\n },\n responses: [\n { status: 200, description: 'User ACL updated', schema: userAclUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: userAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: userAclErrorSchema },\n { status: 403, description: 'Insufficient privileges to modify ACL', schema: userAclErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -68,7 +68,8 @@ const userListItemSchema = z.object({
|
|
|
68
68
|
tenantId: z.string().uuid().nullable(),
|
|
69
69
|
tenantName: z.string().nullable(),
|
|
70
70
|
roles: z.array(z.string()),
|
|
71
|
-
roleIds: z.array(z.string().uuid()).optional()
|
|
71
|
+
roleIds: z.array(z.string().uuid()).optional(),
|
|
72
|
+
updatedAt: z.string().nullable().optional()
|
|
72
73
|
});
|
|
73
74
|
const userListResponseSchema = z.object({
|
|
74
75
|
items: z.array(userListItemSchema),
|
|
@@ -385,6 +386,7 @@ async function GET(req) {
|
|
|
385
386
|
roles: roleMap[uid] || [],
|
|
386
387
|
roleIds: roleIdMap[uid] || [],
|
|
387
388
|
hasPassword: !!u.passwordHash,
|
|
389
|
+
updatedAt: u.updatedAt instanceof Date ? u.updatedAt.toISOString() : null,
|
|
388
390
|
...cfByUser[uid] || {}
|
|
389
391
|
};
|
|
390
392
|
});
|