@open-mercato/core 0.6.6-develop.6317.1.b3be10ab84 → 0.6.6-develop.6331.1.a33b8e99b7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +3 -0
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +2 -2
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +2 -2
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +12 -0
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +12 -0
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/data/entities.js +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +3 -3
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/business_rules/api/sets/route.js +3 -3
- package/dist/modules/business_rules/api/sets/route.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.js +12 -1
- package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.js +12 -1
- package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
- package/dist/modules/catalog/api/product-media/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +12 -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 +15 -0
- 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 +12 -1
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/components/PriceKindSettings.js +13 -3
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/lib/bulkDelete.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +15 -3
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +2 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +12 -1
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +3 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +3 -3
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
- package/dist/modules/customers/api/addresses/route.js +4 -2
- package/dist/modules/customers/api/addresses/route.js.map +2 -2
- package/dist/modules/customers/api/tags/route.js +8 -3
- package/dist/modules/customers/api/tags/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +13 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +24 -9
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +53 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +13 -3
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +13 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +5 -5
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +24 -2
- package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
- package/dist/modules/customers/commands/pipelines.js +24 -2
- package/dist/modules/customers/commands/pipelines.js.map +2 -2
- package/dist/modules/customers/components/detail/AddressesSection.js +46 -29
- package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +5 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
- package/dist/modules/customers/components/detail/notesAdapter.js +25 -18
- package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/runs.js +1 -0
- package/dist/modules/data_sync/api/runs.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +38 -19
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +1 -1
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +35 -16
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +1 -0
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-schedule-service.js +19 -5
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +3 -3
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js +3 -3
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +12 -1
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +12 -0
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +12 -0
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
- package/dist/modules/entities/api/entities.js +2 -2
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -6
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +2 -2
- 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.map +2 -2
- package/dist/modules/feature_toggles/data/entities.js +1 -1
- package/dist/modules/feature_toggles/data/entities.js.map +2 -2
- package/dist/modules/feature_toggles/data/validators.js +2 -1
- package/dist/modules/feature_toggles/data/validators.js.map +2 -2
- package/dist/modules/feature_toggles/lib/queries.js +2 -1
- package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
- package/dist/modules/messages/api/unread-count/route.js +32 -0
- package/dist/modules/messages/api/unread-count/route.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +12 -1
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +12 -1
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/resources/components/detail/notesAdapter.js +25 -18
- package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +14 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +11 -10
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +6 -1
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +3 -3
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +3 -3
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +6 -1
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentDialog.js +6 -3
- package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +7 -3
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +6 -3
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +7 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/di.js +39 -3
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/sales/setup.js +2 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/en.js +11 -1
- package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
- package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +12 -1
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +12 -1
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +12 -1
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/commands/job-histories.js +3 -3
- package/dist/modules/staff/commands/job-histories.js.map +2 -2
- package/dist/modules/staff/components/detail/addressesAdapter.js +33 -24
- package/dist/modules/staff/components/detail/addressesAdapter.js.map +2 -2
- package/dist/modules/staff/components/detail/notesAdapter.js +25 -18
- package/dist/modules/staff/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +37 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +7 -1
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js +3 -3
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +12 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +11 -1
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +3 -0
- package/src/modules/auth/api/roles/acl/route.ts +2 -2
- package/src/modules/auth/api/users/acl/route.ts +2 -2
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +17 -0
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -0
- package/src/modules/auth/data/entities.ts +2 -2
- package/src/modules/business_rules/api/rules/route.ts +3 -3
- package/src/modules/business_rules/api/sets/route.ts +3 -3
- package/src/modules/business_rules/backend/rules/[id]/page.tsx +17 -1
- package/src/modules/business_rules/backend/sets/[id]/page.tsx +18 -1
- package/src/modules/catalog/api/product-media/route.ts +4 -0
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +17 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +16 -1
- package/src/modules/catalog/components/PriceKindSettings.tsx +15 -7
- package/src/modules/catalog/lib/bulkDelete.ts +6 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -3
- package/src/modules/currencies/backend/currencies/page.tsx +2 -1
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +17 -1
- package/src/modules/currencies/backend/exchange-rates/page.tsx +3 -2
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -3
- package/src/modules/customer_accounts/api/admin/users/[id].ts +3 -3
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
- package/src/modules/customers/api/addresses/route.ts +2 -0
- package/src/modules/customers/api/tags/route.ts +6 -1
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +19 -2
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +29 -8
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +47 -20
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +13 -3
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -2
- package/src/modules/customers/commands/interactions.ts +5 -5
- package/src/modules/customers/commands/pipeline-stages.ts +26 -2
- package/src/modules/customers/commands/pipelines.ts +26 -2
- package/src/modules/customers/components/detail/AddressesSection.tsx +50 -28
- package/src/modules/customers/components/detail/DealForm.tsx +12 -0
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
- package/src/modules/customers/components/detail/notesAdapter.ts +29 -18
- 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/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
- package/src/modules/data_sync/api/runs.ts +1 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +38 -20
- package/src/modules/data_sync/api/schedules/route.ts +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +35 -16
- package/src/modules/data_sync/data/validators.ts +1 -0
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
- package/src/modules/data_sync/lib/sync-schedule-service.ts +30 -5
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +3 -3
- package/src/modules/dictionaries/api/[dictionaryId]/route.ts +3 -3
- package/src/modules/dictionaries/components/DictionariesManager.tsx +21 -1
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -0
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -0
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +17 -0
- package/src/modules/entities/api/entities.ts +2 -2
- package/src/modules/entities/api/records.ts +24 -6
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
- package/src/modules/feature_toggles/api/overrides/route.ts +2 -2
- package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +12 -0
- package/src/modules/feature_toggles/data/entities.ts +1 -1
- package/src/modules/feature_toggles/data/validators.ts +1 -0
- package/src/modules/feature_toggles/lib/queries.ts +1 -0
- package/src/modules/inbox_ops/api/settings/route.ts +2 -2
- package/src/modules/messages/api/unread-count/route.ts +40 -0
- package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
- package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +17 -1
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +17 -1
- package/src/modules/resources/components/detail/notesAdapter.ts +29 -18
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -1
- package/src/modules/sales/commands/documents.ts +11 -10
- package/src/modules/sales/commands/payments.ts +11 -0
- package/src/modules/sales/commands/returns.ts +3 -3
- package/src/modules/sales/commands/shared.ts +10 -4
- package/src/modules/sales/commands/shipments.ts +14 -0
- package/src/modules/sales/components/documents/PaymentDialog.tsx +7 -3
- package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -3
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +7 -3
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +8 -3
- package/src/modules/sales/di.ts +68 -9
- package/src/modules/sales/setup.ts +2 -0
- package/src/modules/shipping_carriers/i18n/de.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.ts +10 -0
- package/src/modules/shipping_carriers/i18n/es.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +17 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +17 -1
- package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +17 -1
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -1
- package/src/modules/staff/commands/job-histories.ts +3 -3
- package/src/modules/staff/components/detail/addressesAdapter.ts +40 -23
- package/src/modules/staff/components/detail/notesAdapter.ts +28 -18
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +74 -0
- package/src/modules/translations/components/TranslationManager.tsx +14 -1
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
- package/src/modules/workflows/api/definitions/[id]/route.ts +3 -3
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +18 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +19 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "@open-mercato/ui/primitives/dialog";
|
|
24
24
|
import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
25
25
|
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
26
|
+
import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
26
27
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
27
28
|
import { raiseCrudError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
28
29
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -205,6 +206,7 @@ function AttachmentPartitionSettings({ s3Enabled }) {
|
|
|
205
206
|
await loadItems();
|
|
206
207
|
} catch (err) {
|
|
207
208
|
console.error("[attachments.partitions] save failed", err);
|
|
209
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
208
210
|
const message = err instanceof Error ? err.message : t("attachments.partitions.errors.save", "Failed to save partition.");
|
|
209
211
|
setError(message);
|
|
210
212
|
} finally {
|
|
@@ -239,6 +241,7 @@ function AttachmentPartitionSettings({ s3Enabled }) {
|
|
|
239
241
|
await loadItems();
|
|
240
242
|
} catch (err) {
|
|
241
243
|
console.error("[attachments.partitions] delete failed", err);
|
|
244
|
+
if (surfaceRecordConflict(err, t)) return;
|
|
242
245
|
flash(t("attachments.partitions.errors.delete", "Failed to delete partition."), "error");
|
|
243
246
|
}
|
|
244
247
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/attachments/components/AttachmentPartitionSettings.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n storageDriver: string\n configJson: Record<string, unknown> | null\n envKey: string\n createdAt: string | null\n updatedAt?: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\ntype S3CredentialsSource = 'integration' | 'env'\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n storageDriver: 'local',\n s3CredentialsSource: 'integration' as S3CredentialsSource,\n s3CredentialsEnvPrefix: '',\n s3Bucket: '',\n s3Region: '',\n s3Endpoint: '',\n s3ForcePathStyle: false,\n}\n\nconst ALL_STORAGE_DRIVER_OPTIONS = [\n { value: 'local', label: 'Local filesystem' },\n { value: 's3', label: 'Amazon S3 / S3-compatible', requiresModule: 's3' },\n]\n\nconst OCR_MODEL_DEFAULT_VALUE = '__default__'\n\nconst OCR_MODEL_OPTIONS = [\n { value: OCR_MODEL_DEFAULT_VALUE, label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport type AttachmentPartitionSettingsProps = {\n s3Enabled: boolean\n}\n\nexport function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSettingsProps) {\n const storageDriverOptions = ALL_STORAGE_DRIVER_OPTIONS.filter(\n (opt) => !opt.requiresModule || (opt.requiresModule === 's3' && s3Enabled),\n )\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const s3Unavailable = !s3Enabled\n const s3SelectedWhileUnavailable = s3Unavailable && form.storageDriver === 's3'\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n const cfg = state.entry.configJson ?? {}\n const credsPrefix = typeof cfg.credentialsEnvPrefix === 'string' ? cfg.credentialsEnvPrefix : ''\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n storageDriver: state.entry.storageDriver ?? 'local',\n s3CredentialsSource: credsPrefix ? 'env' : 'integration',\n s3CredentialsEnvPrefix: credsPrefix,\n s3Bucket: typeof cfg.bucket === 'string' ? cfg.bucket : '',\n s3Region: typeof cfg.region === 'string' ? cfg.region : '',\n s3Endpoint: typeof cfg.endpoint === 'string' ? cfg.endpoint : '',\n s3ForcePathStyle: cfg.forcePathStyle === true,\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n if (form.storageDriver === 's3' && form.s3CredentialsSource === 'env') {\n if (!form.s3CredentialsEnvPrefix.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3EnvPrefixRequired',\n 'Credentials env prefix is required when using a separate credentials source.',\n ),\n )\n return\n }\n if (!form.s3Bucket.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3BucketRequired',\n 'Bucket is required when using a separate credentials source.',\n ),\n )\n return\n }\n }\n setSubmitting(true)\n setError(null)\n try {\n const s3ConfigJson =\n form.storageDriver === 's3'\n ? {\n bucket: form.s3Bucket.trim() || undefined,\n region: form.s3Region.trim() || undefined,\n endpoint: form.s3Endpoint.trim() || undefined,\n forcePathStyle: form.s3ForcePathStyle || undefined,\n ...(form.s3CredentialsSource === 'env'\n ? { credentialsEnvPrefix: form.s3CredentialsEnvPrefix.trim() || undefined }\n : {}),\n }\n : null\n\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n storageDriver: form.storageDriver,\n configJson: s3ConfigJson,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const lockHeader =\n dialog.mode === 'edit' ? buildOptimisticLockHeader(dialog.entry.updatedAt) : {}\n const call = await withScopedApiRequestHeaders(lockHeader, () =>\n apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n }),\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n () =>\n apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n }),\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.storageDriver', 'Storage'),\n accessorKey: 'storageDriver',\n cell: ({ row }) => (\n <span className=\"text-sm capitalize\">\n {row.original.storageDriver === 's3' ? 'S3' : row.original.storageDriver ?? 'local'}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel ? form.ocrModel : OCR_MODEL_DEFAULT_VALUE}\n onValueChange={(value) =>\n setForm((prev) => ({\n ...prev,\n ocrModel: value === OCR_MODEL_DEFAULT_VALUE ? '' : value,\n }))\n }\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(\n `attachments.partitions.form.ocrModelOptions.${\n option.value === OCR_MODEL_DEFAULT_VALUE ? 'default' : option.value\n }`,\n option.label,\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-storage-driver\">\n {t('attachments.partitions.form.storageDriverLabel', 'Storage Driver')}\n </Label>\n <select\n id=\"partition-storage-driver\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.storageDriver}\n onChange={(event) => setForm((prev) => ({ ...prev, storageDriver: event.target.value }))}\n >\n {storageDriverOptions.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.value === 's3' && s3Unavailable}\n >\n {option.label}\n </option>\n ))}\n </select>\n {s3Unavailable ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.storageDriverS3DisabledHelp',\n 'S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart to enable S3 partitions.',\n )}\n </p>\n ) : null}\n </div>\n {form.storageDriver === 's3' && (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {t('attachments.partitions.form.s3ConfigTitle', 'S3 Configuration')}\n </p>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-source\">\n {t('attachments.partitions.form.s3CredsSourceLabel', 'Credentials source')}\n </Label>\n <select\n id=\"partition-s3-creds-source\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.s3CredentialsSource}\n onChange={(event) =>\n setForm((prev) => ({\n ...prev,\n s3CredentialsSource: event.target.value as S3CredentialsSource,\n ...(event.target.value === 'integration' ? { s3CredentialsEnvPrefix: '' } : {}),\n }))\n }\n >\n <option value=\"integration\">\n {t(\n 'attachments.partitions.form.s3CredsSourceIntegration',\n 'Use Integration Marketplace credentials',\n )}\n </option>\n <option value=\"env\">\n {t(\n 'attachments.partitions.form.s3CredsSourceEnv',\n 'Use separate env-based credentials',\n )}\n </option>\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3CredsSourceIntegrationHelp',\n 'This partition reuses the credentials, bucket, region and endpoint configured in the S3 integration. Override individual fields below only when needed.',\n )\n : t(\n 'attachments.partitions.form.s3CredsSourceEnvHelp',\n 'This partition uses its own credentials read from environment variables. The integration credentials are ignored. Bucket and credentials prefix are required.',\n )}\n </p>\n </div>\n {form.s3CredentialsSource === 'env' && (\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-prefix\">\n {t('attachments.partitions.form.s3CredsPrefixLabel', 'Credentials Env Prefix')}\n </Label>\n <Input\n id=\"partition-s3-creds-prefix\"\n value={form.s3CredentialsEnvPrefix}\n onChange={(event) => setForm((prev) => ({ ...prev, s3CredentialsEnvPrefix: event.target.value }))}\n placeholder=\"OM_INTEGRATION_STORAGE_S3\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.s3CredsPrefixHelp',\n 'Reads {PREFIX}_ACCESS_KEY_ID and {PREFIX}_SECRET_ACCESS_KEY from environment variables.',\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-bucket\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3BucketOverrideLabel', 'Bucket override (optional)')\n : t('attachments.partitions.form.s3BucketLabel', 'Bucket')}\n </Label>\n <Input\n id=\"partition-s3-bucket\"\n value={form.s3Bucket}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Bucket: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3BucketOverridePlaceholder',\n 'Leave empty to use integration bucket',\n )\n : 'my-company-attachments'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-region\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3RegionOverrideLabel', 'Region override (optional)')\n : t('attachments.partitions.form.s3RegionLabel', 'Region')}\n </Label>\n <Input\n id=\"partition-s3-region\"\n value={form.s3Region}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Region: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3RegionOverridePlaceholder',\n 'Leave empty to use integration region',\n )\n : 'us-east-1'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-endpoint\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3EndpointOverrideLabel', 'Custom endpoint override (optional)')\n : t('attachments.partitions.form.s3EndpointLabel', 'Custom Endpoint')}\n </Label>\n <Input\n id=\"partition-s3-endpoint\"\n value={form.s3Endpoint}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Endpoint: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3EndpointOverridePlaceholder',\n 'Leave empty to use integration endpoint',\n )\n : 'https://fra1.digitaloceanspaces.com'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('attachments.partitions.form.s3EndpointHelp', 'Leave empty for AWS S3. Required for MinIO, DigitalOcean Spaces, etc.')}\n </p>\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.s3ForcePathStyle}\n onChange={(event) => setForm((prev) => ({ ...prev, s3ForcePathStyle: event.target.checked }))}\n />\n {t('attachments.partitions.form.s3ForcePathStyleLabel', 'Force Path Style (required for MinIO)')}\n </label>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {s3SelectedWhileUnavailable ? (\n <p className=\"text-sm text-status-error-text\">\n {t(\n 'attachments.partitions.errors.s3Disabled',\n 'This partition uses S3, but S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart the app.',\n )}\n </p>\n ) : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting || s3SelectedWhileUnavailable}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n storageDriver: string\n configJson: Record<string, unknown> | null\n envKey: string\n createdAt: string | null\n updatedAt?: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\ntype S3CredentialsSource = 'integration' | 'env'\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n storageDriver: 'local',\n s3CredentialsSource: 'integration' as S3CredentialsSource,\n s3CredentialsEnvPrefix: '',\n s3Bucket: '',\n s3Region: '',\n s3Endpoint: '',\n s3ForcePathStyle: false,\n}\n\nconst ALL_STORAGE_DRIVER_OPTIONS = [\n { value: 'local', label: 'Local filesystem' },\n { value: 's3', label: 'Amazon S3 / S3-compatible', requiresModule: 's3' },\n]\n\nconst OCR_MODEL_DEFAULT_VALUE = '__default__'\n\nconst OCR_MODEL_OPTIONS = [\n { value: OCR_MODEL_DEFAULT_VALUE, label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport type AttachmentPartitionSettingsProps = {\n s3Enabled: boolean\n}\n\nexport function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSettingsProps) {\n const storageDriverOptions = ALL_STORAGE_DRIVER_OPTIONS.filter(\n (opt) => !opt.requiresModule || (opt.requiresModule === 's3' && s3Enabled),\n )\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const s3Unavailable = !s3Enabled\n const s3SelectedWhileUnavailable = s3Unavailable && form.storageDriver === 's3'\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n const cfg = state.entry.configJson ?? {}\n const credsPrefix = typeof cfg.credentialsEnvPrefix === 'string' ? cfg.credentialsEnvPrefix : ''\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n storageDriver: state.entry.storageDriver ?? 'local',\n s3CredentialsSource: credsPrefix ? 'env' : 'integration',\n s3CredentialsEnvPrefix: credsPrefix,\n s3Bucket: typeof cfg.bucket === 'string' ? cfg.bucket : '',\n s3Region: typeof cfg.region === 'string' ? cfg.region : '',\n s3Endpoint: typeof cfg.endpoint === 'string' ? cfg.endpoint : '',\n s3ForcePathStyle: cfg.forcePathStyle === true,\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n if (form.storageDriver === 's3' && form.s3CredentialsSource === 'env') {\n if (!form.s3CredentialsEnvPrefix.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3EnvPrefixRequired',\n 'Credentials env prefix is required when using a separate credentials source.',\n ),\n )\n return\n }\n if (!form.s3Bucket.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3BucketRequired',\n 'Bucket is required when using a separate credentials source.',\n ),\n )\n return\n }\n }\n setSubmitting(true)\n setError(null)\n try {\n const s3ConfigJson =\n form.storageDriver === 's3'\n ? {\n bucket: form.s3Bucket.trim() || undefined,\n region: form.s3Region.trim() || undefined,\n endpoint: form.s3Endpoint.trim() || undefined,\n forcePathStyle: form.s3ForcePathStyle || undefined,\n ...(form.s3CredentialsSource === 'env'\n ? { credentialsEnvPrefix: form.s3CredentialsEnvPrefix.trim() || undefined }\n : {}),\n }\n : null\n\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n storageDriver: form.storageDriver,\n configJson: s3ConfigJson,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const lockHeader =\n dialog.mode === 'edit' ? buildOptimisticLockHeader(dialog.entry.updatedAt) : {}\n const call = await withScopedApiRequestHeaders(lockHeader, () =>\n apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n }),\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n if (surfaceRecordConflict(err, t)) return\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n () =>\n apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n }),\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n if (surfaceRecordConflict(err, t)) return\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.storageDriver', 'Storage'),\n accessorKey: 'storageDriver',\n cell: ({ row }) => (\n <span className=\"text-sm capitalize\">\n {row.original.storageDriver === 's3' ? 'S3' : row.original.storageDriver ?? 'local'}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel ? form.ocrModel : OCR_MODEL_DEFAULT_VALUE}\n onValueChange={(value) =>\n setForm((prev) => ({\n ...prev,\n ocrModel: value === OCR_MODEL_DEFAULT_VALUE ? '' : value,\n }))\n }\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(\n `attachments.partitions.form.ocrModelOptions.${\n option.value === OCR_MODEL_DEFAULT_VALUE ? 'default' : option.value\n }`,\n option.label,\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-storage-driver\">\n {t('attachments.partitions.form.storageDriverLabel', 'Storage Driver')}\n </Label>\n <select\n id=\"partition-storage-driver\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.storageDriver}\n onChange={(event) => setForm((prev) => ({ ...prev, storageDriver: event.target.value }))}\n >\n {storageDriverOptions.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.value === 's3' && s3Unavailable}\n >\n {option.label}\n </option>\n ))}\n </select>\n {s3Unavailable ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.storageDriverS3DisabledHelp',\n 'S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart to enable S3 partitions.',\n )}\n </p>\n ) : null}\n </div>\n {form.storageDriver === 's3' && (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {t('attachments.partitions.form.s3ConfigTitle', 'S3 Configuration')}\n </p>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-source\">\n {t('attachments.partitions.form.s3CredsSourceLabel', 'Credentials source')}\n </Label>\n <select\n id=\"partition-s3-creds-source\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.s3CredentialsSource}\n onChange={(event) =>\n setForm((prev) => ({\n ...prev,\n s3CredentialsSource: event.target.value as S3CredentialsSource,\n ...(event.target.value === 'integration' ? { s3CredentialsEnvPrefix: '' } : {}),\n }))\n }\n >\n <option value=\"integration\">\n {t(\n 'attachments.partitions.form.s3CredsSourceIntegration',\n 'Use Integration Marketplace credentials',\n )}\n </option>\n <option value=\"env\">\n {t(\n 'attachments.partitions.form.s3CredsSourceEnv',\n 'Use separate env-based credentials',\n )}\n </option>\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3CredsSourceIntegrationHelp',\n 'This partition reuses the credentials, bucket, region and endpoint configured in the S3 integration. Override individual fields below only when needed.',\n )\n : t(\n 'attachments.partitions.form.s3CredsSourceEnvHelp',\n 'This partition uses its own credentials read from environment variables. The integration credentials are ignored. Bucket and credentials prefix are required.',\n )}\n </p>\n </div>\n {form.s3CredentialsSource === 'env' && (\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-prefix\">\n {t('attachments.partitions.form.s3CredsPrefixLabel', 'Credentials Env Prefix')}\n </Label>\n <Input\n id=\"partition-s3-creds-prefix\"\n value={form.s3CredentialsEnvPrefix}\n onChange={(event) => setForm((prev) => ({ ...prev, s3CredentialsEnvPrefix: event.target.value }))}\n placeholder=\"OM_INTEGRATION_STORAGE_S3\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.s3CredsPrefixHelp',\n 'Reads {PREFIX}_ACCESS_KEY_ID and {PREFIX}_SECRET_ACCESS_KEY from environment variables.',\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-bucket\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3BucketOverrideLabel', 'Bucket override (optional)')\n : t('attachments.partitions.form.s3BucketLabel', 'Bucket')}\n </Label>\n <Input\n id=\"partition-s3-bucket\"\n value={form.s3Bucket}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Bucket: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3BucketOverridePlaceholder',\n 'Leave empty to use integration bucket',\n )\n : 'my-company-attachments'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-region\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3RegionOverrideLabel', 'Region override (optional)')\n : t('attachments.partitions.form.s3RegionLabel', 'Region')}\n </Label>\n <Input\n id=\"partition-s3-region\"\n value={form.s3Region}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Region: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3RegionOverridePlaceholder',\n 'Leave empty to use integration region',\n )\n : 'us-east-1'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-endpoint\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3EndpointOverrideLabel', 'Custom endpoint override (optional)')\n : t('attachments.partitions.form.s3EndpointLabel', 'Custom Endpoint')}\n </Label>\n <Input\n id=\"partition-s3-endpoint\"\n value={form.s3Endpoint}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Endpoint: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3EndpointOverridePlaceholder',\n 'Leave empty to use integration endpoint',\n )\n : 'https://fra1.digitaloceanspaces.com'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('attachments.partitions.form.s3EndpointHelp', 'Leave empty for AWS S3. Required for MinIO, DigitalOcean Spaces, etc.')}\n </p>\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.s3ForcePathStyle}\n onChange={(event) => setForm((prev) => ({ ...prev, s3ForcePathStyle: event.target.checked }))}\n />\n {t('attachments.partitions.form.s3ForcePathStyleLabel', 'Force Path Style (required for MinIO)')}\n </label>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {s3SelectedWhileUnavailable ? (\n <p className=\"text-sm text-status-error-text\">\n {t(\n 'attachments.partitions.errors.s3Disabled',\n 'This partition uses S3, but S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart the app.',\n )}\n </p>\n ) : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting || s3SelectedWhileUnavailable}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ 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 { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
-
import {
|
|
7
|
+
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
8
8
|
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
9
9
|
import { RoleAcl, Role } from "@open-mercato/core/modules/auth/data/entities";
|
|
10
10
|
import { resolveIsSuperAdmin } from "@open-mercato/core/modules/auth/lib/tenantAccess";
|
|
@@ -152,7 +152,7 @@ async function PUT(req) {
|
|
|
152
152
|
let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId });
|
|
153
153
|
if (acl) {
|
|
154
154
|
try {
|
|
155
|
-
|
|
155
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
156
156
|
resourceKind: "auth.role_acl",
|
|
157
157
|
resourceId: acl.id,
|
|
158
158
|
current: acl.updatedAt ?? null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/auth/api/roles/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 { 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 { RoleAcl, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n normalizeGrantFeatureList,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\n\ntype TaggableCache = { deleteByTags?: (tags: string[]) => Promise<void> | void }\n\nconst getSchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n})\nconst putSchema = z.object({\n roleId: z.string().uuid(),\n isSuperAdmin: z.boolean().optional(),\n features: z.array(z.string()).optional(),\n organizations: z.array(z.string()).nullable().optional(),\n tenantId: z.string().uuid().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 roleAclResponseSchema = z.object({\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 roleAclUpdateResponseSchema = z.object({\n ok: z.literal(true),\n sanitized: z.boolean(),\n})\n\nconst roleAclErrorSchema = 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({\n roleId: url.searchParams.get('roleId'),\n tenantId: url.searchParams.get('tenantId') || undefined,\n })\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = container.resolve('em') as EntityManager\n const authTenantId = auth.tenantId ?? null\n const roleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n roleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, roleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let tenantScope = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== tenantScope) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) tenantScope = parsed.data.tenantId\n else return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (!tenantScope && !isSuperAdmin) tenantScope = authTenantId ?? null\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: tenantScope,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\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 acl = tenantScope\n ? await em.findOne(RoleAcl, { role, tenantId: tenantScope })\n : null\n const response = acl\n ? {\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 : { isSuperAdmin: false, features: [], organizations: null, updatedAt: null }\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [{ id: parsed.data.roleId, ...response }],\n idField: 'id',\n resourceKind: 'auth.role_acl',\n organizationId: auth.orgId ?? null,\n tenantId: tenantScope,\n query: { roleId: parsed.data.roleId, tenantId: tenantScope },\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 EntityManager\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const rbacService = container.resolve('rbacService') as RbacService\n const authTenantId = auth.tenantId ?? null\n const putRoleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n putRoleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, putRoleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let targetTenantId = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== targetTenantId) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) {\n targetTenantId = parsed.data.tenantId\n } else {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n }\n if (!targetTenantId && !isSuperAdmin) targetTenantId = authTenantId ?? null\n if (!targetTenantId) return NextResponse.json({ error: 'Tenant required' }, { status: 400 })\n\n if (!isSuperAdmin && targetTenantId !== authTenantId) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\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 let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId })\n // Optimistic lock: refuse a stale ACL overwrite so two admins editing the same\n // role's features in parallel cannot silently clobber each other (#2055). The\n // check is strictly additive \u2014 when the client sends no expected-version header\n // it is a no-op. Skipped when the ACL row does not exist yet (first grant has\n // no prior version to conflict with).\n if (acl) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: 'auth.role_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 } else {\n acl = em.create(RoleAcl, {\n role,\n tenantId: targetTenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n }\n\n const existingIsSuperAdmin = !!acl.isSuperAdmin\n const existingFeatures = normalizeGrantFeatureList(acl.featuresJson)\n const existingOrganizations = normalizeOrganizations(acl.organizationsJson)\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? existingIsSuperAdmin\n const requestedFeatures = parsed.data.features === undefined\n ? existingFeatures\n : normalizeGrantFeatureList(parsed.data.features)\n const requestedOrganizations = parsed.data.organizations === undefined\n ? existingOrganizations\n : normalizeOrganizations(parsed.data.organizations)\n\n try {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n isSuperAdmin: requestedIsSuperAdmin,\n features: requestedFeatures,\n organizations: requestedOrganizations,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Persist the ACL mutation inside a transaction so the role-permission write\n // commits atomically (proper ACL-edit transaction handling).\n const aclToPersist = acl\n await withAtomicFlush(\n em,\n [\n () => {\n aclToPersist.organizationsJson = requestedOrganizations\n aclToPersist.isSuperAdmin = requestedIsSuperAdmin\n aclToPersist.featuresJson = requestedFeatures\n em.persist(aclToPersist)\n },\n ],\n { transaction: true },\n )\n\n // Invalidate cache for all users in this tenant since role ACL changed\n if (targetTenantId) {\n await rbacService.invalidateTenantCache(targetTenantId)\n // Sidebar nav caches depend on RBAC; invalidate tenant scope nav caches\n try {\n const cache = container.resolve('cache') as TaggableCache | undefined\n if (cache?.deleteByTags) await cache.deleteByTags([`rbac:tenant:${targetTenantId}`])\n } catch {}\n }\n \n return NextResponse.json({\n ok: true,\n sanitized: false,\n })\n}\n\nfunction normalizeOrganizations(organizations: unknown): string[] | null {\n if (!Array.isArray(organizations)) return null\n return normalizeGrantFeatureList(organizations)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Role ACL management',\n methods: {\n GET: {\n summary: 'Fetch role ACL',\n description: 'Returns the feature and organization assignments associated with a role within the current tenant.',\n query: getSchema,\n responses: [\n { status: 200, description: 'Role ACL entry', schema: roleAclResponseSchema },\n { status: 400, description: 'Invalid role id', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update role ACL',\n description: 'Replaces the feature list, super admin flag, and optional organization assignments for a role.',\n requestBody: {\n contentType: 'application/json',\n schema: putSchema,\n },\n responses: [\n { status: 200, description: 'Role ACL updated', schema: roleAclUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 403, description: 'Insufficient privileges to modify ACL', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,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 { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { RoleAcl, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n normalizeGrantFeatureList,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\n\ntype TaggableCache = { deleteByTags?: (tags: string[]) => Promise<void> | void }\n\nconst getSchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n})\nconst putSchema = z.object({\n roleId: z.string().uuid(),\n isSuperAdmin: z.boolean().optional(),\n features: z.array(z.string()).optional(),\n organizations: z.array(z.string()).nullable().optional(),\n tenantId: z.string().uuid().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 roleAclResponseSchema = z.object({\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 roleAclUpdateResponseSchema = z.object({\n ok: z.literal(true),\n sanitized: z.boolean(),\n})\n\nconst roleAclErrorSchema = 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({\n roleId: url.searchParams.get('roleId'),\n tenantId: url.searchParams.get('tenantId') || undefined,\n })\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = container.resolve('em') as EntityManager\n const authTenantId = auth.tenantId ?? null\n const roleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n roleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, roleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let tenantScope = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== tenantScope) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) tenantScope = parsed.data.tenantId\n else return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (!tenantScope && !isSuperAdmin) tenantScope = authTenantId ?? null\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: tenantScope,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\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 acl = tenantScope\n ? await em.findOne(RoleAcl, { role, tenantId: tenantScope })\n : null\n const response = acl\n ? {\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 : { isSuperAdmin: false, features: [], organizations: null, updatedAt: null }\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [{ id: parsed.data.roleId, ...response }],\n idField: 'id',\n resourceKind: 'auth.role_acl',\n organizationId: auth.orgId ?? null,\n tenantId: tenantScope,\n query: { roleId: parsed.data.roleId, tenantId: tenantScope },\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 EntityManager\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const rbacService = container.resolve('rbacService') as RbacService\n const authTenantId = auth.tenantId ?? null\n const putRoleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n putRoleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, putRoleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let targetTenantId = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== targetTenantId) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) {\n targetTenantId = parsed.data.tenantId\n } else {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n }\n if (!targetTenantId && !isSuperAdmin) targetTenantId = authTenantId ?? null\n if (!targetTenantId) return NextResponse.json({ error: 'Tenant required' }, { status: 400 })\n\n if (!isSuperAdmin && targetTenantId !== authTenantId) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\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 let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId })\n // Optimistic lock: refuse a stale ACL overwrite so two admins editing the same\n // role's features in parallel cannot silently clobber each other (#2055). The\n // check is strictly additive \u2014 when the client sends no expected-version header\n // it is a no-op. Skipped when the ACL row does not exist yet (first grant has\n // no prior version to conflict with).\n if (acl) {\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'auth.role_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 } else {\n acl = em.create(RoleAcl, {\n role,\n tenantId: targetTenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n }\n\n const existingIsSuperAdmin = !!acl.isSuperAdmin\n const existingFeatures = normalizeGrantFeatureList(acl.featuresJson)\n const existingOrganizations = normalizeOrganizations(acl.organizationsJson)\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? existingIsSuperAdmin\n const requestedFeatures = parsed.data.features === undefined\n ? existingFeatures\n : normalizeGrantFeatureList(parsed.data.features)\n const requestedOrganizations = parsed.data.organizations === undefined\n ? existingOrganizations\n : normalizeOrganizations(parsed.data.organizations)\n\n try {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n isSuperAdmin: requestedIsSuperAdmin,\n features: requestedFeatures,\n organizations: requestedOrganizations,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Persist the ACL mutation inside a transaction so the role-permission write\n // commits atomically (proper ACL-edit transaction handling).\n const aclToPersist = acl\n await withAtomicFlush(\n em,\n [\n () => {\n aclToPersist.organizationsJson = requestedOrganizations\n aclToPersist.isSuperAdmin = requestedIsSuperAdmin\n aclToPersist.featuresJson = requestedFeatures\n em.persist(aclToPersist)\n },\n ],\n { transaction: true },\n )\n\n // Invalidate cache for all users in this tenant since role ACL changed\n if (targetTenantId) {\n await rbacService.invalidateTenantCache(targetTenantId)\n // Sidebar nav caches depend on RBAC; invalidate tenant scope nav caches\n try {\n const cache = container.resolve('cache') as TaggableCache | undefined\n if (cache?.deleteByTags) await cache.deleteByTags([`rbac:tenant:${targetTenantId}`])\n } catch {}\n }\n \n return NextResponse.json({\n ok: true,\n sanitized: false,\n })\n}\n\nfunction normalizeOrganizations(organizations: unknown): string[] | null {\n if (!Array.isArray(organizations)) return null\n return normalizeGrantFeatureList(organizations)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Role ACL management',\n methods: {\n GET: {\n summary: 'Fetch role ACL',\n description: 'Returns the feature and organization assignments associated with a role within the current tenant.',\n query: getSchema,\n responses: [\n { status: 200, description: 'Role ACL entry', schema: roleAclResponseSchema },\n { status: 400, description: 'Invalid role id', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update role ACL',\n description: 'Replaces the feature list, super admin flag, and optional organization assignments for a role.',\n requestBody: {\n contentType: 'application/json',\n schema: putSchema,\n },\n responses: [\n { status: 200, description: 'Role ACL updated', schema: roleAclUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 403, description: 'Insufficient privileges to modify ACL', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ 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 { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
8
8
|
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
9
9
|
import { UserAcl } from "@open-mercato/core/modules/auth/data/entities";
|
|
10
10
|
import {
|
|
@@ -134,7 +134,7 @@ async function PUT(req) {
|
|
|
134
134
|
let acl = await em.findOne(UserAcl, { user: parsed.data.userId, tenantId: auth.tenantId });
|
|
135
135
|
if (acl) {
|
|
136
136
|
try {
|
|
137
|
-
|
|
137
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
138
138
|
resourceKind: "auth.user_acl",
|
|
139
139
|
resourceId: acl.id,
|
|
140
140
|
current: acl.updatedAt ?? null,
|
|
@@ -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 { 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 {\n assertActorCanAccessUserTarget,\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminUserTarget,\n normalizeGrantFeatureList,\n} 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 await assertActorCanAccessUserTarget({\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 await assertActorCanAccessUserTarget({\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 = normalizeGrantFeatureList(parsed.data.features)\n const organizations = normalizeOrganizations(parsed.data.organizations)\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 ? normalizeGrantFeatureList(acl.featuresJson) : []\n\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? false\n\n try {\n await assertActorCanGrantAcl({\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 isSuperAdmin: requestedIsSuperAdmin,\n features: requestedFeatures,\n organizations,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const effectiveFeatures = actorIsSuperAdmin\n ? requestedFeatures\n : sanitizeTenantFeatures(requestedFeatures)\n\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 normalizeOrganizations(organizations: unknown): string[] | null {\n if (!Array.isArray(organizations)) return null\n return normalizeGrantFeatureList(organizations)\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 { enforceCommandOptimisticLockWithGuards } 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 {\n assertActorCanAccessUserTarget,\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminUserTarget,\n normalizeGrantFeatureList,\n} 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 await assertActorCanAccessUserTarget({\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 await assertActorCanAccessUserTarget({\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 = normalizeGrantFeatureList(parsed.data.features)\n const organizations = normalizeOrganizations(parsed.data.organizations)\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 await enforceCommandOptimisticLockWithGuards(container, {\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 ? normalizeGrantFeatureList(acl.featuresJson) : []\n\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? false\n\n try {\n await assertActorCanGrantAcl({\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 isSuperAdmin: requestedIsSuperAdmin,\n features: requestedFeatures,\n organizations,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const effectiveFeatures = actorIsSuperAdmin\n ? requestedFeatures\n : sanitizeTenantFeatures(requestedFeatures)\n\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 normalizeOrganizations(organizations: unknown): string[] | null {\n if (!Array.isArray(organizations)) return null\n return normalizeGrantFeatureList(organizations)\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,8CAA8C;AACvD,SAAS,uBAAuB;AAChC,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,MAAM,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACxD,MAAM,YAAY,EAAE,OAAO;AAAA,EACzB,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,cAAc,EAAE,QAAQ,EAAE,SAAS;AAAA,EACnC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACvC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS;AACzD,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,cAAc,EAAE,QAAQ;AAAA,EACxB,cAAc,EAAE,QAAQ;AAAA,EACxB,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAC5B,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAC5C,WAAW,EAAE,OAAO,EAAE,SAAS;AACjC,CAAC;AAED,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAC3C,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,WAAW,EAAE,QAAQ;AACvB,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAEzD,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,SAAS,UAAU,UAAU,EAAE,QAAQ,IAAI,aAAa,IAAI,QAAQ,EAAE,CAAC;AAC7E,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,KAAK,EAAE,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AACzF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,cAAc,UAAU,QAAQ,aAAa;AACnD,QAAM,WAAW,KAAK,MAClB,MAAM,YAAY,QAAQ,KAAK,KAAK,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK,CAAC,IAC3G;AACJ,MAAI,CAAC,UAAU,gBAAgB,KAAK,KAAK;AACvC,QAAI;AACF,YAAM,yCAAyC;AAAA,QAC7C;AAAA,QACA;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK,YAAY;AAAA,QAC3B,gBAAgB,KAAK,SAAS;AAAA,QAC9B,cAAc,OAAO,KAAK;AAAA,QAC1B,mBAAmB;AAAA,MACrB,CAAC;AACD,YAAM,+BAA+B;AAAA,QACnC;AAAA,QACA;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK,YAAY;AAAA,QAC3B,gBAAgB,KAAK,SAAS;AAAA,QAC9B,cAAc,OAAO,KAAK;AAAA,QAC1B,mBAAmB;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,YAAM;AAAA,IACR;AAAA,EACF;AACA,QAAM,MAAM,MAAM,GAAG,QAAQ,SAAS,EAAE,MAAM,OAAO,KAAK,QAAe,UAAU,KAAK,SAAgB,CAAC;AACzG,QAAM,WAAW,MACb;AAAA,IACE,cAAc;AAAA,IACd,cAAc,CAAC,CAAC,IAAI;AAAA,IACpB,UAAU,MAAM,QAAQ,IAAI,YAAY,IAAI,IAAI,eAAe,CAAC;AAAA,IAChE,eAAe,MAAM,QAAQ,IAAI,iBAAiB,IAAI,IAAI,oBAAoB;AAAA,IAC9E,WAAW,IAAI,qBAAqB,OAAO,IAAI,UAAU,YAAY,IAAI;AAAA,EAC3E,IACA,EAAE,cAAc,OAAO,cAAc,OAAO,UAAU,CAAC,GAAG,eAAe,MAAM,WAAW,KAAK;AAEnG,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,OAAO,CAAC,EAAE,IAAI,OAAO,KAAK,QAAQ,GAAG,SAAS,CAAC;AAAA,IAC/C,SAAS;AAAA,IACT,cAAc;AAAA,IACd,gBAAgB,KAAK,SAAS;AAAA,IAC9B,UAAU,KAAK,YAAY;AAAA,IAC3B,OAAO,EAAE,QAAQ,OAAO,KAAK,OAAO;AAAA,IACpC,YAAY;AAAA,EACd,CAAC;AAED,SAAO,aAAa,KAAK,QAAQ;AACnC;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,SAAS,UAAU,UAAU,IAAI;AACvC,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,KAAK,EAAE,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AACzF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,cAAc,UAAU,QAAQ,aAAa;AAEnD,QAAM,WAAW,KAAK,MAClB,MAAM,YAAY,QAAQ,KAAK,KAAK,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK,CAAC,IAC3G;AACJ,QAAM,oBAAoB,CAAC,CAAC,UAAU;AAEtC,MAAI,CAAC,qBAAqB,KAAK,KAAK;AAClC,QAAI;AACF,YAAM,yCAAyC;AAAA,QAC7C;AAAA,QACA;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK,YAAY;AAAA,QAC3B,gBAAgB,KAAK,SAAS;AAAA,QAC9B,cAAc,OAAO,KAAK;AAAA,QAC1B,mBAAmB;AAAA,MACrB,CAAC;AACD,YAAM,+BAA+B;AAAA,QACnC;AAAA,QACA;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK,YAAY;AAAA,QAC3B,gBAAgB,KAAK,SAAS;AAAA,QAC9B,cAAc,OAAO,KAAK;AAAA,QAC1B,mBAAmB;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,oBAAoB,0BAA0B,OAAO,KAAK,QAAQ;AACxE,QAAM,gBAAgB,uBAAuB,OAAO,KAAK,aAAa;AAEtE,MAAI,MAAM,MAAM,GAAG,QAAQ,SAAS,EAAE,MAAM,OAAO,KAAK,QAAe,UAAU,KAAK,SAAgB,CAAC;AAIvG,MAAI,KAAK;AACP,QAAI;AACF,YAAM,uCAAuC,WAAW;AAAA,QACtD,cAAc;AAAA,QACd,YAAY,IAAI;AAAA,QAChB,SAAS,IAAI,aAAa;AAAA,QAC1B,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,YAAM;AAAA,IACR;AAAA,EACF;AACA,QAAM,uBAAuB,MAAM,CAAC,CAAC,IAAI,eAAe;AACxD,QAAM,mBAAmB,MAAM,0BAA0B,IAAI,YAAY,IAAI,CAAC;AAE9E,QAAM,wBAAwB,OAAO,KAAK,gBAAgB;AAE1D,MAAI;AACF,UAAM,uBAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,UAAU,KAAK,YAAY;AAAA,MAC3B,gBAAgB,KAAK,SAAS;AAAA,MAC9B,cAAc;AAAA,MACd,UAAU;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,UAAM;AAAA,EACR;AAEA,QAAM,oBAAoB,oBACtB,oBACA,uBAAuB,iBAAiB;AAE5C,MAAI,wBAAwB;AAE5B,MAAI,CAAC,mBAAmB;AACtB,QAAI,yBAAyB,CAAC,sBAAsB;AAClD,YAAM,UAAU,yDAAyD;AAAA,IAC3E;AACA,QAAI,wBAAwB,0BAA0B,OAAO;AAC3D,8BAAwB;AAAA,IAC1B,OAAO;AACL,8BAAwB;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,eAAe,yBAAyB,kBAAkB,SAAS;AAIzE,MAAI,CAAC,cAAc;AACjB,QAAI,KAAK;AACP,YAAM,cAAc;AACpB,YAAM,gBAAgB,IAAI,CAAC,MAAM,GAAG,OAAO,WAAW,CAAC,GAAG,EAAE,aAAa,KAAK,CAAC;AAAA,IACjF;AAAA,EACF,OAAO;AACL,QAAI,CAAC,KAAK;AACR,YAAM,GAAG,OAAO,SAAS,EAAE,MAAM,OAAO,KAAK,QAAe,UAAU,KAAK,SAAgB,CAAC;AAAA,IAC9F;AACA,UAAM,YAAY;AAClB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,oBAAU,eAAe;AACzB,oBAAU,eAAe;AACzB,oBAAU,oBAAoB;AAC9B,aAAG,QAAQ,SAAS;AAAA,QACtB;AAAA,MACF;AAAA,MACA,EAAE,aAAa,KAAK;AAAA,IACtB;AAAA,EACF;AAGA,QAAM,YAAY,oBAAoB,OAAO,KAAK,MAAM;AACxD,MAAI;AACF,UAAM,QAAQ,UAAU,QAAQ,OAAO;AACvC,QAAI,MAAO,OAAM,MAAM,aAAa,CAAC,aAAa,OAAO,KAAK,MAAM,EAAE,CAAC;AAAA,EACzE,QAAQ;AAAA,EAAC;AAET,SAAO,aAAa,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,WAAW,CAAC,sBAAsB,qBAAqB,mBAAmB,mBAAmB,gBAAgB,KAAK,0BAA0B;AAAA,EAC9I,CAAC;AACH;AAEA,SAAS,uBAAuB,eAAyC;AACvE,MAAI,CAAC,MAAM,QAAQ,aAAa,EAAG,QAAO;AAC1C,SAAO,0BAA0B,aAAa;AAChD;AAEA,SAAS,uBAAuB,UAA8B;AAC5D,SAAO,SAAS,OAAO,CAAC,YAAY,CAAC,0BAA0B,OAAO,CAAC;AACzE;AAEA,SAAS,0BAA0B,SAA0B;AAC3D,MAAI,YAAY,OAAO,YAAY,cAAe,QAAO;AACzD,MAAI,QAAQ,WAAW,mBAAmB,EAAG,QAAO;AACpD,SAAO;AACT;AAEA,SAAS,qBAAqB,WAAqB,WAAqB,UAA6B;AACnG,MAAI,UAAU,WAAW,UAAU,OAAQ,QAAO;AAClD,QAAM,eAAe,IAAI,IAAI,SAAS;AACtC,QAAM,cAAc,IAAI,IAAI,QAAQ;AAEpC,MAAI,aAAa,SAAS,YAAY,MAAM;AAC1C,QAAI,YAAY;AAChB,eAAW,SAAS,cAAc;AAChC,UAAI,CAAC,YAAY,IAAI,KAAK,GAAG;AAC3B,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AACA,QAAI,UAAW,QAAO;AAAA,EACxB;AACA,SAAO;AACT;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,sBAAsB;AAAA,QAC5E,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,mBAAmB;AAAA,QAC1E,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,mBAAmB;AAAA,MACzE;AAAA,IACF;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,4BAA4B;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,mBAAmB;AAAA,QAC1E,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,mBAAmB;AAAA,QACvE,EAAE,QAAQ,KAAK,aAAa,yCAAyC,QAAQ,mBAAmB;AAAA,MAClG;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
4
5
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
5
6
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
6
7
|
import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
8
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from "@open-mercato/ui/backend/injection/recordContext";
|
|
7
9
|
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
8
10
|
import { deleteCrud, updateCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
9
11
|
import { collectCustomFieldValues } from "@open-mercato/ui/backend/utils/customFieldValues";
|
|
@@ -17,6 +19,7 @@ import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-
|
|
|
17
19
|
function EditRolePage({ params }) {
|
|
18
20
|
const id = params?.id;
|
|
19
21
|
const t = useT();
|
|
22
|
+
const pathname = usePathname();
|
|
20
23
|
const [initial, setInitial] = React.useState(null);
|
|
21
24
|
const [loading, setLoading] = React.useState(true);
|
|
22
25
|
const [aclData, setAclData] = React.useState({ isSuperAdmin: false, features: [], organizations: null });
|
|
@@ -156,6 +159,15 @@ function EditRolePage({ params }) {
|
|
|
156
159
|
) : null
|
|
157
160
|
}
|
|
158
161
|
], [aclData, actorIsSuperAdmin, detailFieldIds, id, initial, loading, selectedTenantId, t]);
|
|
162
|
+
useSetCurrentRecordInjectionContext(
|
|
163
|
+
buildRecordInjectionContext({
|
|
164
|
+
resourceKind: "auth.role",
|
|
165
|
+
resourceId: id || null,
|
|
166
|
+
updatedAt: initial?.updatedAt ?? null,
|
|
167
|
+
data: initial,
|
|
168
|
+
path: pathname
|
|
169
|
+
})
|
|
170
|
+
);
|
|
159
171
|
if (!id) return null;
|
|
160
172
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
161
173
|
CrudForm,
|