@open-mercato/core 0.6.6-develop.6256.1.9fc16aedc4 → 0.6.6-develop.6286.1.d985c5c374
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 +2 -2
- package/dist/generated/entities/carrier_shipment_idempotency_key/index.js +19 -0
- package/dist/generated/entities/carrier_shipment_idempotency_key/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +10 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/lib/productResolution.js +45 -0
- package/dist/modules/catalog/lib/productResolution.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +1 -1
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +2 -2
- package/dist/modules/currencies/api/fetch-configs/route.js +34 -0
- package/dist/modules/currencies/api/fetch-configs/route.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +17 -15
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +176 -161
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +75 -59
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +17 -10
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +6 -4
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js +5 -2
- package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +19 -21
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +1 -1
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +9 -45
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +11 -17
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +12 -10
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/lib/id-mapping.js +46 -1
- package/dist/modules/data_sync/lib/id-mapping.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/syncRunStatus.js +30 -0
- package/dist/modules/data_sync/lib/syncRunStatus.js.map +7 -0
- package/dist/modules/entities/api/records.js +6 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/feature_toggles/api/global/route.js +17 -15
- package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/openapi.js +2 -2
- package/dist/modules/feature_toggles/api/openapi.js.map +2 -2
- package/dist/modules/feature_toggles/commands/global.js +14 -0
- package/dist/modules/feature_toggles/commands/global.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +6 -0
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/archive/route.js +25 -7
- package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/read/route.js +24 -6
- package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +13 -1
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/openapi.js +3 -1
- package/dist/modules/messages/api/openapi.js.map +2 -2
- package/dist/modules/messages/commands/conversation.js +77 -0
- package/dist/modules/messages/commands/conversation.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +16 -4
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +3 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +29 -4
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +10 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +13 -11
- package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +5 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +5 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/refund/route.js +5 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +52 -25
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/payment_gateways/lib/status-machine.js +13 -0
- package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +8 -2
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/perspectives/data/entities.js +33 -3
- package/dist/modules/perspectives/data/entities.js.map +2 -2
- package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js +63 -0
- package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js.map +7 -0
- package/dist/modules/perspectives/services/perspectiveService.js +17 -0
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/planner/commands/availability-weekly.js +33 -1
- package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +14 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/progress/api/active/route.js +1 -1
- package/dist/modules/progress/api/active/route.js.map +2 -2
- package/dist/modules/progress/lib/progressService.js.map +1 -1
- package/dist/modules/progress/lib/progressServiceImpl.js +33 -8
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/query_index/api/status.js +19 -24
- package/dist/modules/query_index/api/status.js.map +3 -3
- package/dist/modules/query_index/lib/coverage.js +35 -0
- package/dist/modules/query_index/lib/coverage.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +11 -6
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/resources/api/resource-types.js +5 -37
- package/dist/modules/resources/api/resource-types.js.map +2 -2
- package/dist/modules/resources/api/resourceTypeCounts.js +35 -0
- package/dist/modules/resources/api/resourceTypeCounts.js.map +7 -0
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +1 -1
- package/dist/modules/resources/backend/resources/resource-types/page.js +2 -1
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnDialog.js +4 -1
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +17 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/di.js +4 -0
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/sales/services/salesOrderService.js +26 -0
- package/dist/modules/sales/services/salesOrderService.js.map +7 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js +8 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/data/entities.js +38 -0
- package/dist/modules/shipping_carriers/data/entities.js.map +2 -2
- package/dist/modules/shipping_carriers/data/validators.js +2 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-idempotency.js +76 -0
- package/dist/modules/shipping_carriers/lib/shipment-idempotency.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js +2 -1
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +20 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +82 -43
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js +14 -0
- package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +15 -6
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +9 -18
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/generated/entities/carrier_shipment_idempotency_key/index.ts +8 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +10 -0
- package/package.json +9 -8
- package/src/modules/catalog/lib/productResolution.ts +75 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +1 -1
- package/src/modules/currencies/api/fetch-configs/route.ts +36 -0
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +20 -17
- package/src/modules/currencies/i18n/de.json +2 -1
- package/src/modules/currencies/i18n/en.json +2 -1
- package/src/modules/currencies/i18n/es.json +2 -1
- package/src/modules/currencies/i18n/pl.json +2 -1
- package/src/modules/customers/api/companies/[id]/route.ts +214 -189
- package/src/modules/customers/api/people/[id]/route.ts +106 -80
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +19 -12
- package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +8 -6
- package/src/modules/customers/components/detail/AiActionChips.tsx +6 -3
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +16 -18
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +11 -58
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +13 -20
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +10 -8
- package/src/modules/data_sync/lib/id-mapping.ts +58 -1
- package/src/modules/data_sync/lib/sync-engine.ts +2 -2
- package/src/modules/data_sync/lib/syncRunStatus.ts +39 -0
- package/src/modules/entities/api/records.ts +14 -0
- package/src/modules/feature_toggles/api/global/route.ts +16 -14
- package/src/modules/feature_toggles/api/openapi.ts +2 -2
- package/src/modules/feature_toggles/commands/global.ts +15 -0
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +7 -0
- package/src/modules/messages/api/[id]/conversation/archive/route.ts +29 -6
- package/src/modules/messages/api/[id]/conversation/read/route.ts +29 -6
- package/src/modules/messages/api/[id]/route.ts +15 -0
- package/src/modules/messages/api/openapi.ts +2 -0
- package/src/modules/messages/commands/conversation.ts +87 -0
- package/src/modules/messages/components/MessageDetailPageClient.tsx +16 -4
- package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +2 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +39 -4
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +15 -1
- package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +29 -14
- package/src/modules/messages/components/message-detail/types.ts +2 -0
- package/src/modules/messages/i18n/de.json +4 -0
- package/src/modules/messages/i18n/en.json +4 -0
- package/src/modules/messages/i18n/es.json +4 -0
- package/src/modules/messages/i18n/pl.json +4 -0
- package/src/modules/payment_gateways/api/cancel/route.ts +5 -0
- package/src/modules/payment_gateways/api/capture/route.ts +5 -0
- package/src/modules/payment_gateways/api/refund/route.ts +5 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +58 -25
- package/src/modules/payment_gateways/lib/status-machine.ts +16 -0
- package/src/modules/perspectives/api/[tableId]/route.ts +8 -2
- package/src/modules/perspectives/data/entities.ts +43 -3
- package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +69 -25
- package/src/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.ts +65 -0
- package/src/modules/perspectives/services/perspectiveService.ts +17 -0
- package/src/modules/planner/commands/availability-weekly.ts +48 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +31 -10
- package/src/modules/progress/api/active/route.ts +1 -1
- package/src/modules/progress/lib/progressService.ts +2 -2
- package/src/modules/progress/lib/progressServiceImpl.ts +33 -8
- package/src/modules/query_index/api/status.ts +28 -32
- package/src/modules/query_index/lib/coverage.ts +63 -0
- package/src/modules/query_index/lib/engine.ts +37 -24
- package/src/modules/resources/api/resource-types.ts +4 -44
- package/src/modules/resources/api/resourceTypeCounts.ts +74 -0
- package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
- package/src/modules/resources/backend/resources/resource-types/page.tsx +1 -0
- package/src/modules/sales/components/documents/ReturnDialog.tsx +4 -1
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +20 -2
- package/src/modules/sales/di.ts +6 -0
- package/src/modules/sales/services/salesOrderService.ts +38 -0
- package/src/modules/shipping_carriers/api/shipments/route.ts +8 -0
- package/src/modules/shipping_carriers/data/entities.ts +33 -0
- package/src/modules/shipping_carriers/data/validators.ts +1 -0
- package/src/modules/shipping_carriers/i18n/de.json +1 -0
- package/src/modules/shipping_carriers/i18n/en.json +1 -0
- package/src/modules/shipping_carriers/i18n/es.json +1 -0
- package/src/modules/shipping_carriers/i18n/pl.json +1 -0
- package/src/modules/shipping_carriers/lib/shipment-idempotency.ts +119 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts +3 -1
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +24 -2
- package/src/modules/shipping_carriers/lib/shipping-service.ts +87 -43
- package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +197 -0
- package/src/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.ts +14 -0
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +15 -7
- package/src/modules/translations/components/TranslationManager.tsx +17 -24
- package/src/modules/translations/i18n/de.json +1 -0
- package/src/modules/translations/i18n/en.json +1 -0
- package/src/modules/translations/i18n/es.json +1 -0
- package/src/modules/translations/i18n/pl.json +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { CatalogProduct } from "../data/entities.js";
|
|
3
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
+
function normalizeLookupValue(value) {
|
|
5
|
+
return typeof value === "string" ? value.trim() : "";
|
|
6
|
+
}
|
|
7
|
+
async function resolveProductBySkuOrExternalId(em, lookup, scope) {
|
|
8
|
+
const externalId = normalizeLookupValue(lookup.externalId);
|
|
9
|
+
const sku = normalizeLookupValue(lookup.sku);
|
|
10
|
+
if (!externalId && !sku) return null;
|
|
11
|
+
if (externalId && UUID_REGEX.test(externalId)) {
|
|
12
|
+
const byExternalId = await findOneWithDecryption(
|
|
13
|
+
em,
|
|
14
|
+
CatalogProduct,
|
|
15
|
+
{
|
|
16
|
+
id: externalId,
|
|
17
|
+
organizationId: scope.organizationId,
|
|
18
|
+
tenantId: scope.tenantId,
|
|
19
|
+
deletedAt: null
|
|
20
|
+
},
|
|
21
|
+
void 0,
|
|
22
|
+
scope
|
|
23
|
+
);
|
|
24
|
+
if (byExternalId) return byExternalId;
|
|
25
|
+
}
|
|
26
|
+
if (sku) {
|
|
27
|
+
return findOneWithDecryption(
|
|
28
|
+
em,
|
|
29
|
+
CatalogProduct,
|
|
30
|
+
{
|
|
31
|
+
sku,
|
|
32
|
+
organizationId: scope.organizationId,
|
|
33
|
+
tenantId: scope.tenantId,
|
|
34
|
+
deletedAt: null
|
|
35
|
+
},
|
|
36
|
+
void 0,
|
|
37
|
+
scope
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
resolveProductBySkuOrExternalId
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=productResolution.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/catalog/lib/productResolution.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CatalogProduct } from '../data/entities'\n\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nexport type ResolveCatalogProductLookup = {\n sku?: string | null\n externalId?: string | null\n}\n\nexport type ResolveCatalogProductScope = {\n organizationId: string\n tenantId: string\n}\n\nfunction normalizeLookupValue(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * Public seam for cross-module callers (e.g. the `sync_magento` order mapper) to look up a\n * {@link CatalogProduct} without reaching into the catalog ORM directly. Uses\n * `findOneWithDecryption` so encrypted product fields are respected, and always scopes the query by\n * `organizationId` + `tenantId` plus `deletedAt: null`.\n *\n * Catalog does not store a foreign external id on the product \u2014 those mappings live per-integration\n * in `SyncExternalIdMapping` (data_sync) and resolve to the local OM product id. `externalId` here is\n * therefore matched against the OM product `id` (the value an external\u2192local mapping yields) and only\n * when it is a valid UUID, so a stray non-UUID value falls through to the SKU lookup instead of\n * raising a Postgres uuid-cast error. When both identifiers are supplied the `externalId` match takes\n * precedence, mirroring the mapping-first/SKU-fallback order used by the existing sync importers.\n */\nexport async function resolveProductBySkuOrExternalId(\n em: EntityManager,\n lookup: ResolveCatalogProductLookup,\n scope: ResolveCatalogProductScope,\n): Promise<CatalogProduct | null> {\n const externalId = normalizeLookupValue(lookup.externalId)\n const sku = normalizeLookupValue(lookup.sku)\n if (!externalId && !sku) return null\n\n if (externalId && UUID_REGEX.test(externalId)) {\n const byExternalId = await findOneWithDecryption(\n em,\n CatalogProduct,\n {\n id: externalId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (byExternalId) return byExternalId\n }\n\n if (sku) {\n return findOneWithDecryption(\n em,\n CatalogProduct,\n {\n sku,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n }\n\n return null\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAE/B,MAAM,aAAa;AAYnB,SAAS,qBAAqB,OAA0C;AACtE,SAAO,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;AACpD;AAeA,eAAsB,gCACpB,IACA,QACA,OACgC;AAChC,QAAM,aAAa,qBAAqB,OAAO,UAAU;AACzD,QAAM,MAAM,qBAAqB,OAAO,GAAG;AAC3C,MAAI,CAAC,cAAc,CAAC,IAAK,QAAO;AAEhC,MAAI,cAAc,WAAW,KAAK,UAAU,GAAG;AAC7C,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,aAAc,QAAO;AAAA,EAC3B;AAEA,MAAI,KAAK;AACP,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,QACE;AAAA,QACA,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -101,7 +101,7 @@ async function handle(job, ctx) {
|
|
|
101
101
|
let firstPage = true;
|
|
102
102
|
const MAX_PAGES = 100;
|
|
103
103
|
for (let pageIndex = 0; pageIndex < MAX_PAGES; pageIndex += 1) {
|
|
104
|
-
if (await progressService.isCancellationRequested(progressJobId, scope.tenantId)) {
|
|
104
|
+
if (await progressService.isCancellationRequested(progressJobId, scope.tenantId, scope.organizationId)) {
|
|
105
105
|
await progressService.markCancelled(progressJobId, progressContext);
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/communication_channels/workers/channel-import-history.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CommunicationChannel } from '../data/entities'\nimport {\n COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID,\n type IngestInboundMessageInput,\n} from '../commands/ingest-inbound-message'\nimport { COMMUNICATION_CHANNELS_QUEUES } from '../lib/queue'\nimport { refreshCredentialsIfNeeded } from '../lib/credential-refresh'\nimport { classifyOutboundError } from '../lib/error-classification'\nimport type { ChannelAdapterRegistry } from '../lib/registry'\nimport type {\n ChannelImportHistoryJobPayload,\n} from '../commands/queue-import-history'\nimport type {\n ProgressService,\n ProgressServiceContext,\n} from '../../progress/lib/progressService'\n\n/**\n * Spec B \u00A7 Phase B6 \u2014 operator-triggered backlog import worker.\n *\n * Distinct from `poll-channel` (which runs every scheduler tick and ingests\n * *new* mail since the channel cursor). This worker reaches backward in time\n * by calling `adapter.importHistory` with explicit `sinceDays` / `contactEmails`\n * filters, paginating until the adapter signals `hasMore: false` or the\n * `maxMessages` cap is reached.\n *\n * Runs with `concurrency: 1` to avoid hammering the provider with multiple\n * historical sweeps in parallel; per-channel concurrency is additionally\n * enforced at enqueue time by `queueImportHistory`.\n */\nexport const metadata: WorkerMeta = {\n queue: COMMUNICATION_CHANNELS_QUEUES.importHistory,\n id: 'communication_channels:channel-import-history',\n concurrency: 1,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\ntype CredentialsServiceLike = {\n resolve: (\n integrationId: string,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<Record<string, unknown> | null>\n save?: (\n integrationId: string,\n credentials: Record<string, unknown>,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<void>\n}\n\nexport default async function handle(\n job: QueuedJob<ChannelImportHistoryJobPayload>,\n ctx: HandlerContext,\n): Promise<void> {\n const payload = job.payload\n const { progressJobId, channelId, sinceDays, contactEmails, maxMessages, scope } = payload\n\n const progressService = ctx.resolve<ProgressService>('progressService')\n const progressContext: ProgressServiceContext = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n\n const em = (ctx.resolve('em') as EntityManager).fork()\n const adapterRegistry = ctx.resolve<ChannelAdapterRegistry>('channelAdapterRegistry')\n\n try {\n await progressService.startJob(progressJobId, progressContext)\n\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id: channelId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!channel) {\n await progressService.failJob(\n progressJobId,\n { errorMessage: 'Channel not found' },\n progressContext,\n )\n return\n }\n if (!channel.isActive || channel.status !== 'connected') {\n await progressService.failJob(\n progressJobId,\n { errorMessage: `Channel is not connected (status=${channel.status})` },\n progressContext,\n )\n return\n }\n\n const adapter = adapterRegistry?.get(channel.providerKey)\n if (!adapter || typeof adapter.importHistory !== 'function') {\n await progressService.failJob(\n progressJobId,\n {\n errorMessage: `Provider \"${channel.providerKey}\" does not support history import`,\n },\n progressContext,\n )\n return\n }\n\n // Resolve + refresh credentials. Same flow as poll-channel.\n let credentialsService: CredentialsServiceLike | null = null\n try {\n credentialsService = ctx.resolve<CredentialsServiceLike>('integrationCredentialsService')\n } catch {\n credentialsService = null\n }\n const credentialsScope = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: channel.userId ?? null,\n }\n let credentials: Record<string, unknown> = {}\n if (channel.credentialsRef && credentialsService) {\n try {\n credentials =\n (await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope)) ?? {}\n } catch {\n credentials = {}\n }\n }\n const refreshed = await refreshCredentialsIfNeeded(\n { adapter, channelId: channel.id, credentials, scope: credentialsScope },\n { credentialsService },\n )\n credentials = refreshed.credentials\n\n const commandBus = ctx.resolve<CommandBus>('commandBus')\n const containerProxy = { resolve: ctx.resolve.bind(ctx) }\n const commandCtx = {\n container: containerProxy as never,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n\n let cursor: string | undefined\n let processedCount = 0\n let totalCount = maxMessages\n let firstPage = true\n\n // Bounded loop guards against a misbehaving adapter (infinite hasMore).\n // 100 pages * HARD_CAP (default 200) = 20k messages \u2014 far above the 5k cap\n // enforced by the schema, so this only trips on adapter bugs.\n const MAX_PAGES = 100\n for (let pageIndex = 0; pageIndex < MAX_PAGES; pageIndex += 1) {\n if (await progressService.isCancellationRequested(progressJobId, scope.tenantId)) {\n await progressService.markCancelled(progressJobId, progressContext)\n return\n }\n\n const page = await adapter.importHistory!({\n credentials,\n scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },\n sinceDays,\n contactEmails,\n maxMessages,\n cursor,\n })\n\n if (firstPage && typeof page.totalCandidates === 'number') {\n totalCount = Math.min(maxMessages, page.totalCandidates)\n await progressService.updateProgress(\n progressJobId,\n { totalCount, processedCount: 0 },\n progressContext,\n )\n firstPage = false\n }\n\n for (const message of page.messages) {\n if (processedCount >= maxMessages) break\n try {\n const input: IngestInboundMessageInput = {\n channelId: channel.id,\n providerKey: channel.providerKey,\n channelType: channel.channelType,\n scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },\n message,\n }\n await commandBus.execute(COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID, {\n input,\n ctx: commandCtx as never,\n })\n } catch (err) {\n // Import-history is best-effort per-message. Same classification\n // logic as poll-channel: a transient failure aborts the whole job\n // (the operator can retry), permanent failures are logged and\n // skipped (the bad message would loop forever otherwise).\n const classification = classifyOutboundError(err)\n if (classification.transient) {\n throw err\n }\n console.warn(\n `[communication_channels:channel-import-history] permanent ingest failure on channel ${channel.id}; skipping message ${message.externalMessageId}. Reason: ${classification.message}`,\n )\n }\n processedCount += 1\n }\n\n await progressService.updateProgress(\n progressJobId,\n { processedCount, totalCount },\n progressContext,\n )\n\n if (processedCount >= maxMessages) break\n if (!page.hasMore || !page.nextCursor) break\n cursor = page.nextCursor\n }\n\n await progressService.completeJob(\n progressJobId,\n { resultSummary: { importedCount: processedCount, channelId } },\n progressContext,\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Import history job failed'\n const stack = err instanceof Error ? err.stack : undefined\n try {\n await progressService.failJob(\n progressJobId,\n { errorMessage: message.slice(0, 2000), errorStack: stack?.slice(0, 10000) },\n progressContext,\n )\n } catch (failErr) {\n console.error(\n `[communication_channels:channel-import-history] failed to mark progress job ${progressJobId} as failed: ${\n failErr instanceof Error ? failErr.message : String(failErr)\n }`,\n )\n }\n throw err\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CommunicationChannel } from '../data/entities'\nimport {\n COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID,\n type IngestInboundMessageInput,\n} from '../commands/ingest-inbound-message'\nimport { COMMUNICATION_CHANNELS_QUEUES } from '../lib/queue'\nimport { refreshCredentialsIfNeeded } from '../lib/credential-refresh'\nimport { classifyOutboundError } from '../lib/error-classification'\nimport type { ChannelAdapterRegistry } from '../lib/registry'\nimport type {\n ChannelImportHistoryJobPayload,\n} from '../commands/queue-import-history'\nimport type {\n ProgressService,\n ProgressServiceContext,\n} from '../../progress/lib/progressService'\n\n/**\n * Spec B \u00A7 Phase B6 \u2014 operator-triggered backlog import worker.\n *\n * Distinct from `poll-channel` (which runs every scheduler tick and ingests\n * *new* mail since the channel cursor). This worker reaches backward in time\n * by calling `adapter.importHistory` with explicit `sinceDays` / `contactEmails`\n * filters, paginating until the adapter signals `hasMore: false` or the\n * `maxMessages` cap is reached.\n *\n * Runs with `concurrency: 1` to avoid hammering the provider with multiple\n * historical sweeps in parallel; per-channel concurrency is additionally\n * enforced at enqueue time by `queueImportHistory`.\n */\nexport const metadata: WorkerMeta = {\n queue: COMMUNICATION_CHANNELS_QUEUES.importHistory,\n id: 'communication_channels:channel-import-history',\n concurrency: 1,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\ntype CredentialsServiceLike = {\n resolve: (\n integrationId: string,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<Record<string, unknown> | null>\n save?: (\n integrationId: string,\n credentials: Record<string, unknown>,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<void>\n}\n\nexport default async function handle(\n job: QueuedJob<ChannelImportHistoryJobPayload>,\n ctx: HandlerContext,\n): Promise<void> {\n const payload = job.payload\n const { progressJobId, channelId, sinceDays, contactEmails, maxMessages, scope } = payload\n\n const progressService = ctx.resolve<ProgressService>('progressService')\n const progressContext: ProgressServiceContext = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n\n const em = (ctx.resolve('em') as EntityManager).fork()\n const adapterRegistry = ctx.resolve<ChannelAdapterRegistry>('channelAdapterRegistry')\n\n try {\n await progressService.startJob(progressJobId, progressContext)\n\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n {\n id: channelId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!channel) {\n await progressService.failJob(\n progressJobId,\n { errorMessage: 'Channel not found' },\n progressContext,\n )\n return\n }\n if (!channel.isActive || channel.status !== 'connected') {\n await progressService.failJob(\n progressJobId,\n { errorMessage: `Channel is not connected (status=${channel.status})` },\n progressContext,\n )\n return\n }\n\n const adapter = adapterRegistry?.get(channel.providerKey)\n if (!adapter || typeof adapter.importHistory !== 'function') {\n await progressService.failJob(\n progressJobId,\n {\n errorMessage: `Provider \"${channel.providerKey}\" does not support history import`,\n },\n progressContext,\n )\n return\n }\n\n // Resolve + refresh credentials. Same flow as poll-channel.\n let credentialsService: CredentialsServiceLike | null = null\n try {\n credentialsService = ctx.resolve<CredentialsServiceLike>('integrationCredentialsService')\n } catch {\n credentialsService = null\n }\n const credentialsScope = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: channel.userId ?? null,\n }\n let credentials: Record<string, unknown> = {}\n if (channel.credentialsRef && credentialsService) {\n try {\n credentials =\n (await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope)) ?? {}\n } catch {\n credentials = {}\n }\n }\n const refreshed = await refreshCredentialsIfNeeded(\n { adapter, channelId: channel.id, credentials, scope: credentialsScope },\n { credentialsService },\n )\n credentials = refreshed.credentials\n\n const commandBus = ctx.resolve<CommandBus>('commandBus')\n const containerProxy = { resolve: ctx.resolve.bind(ctx) }\n const commandCtx = {\n container: containerProxy as never,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n\n let cursor: string | undefined\n let processedCount = 0\n let totalCount = maxMessages\n let firstPage = true\n\n // Bounded loop guards against a misbehaving adapter (infinite hasMore).\n // 100 pages * HARD_CAP (default 200) = 20k messages \u2014 far above the 5k cap\n // enforced by the schema, so this only trips on adapter bugs.\n const MAX_PAGES = 100\n for (let pageIndex = 0; pageIndex < MAX_PAGES; pageIndex += 1) {\n if (await progressService.isCancellationRequested(progressJobId, scope.tenantId, scope.organizationId)) {\n await progressService.markCancelled(progressJobId, progressContext)\n return\n }\n\n const page = await adapter.importHistory!({\n credentials,\n scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },\n sinceDays,\n contactEmails,\n maxMessages,\n cursor,\n })\n\n if (firstPage && typeof page.totalCandidates === 'number') {\n totalCount = Math.min(maxMessages, page.totalCandidates)\n await progressService.updateProgress(\n progressJobId,\n { totalCount, processedCount: 0 },\n progressContext,\n )\n firstPage = false\n }\n\n for (const message of page.messages) {\n if (processedCount >= maxMessages) break\n try {\n const input: IngestInboundMessageInput = {\n channelId: channel.id,\n providerKey: channel.providerKey,\n channelType: channel.channelType,\n scope: { tenantId: scope.tenantId, organizationId: scope.organizationId },\n message,\n }\n await commandBus.execute(COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID, {\n input,\n ctx: commandCtx as never,\n })\n } catch (err) {\n // Import-history is best-effort per-message. Same classification\n // logic as poll-channel: a transient failure aborts the whole job\n // (the operator can retry), permanent failures are logged and\n // skipped (the bad message would loop forever otherwise).\n const classification = classifyOutboundError(err)\n if (classification.transient) {\n throw err\n }\n console.warn(\n `[communication_channels:channel-import-history] permanent ingest failure on channel ${channel.id}; skipping message ${message.externalMessageId}. Reason: ${classification.message}`,\n )\n }\n processedCount += 1\n }\n\n await progressService.updateProgress(\n progressJobId,\n { processedCount, totalCount },\n progressContext,\n )\n\n if (processedCount >= maxMessages) break\n if (!page.hasMore || !page.nextCursor) break\n cursor = page.nextCursor\n }\n\n await progressService.completeJob(\n progressJobId,\n { resultSummary: { importedCount: processedCount, channelId } },\n progressContext,\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Import history job failed'\n const stack = err instanceof Error ? err.stack : undefined\n try {\n await progressService.failJob(\n progressJobId,\n { errorMessage: message.slice(0, 2000), errorStack: stack?.slice(0, 10000) },\n progressContext,\n )\n } catch (failErr) {\n console.error(\n `[communication_channels:channel-import-history] failed to mark progress job ${progressJobId} as failed: ${\n failErr instanceof Error ? failErr.message : String(failErr)\n }`,\n )\n }\n throw err\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
|
+
import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
5
7
|
import { CurrencyFetchConfig } from "../../data/entities.js";
|
|
6
8
|
import {
|
|
7
9
|
createFetchConfig,
|
|
@@ -85,6 +87,19 @@ async function PUT(req) {
|
|
|
85
87
|
if (!id) {
|
|
86
88
|
return NextResponse.json({ error: "ID required" }, { status: 400 });
|
|
87
89
|
}
|
|
90
|
+
const existing = await em.findOne(CurrencyFetchConfig, {
|
|
91
|
+
id,
|
|
92
|
+
tenantId: auth.tenantId,
|
|
93
|
+
organizationId: auth.orgId
|
|
94
|
+
});
|
|
95
|
+
if (existing) {
|
|
96
|
+
enforceCommandOptimisticLock({
|
|
97
|
+
resourceKind: "currencies.currency_fetch_config",
|
|
98
|
+
resourceId: existing.id,
|
|
99
|
+
current: existing.updatedAt ?? null,
|
|
100
|
+
request: req
|
|
101
|
+
});
|
|
102
|
+
}
|
|
88
103
|
const config = await updateFetchConfig(em, id, data, {
|
|
89
104
|
tenantId: auth.tenantId,
|
|
90
105
|
organizationId: auth.orgId,
|
|
@@ -92,6 +107,9 @@ async function PUT(req) {
|
|
|
92
107
|
});
|
|
93
108
|
return NextResponse.json({ config });
|
|
94
109
|
} catch (err) {
|
|
110
|
+
if (isCrudHttpError(err)) {
|
|
111
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
112
|
+
}
|
|
95
113
|
return NextResponse.json({ error: err.message }, { status: 400 });
|
|
96
114
|
} finally {
|
|
97
115
|
await container.dispose?.();
|
|
@@ -110,6 +128,19 @@ async function DELETE(req) {
|
|
|
110
128
|
if (!id) {
|
|
111
129
|
return NextResponse.json({ error: "ID required" }, { status: 400 });
|
|
112
130
|
}
|
|
131
|
+
const existing = await em.findOne(CurrencyFetchConfig, {
|
|
132
|
+
id,
|
|
133
|
+
tenantId: auth.tenantId,
|
|
134
|
+
organizationId: auth.orgId
|
|
135
|
+
});
|
|
136
|
+
if (existing) {
|
|
137
|
+
enforceCommandOptimisticLock({
|
|
138
|
+
resourceKind: "currencies.currency_fetch_config",
|
|
139
|
+
resourceId: existing.id,
|
|
140
|
+
current: existing.updatedAt ?? null,
|
|
141
|
+
request: req
|
|
142
|
+
});
|
|
143
|
+
}
|
|
113
144
|
await deleteFetchConfig(em, id, {
|
|
114
145
|
tenantId: auth.tenantId,
|
|
115
146
|
organizationId: auth.orgId,
|
|
@@ -117,6 +148,9 @@ async function DELETE(req) {
|
|
|
117
148
|
});
|
|
118
149
|
return NextResponse.json({ success: true });
|
|
119
150
|
} catch (err) {
|
|
151
|
+
if (isCrudHttpError(err)) {
|
|
152
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
153
|
+
}
|
|
120
154
|
return NextResponse.json({ error: err.message }, { status: 400 });
|
|
121
155
|
} finally {
|
|
122
156
|
await container.dispose?.();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/currencies/api/fetch-configs/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextRequest, NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CurrencyFetchConfig } from '../../data/entities'\nimport {\n createFetchConfig,\n updateFetchConfig,\n deleteFetchConfig,\n} from '../../commands/fetch-configs'\nimport { currencyFetchConfigCreateSchema, currencyFetchConfigUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['currencies.fetch.view'] },\n POST: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n}\n\nexport async function GET(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n const findFilter: Record<string, unknown> = {\n tenantId: auth.tenantId,\n }\n if (auth.orgId) {\n findFilter.organizationId = auth.orgId\n }\n\n const configs = await em.find(\n CurrencyFetchConfig,\n findFilter,\n {\n orderBy: { provider: 'ASC' },\n }\n )\n\n return NextResponse.json({ configs })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function POST(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const config = await createFetchConfig(em, body, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config }, { status: 201 })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function PUT(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const { id, ...data } = body\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n const config = await updateFetchConfig(em, id, data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function DELETE(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const { searchParams } = new URL(req.url)\n const id = searchParams.get('id')\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n await deleteFetchConfig(em, id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ success: true })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nconst fetchConfigItemSchema = z.object({\n id: z.string().uuid(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n provider: z.string(),\n isEnabled: z.boolean(),\n syncTime: z.string().nullable(),\n lastSyncAt: z.string().nullable().optional(),\n lastSyncStatus: z.string().nullable().optional(),\n lastSyncMessage: z.string().nullable().optional(),\n lastSyncCount: z.number().nullable().optional(),\n config: z.record(z.string(), z.unknown()).nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage currency fetch configurations',\n description: 'Endpoints for managing currency rate fetch configurations from external providers.',\n methods: {\n GET: {\n operationId: 'listCurrencyFetchConfigs',\n summary: 'List currency fetch configurations',\n description: 'Returns all currency fetch configurations scoped to the authenticated organization.',\n responses: [\n {\n status: 200,\n description: 'A list of currency fetch configurations',\n schema: z.object({\n configs: z.array(fetchConfigItemSchema),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n operationId: 'createCurrencyFetchConfig',\n summary: 'Create currency fetch configuration',\n description: 'Creates a new currency fetch configuration.',\n requestBody: {\n schema: currencyFetchConfigCreateSchema,\n contentType: 'application/json',\n },\n responses: [\n {\n status: 201,\n description: 'Currency fetch configuration created successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n operationId: 'updateCurrencyFetchConfig',\n summary: 'Update currency fetch configuration',\n description: 'Updates an existing currency fetch configuration by id.',\n requestBody: {\n schema: currencyFetchConfigUpdateSchema.extend({\n id: z.string().uuid(),\n }),\n contentType: 'application/json',\n },\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration updated successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n DELETE: {\n operationId: 'deleteCurrencyFetchConfig',\n summary: 'Delete currency fetch configuration',\n description: 'Deletes a currency fetch configuration by id.',\n query: z.object({\n id: z.string().uuid().describe('Currency fetch configuration identifier to delete'),\n }),\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration deleted successfully',\n schema: z.object({ success: z.literal(true) }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextRequest, NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { CurrencyFetchConfig } from '../../data/entities'\nimport {\n createFetchConfig,\n updateFetchConfig,\n deleteFetchConfig,\n} from '../../commands/fetch-configs'\nimport { currencyFetchConfigCreateSchema, currencyFetchConfigUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['currencies.fetch.view'] },\n POST: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['currencies.fetch.manage'] },\n}\n\nexport async function GET(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n const findFilter: Record<string, unknown> = {\n tenantId: auth.tenantId,\n }\n if (auth.orgId) {\n findFilter.organizationId = auth.orgId\n }\n\n const configs = await em.find(\n CurrencyFetchConfig,\n findFilter,\n {\n orderBy: { provider: 'ASC' },\n }\n )\n\n return NextResponse.json({ configs })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function POST(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const config = await createFetchConfig(em, body, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config }, { status: 201 })\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function PUT(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n\n let body\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const { id, ...data } = body\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n const existing = await em.findOne(CurrencyFetchConfig, {\n id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (existing) {\n enforceCommandOptimisticLock({\n resourceKind: 'currencies.currency_fetch_config',\n resourceId: existing.id,\n current: existing.updatedAt ?? null,\n request: req,\n })\n }\n\n const config = await updateFetchConfig(em, id, data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ config })\n } catch (err: any) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nexport async function DELETE(req: NextRequest) {\n const container = await createRequestContainer()\n\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const { searchParams } = new URL(req.url)\n const id = searchParams.get('id')\n\n if (!id) {\n return NextResponse.json({ error: 'ID required' }, { status: 400 })\n }\n\n const existing = await em.findOne(CurrencyFetchConfig, {\n id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (existing) {\n enforceCommandOptimisticLock({\n resourceKind: 'currencies.currency_fetch_config',\n resourceId: existing.id,\n current: existing.updatedAt ?? null,\n request: req,\n })\n }\n\n await deleteFetchConfig(em, id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ success: true })\n } catch (err: any) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n return NextResponse.json({ error: err.message }, { status: 400 })\n } finally {\n await (container as any).dispose?.()\n }\n}\n\nconst fetchConfigItemSchema = z.object({\n id: z.string().uuid(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n provider: z.string(),\n isEnabled: z.boolean(),\n syncTime: z.string().nullable(),\n lastSyncAt: z.string().nullable().optional(),\n lastSyncStatus: z.string().nullable().optional(),\n lastSyncMessage: z.string().nullable().optional(),\n lastSyncCount: z.number().nullable().optional(),\n config: z.record(z.string(), z.unknown()).nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage currency fetch configurations',\n description: 'Endpoints for managing currency rate fetch configurations from external providers.',\n methods: {\n GET: {\n operationId: 'listCurrencyFetchConfigs',\n summary: 'List currency fetch configurations',\n description: 'Returns all currency fetch configurations scoped to the authenticated organization.',\n responses: [\n {\n status: 200,\n description: 'A list of currency fetch configurations',\n schema: z.object({\n configs: z.array(fetchConfigItemSchema),\n }),\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n operationId: 'createCurrencyFetchConfig',\n summary: 'Create currency fetch configuration',\n description: 'Creates a new currency fetch configuration.',\n requestBody: {\n schema: currencyFetchConfigCreateSchema,\n contentType: 'application/json',\n },\n responses: [\n {\n status: 201,\n description: 'Currency fetch configuration created successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n PUT: {\n operationId: 'updateCurrencyFetchConfig',\n summary: 'Update currency fetch configuration',\n description: 'Updates an existing currency fetch configuration by id.',\n requestBody: {\n schema: currencyFetchConfigUpdateSchema.extend({\n id: z.string().uuid(),\n }),\n contentType: 'application/json',\n },\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration updated successfully',\n schema: z.object({\n config: fetchConfigItemSchema,\n }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n DELETE: {\n operationId: 'deleteCurrencyFetchConfig',\n summary: 'Delete currency fetch configuration',\n description: 'Deletes a currency fetch configuration by id.',\n query: z.object({\n id: z.string().uuid().describe('Currency fetch configuration identifier to delete'),\n }),\n responses: [\n {\n status: 200,\n description: 'Currency fetch configuration deleted successfully',\n schema: z.object({ success: z.literal(true) }),\n },\n ],\n errors: [\n { status: 400, description: 'Bad request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,7 +8,15 @@ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuarde
|
|
|
8
8
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
9
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
10
10
|
import { Switch } from "@open-mercato/ui/primitives/switch";
|
|
11
|
+
import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
|
|
12
|
+
import { Alert } from "@open-mercato/ui/primitives/alert";
|
|
13
|
+
import { TimeInput } from "@open-mercato/ui/backend/inputs/TimeInput";
|
|
11
14
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
15
|
+
const STATUS_BADGE_VARIANT = {
|
|
16
|
+
success: "success",
|
|
17
|
+
error: "error",
|
|
18
|
+
partial: "warning"
|
|
19
|
+
};
|
|
12
20
|
function CurrencyFetchingConfig() {
|
|
13
21
|
const t = useT();
|
|
14
22
|
const [configs, setConfigs] = useState([]);
|
|
@@ -195,7 +203,7 @@ function CurrencyFetchingConfig() {
|
|
|
195
203
|
if (count === 0) {
|
|
196
204
|
flash(t("currencies.fetch.sync_no_rates"), "warning");
|
|
197
205
|
} else {
|
|
198
|
-
flash(
|
|
206
|
+
flash(t("currencies.fetch.sync_success", { count }), "success");
|
|
199
207
|
}
|
|
200
208
|
await loadConfigs();
|
|
201
209
|
}
|
|
@@ -209,17 +217,12 @@ function CurrencyFetchingConfig() {
|
|
|
209
217
|
loadConfigs();
|
|
210
218
|
}, [loadConfigs]);
|
|
211
219
|
function formatLastSync(date) {
|
|
212
|
-
if (!date) return "
|
|
220
|
+
if (!date) return t("currencies.fetch.last_sync_never");
|
|
213
221
|
return new Date(date).toLocaleString();
|
|
214
222
|
}
|
|
215
223
|
function getStatusBadge(status) {
|
|
216
224
|
if (!status) return null;
|
|
217
|
-
|
|
218
|
-
success: "bg-green-100 text-green-800 px-2 py-1 rounded text-xs",
|
|
219
|
-
error: "bg-red-100 text-red-800 px-2 py-1 rounded text-xs",
|
|
220
|
-
partial: "bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs"
|
|
221
|
-
};
|
|
222
|
-
return /* @__PURE__ */ jsx("span", { className: styles[status] || styles.error, children: status });
|
|
225
|
+
return /* @__PURE__ */ jsx(StatusBadge, { variant: STATUS_BADGE_VARIANT[status] ?? "neutral", children: status });
|
|
223
226
|
}
|
|
224
227
|
function getProviderName(provider) {
|
|
225
228
|
if (provider === "NBP") return t("currencies.fetch.provider_nbp");
|
|
@@ -279,12 +282,10 @@ function CurrencyFetchingConfig() {
|
|
|
279
282
|
":"
|
|
280
283
|
] }),
|
|
281
284
|
/* @__PURE__ */ jsx(
|
|
282
|
-
|
|
285
|
+
TimeInput,
|
|
283
286
|
{
|
|
284
|
-
type: "time",
|
|
285
287
|
value: config.syncTime || "09:00",
|
|
286
|
-
onChange: (
|
|
287
|
-
className: "rounded border bg-background px-2 py-1.5 text-sm"
|
|
288
|
+
onChange: (time) => updateSyncTime(config.id, time, config.updatedAt)
|
|
288
289
|
}
|
|
289
290
|
)
|
|
290
291
|
] }),
|
|
@@ -299,6 +300,7 @@ function CurrencyFetchingConfig() {
|
|
|
299
300
|
/* @__PURE__ */ jsx(
|
|
300
301
|
Button,
|
|
301
302
|
{
|
|
303
|
+
type: "button",
|
|
302
304
|
onClick: () => fetchNow(config.provider),
|
|
303
305
|
disabled: fetching === config.provider,
|
|
304
306
|
size: "default",
|
|
@@ -313,10 +315,9 @@ function CurrencyFetchingConfig() {
|
|
|
313
315
|
config.lastSyncCount !== null && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mt-2", children: [
|
|
314
316
|
t("currencies.fetch.last_sync_count"),
|
|
315
317
|
": ",
|
|
316
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: config.lastSyncCount })
|
|
317
|
-
" rates"
|
|
318
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: config.lastSyncCount })
|
|
318
319
|
] }),
|
|
319
|
-
config.lastSyncMessage && config.lastSyncStatus === "error" && /* @__PURE__ */ jsx(
|
|
320
|
+
config.lastSyncMessage && config.lastSyncStatus === "error" && /* @__PURE__ */ jsx(Alert, { status: "error", style: "lighter", size: "xs", className: "mt-2", children: config.lastSyncMessage })
|
|
320
321
|
] })
|
|
321
322
|
]
|
|
322
323
|
},
|
|
@@ -327,6 +328,7 @@ function CurrencyFetchingConfig() {
|
|
|
327
328
|
/* @__PURE__ */ jsx(
|
|
328
329
|
Button,
|
|
329
330
|
{
|
|
331
|
+
type: "button",
|
|
330
332
|
onClick: () => initializeMissingProviders(availableProviders),
|
|
331
333
|
children: t("currencies.fetch.initialize_providers")
|
|
332
334
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/currencies/components/CurrencyFetchingConfig.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ninterface FetchConfig {\n id: string\n provider: string\n isEnabled: boolean\n syncTime: string | null\n lastSyncAt: string | null\n lastSyncStatus: string | null\n lastSyncMessage: string | null\n lastSyncCount: number | null\n updatedAt?: string | null\n}\n\nexport default function CurrencyFetchingConfig() {\n const t = useT()\n const [configs, setConfigs] = useState<FetchConfig[]>([])\n const [loading, setLoading] = useState(true)\n const [fetching, setFetching] = useState<string | null>(null)\n const [initializing, setInitializing] = useState(false)\n\n // Available providers that should be configured\n const availableProviders = useMemo(() => ['NBP', 'Raiffeisen Bank Polska'], [])\n\n const mutationContextId = 'currencies-fetch-configs:mutation'\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const createProviderConfig = useCallback(async (provider: string) => {\n try {\n await runMutation({\n operation: () => apiCall('/api/currencies/fetch-configs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n provider,\n isEnabled: false,\n syncTime: '09:00',\n }),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_config',\n retryLastMutation,\n },\n mutationPayload: { provider, isEnabled: false, syncTime: '09:00' },\n })\n } catch (err: any) {\n // Ignore errors for duplicate providers\n if (!err.message?.includes('already configured')) {\n throw err\n }\n }\n }, [mutationContextId, retryLastMutation, runMutation])\n\n const initializeMissingProviders = useCallback(async (providers: string[]) => {\n setInitializing(true)\n try {\n for (const provider of providers) {\n await createProviderConfig(provider)\n }\n // Reload configs after initialization\n const { result } = await apiCall('/api/currencies/fetch-configs')\n if (result?.configs) {\n setConfigs(result.configs as FetchConfig[])\n }\n } catch (err: any) {\n console.error('Failed to initialize providers:', err)\n } finally {\n setInitializing(false)\n }\n }, [createProviderConfig])\n\n const loadConfigs = useCallback(async () => {\n try {\n const { result } = await apiCall('/api/currencies/fetch-configs')\n\n if (result?.configs) {\n const existingConfigs = result.configs as FetchConfig[]\n setConfigs(existingConfigs)\n\n // Check if we need to initialize any missing providers\n const existingProviders = existingConfigs.map((c) => c.provider)\n const missingProviders = availableProviders.filter(\n (p) => !existingProviders.includes(p)\n )\n\n // Auto-initialize missing providers\n if (missingProviders.length > 0) {\n await initializeMissingProviders(missingProviders)\n }\n }\n } catch (err: any) {\n flash(err.message || t('currencies.fetch.error_load_configs'), 'error')\n } finally {\n setLoading(false)\n }\n }, [availableProviders, initializeMissingProviders, t])\n\n const toggleEnabled = useCallback(async (configId: string, currentValue: boolean, updatedAt?: string | null) => {\n try {\n const call = await runMutation({\n operation: async () => {\n const response = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () =>\n apiCall('/api/currencies/fetch-configs', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: configId,\n isEnabled: !currentValue,\n }),\n }),\n )\n if (!response.ok) {\n throw Object.assign(new Error('[internal] currencies.fetchConfig.toggle failed'), {\n status: response.status,\n ...((response.result as Record<string, unknown> | null) ?? {}),\n })\n }\n return response\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_config',\n resourceId: configId,\n retryLastMutation,\n },\n mutationPayload: { id: configId, isEnabled: !currentValue },\n })\n\n const result = call.result as { config?: FetchConfig } | null\n if (result?.config) {\n setConfigs((prev) =>\n prev.map((c) => (c.id === configId ? (result.config as FetchConfig) : c))\n )\n flash(\n currentValue\n ? t('currencies.fetch.provider_disabled')\n : t('currencies.fetch.provider_enabled'),\n 'success'\n )\n }\n } catch (err: any) {\n if (extractOptimisticLockConflict(err)) return\n flash(err.message || t('currencies.fetch.error_update_config'), 'error')\n }\n }, [mutationContextId, retryLastMutation, runMutation, t])\n\n const updateSyncTime = useCallback(async (configId: string, syncTime: string, updatedAt?: string | null) => {\n try {\n const call = await runMutation({\n operation: async () => {\n const response = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () =>\n apiCall('/api/currencies/fetch-configs', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: configId,\n syncTime: syncTime || null,\n }),\n }),\n )\n if (!response.ok) {\n throw Object.assign(new Error('[internal] currencies.fetchConfig.updateSyncTime failed'), {\n status: response.status,\n ...((response.result as Record<string, unknown> | null) ?? {}),\n })\n }\n return response\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_config',\n resourceId: configId,\n retryLastMutation,\n },\n mutationPayload: { id: configId, syncTime: syncTime || null },\n })\n\n const result = call.result as { config?: FetchConfig } | null\n if (result?.config) {\n setConfigs((prev) =>\n prev.map((c) => (c.id === configId ? (result.config as FetchConfig) : c))\n )\n }\n } catch (err: any) {\n if (extractOptimisticLockConflict(err)) return\n flash(err.message || t('currencies.fetch.error_update_sync_time'), 'error')\n }\n }, [mutationContextId, retryLastMutation, runMutation, t])\n\n const fetchNow = useCallback(async (provider: string) => {\n setFetching(provider)\n\n try {\n const call = await runMutation({\n operation: () => apiCall('/api/currencies/fetch-rates', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n providers: [provider],\n }),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_rates',\n retryLastMutation,\n },\n mutationPayload: { providers: [provider] },\n })\n\n const result = call.result as { byProvider?: Record<string, { count: number; errors?: string[] }> } | null\n if (result) {\n const byProvider = result.byProvider as Record<string, { count: number; errors?: string[] }>\n const count = byProvider?.[provider]?.count || 0\n\n if (count === 0) {\n flash(t('currencies.fetch.sync_no_rates'), 'warning')\n } else {\n flash(`${t('currencies.fetch.sync_success')}: ${count} rates fetched`, 'success')\n }\n await loadConfigs()\n }\n } catch (err: any) {\n flash(err.message || t('currencies.fetch.sync_error'), 'error')\n } finally {\n setFetching(null)\n }\n }, [mutationContextId, retryLastMutation, runMutation, t, loadConfigs])\n\n useEffect(() => {\n loadConfigs()\n }, [loadConfigs])\n\n function formatLastSync(date: string | null): string {\n if (!date) return 'Never'\n return new Date(date).toLocaleString()\n }\n\n function getStatusBadge(status: string | null) {\n if (!status) return null\n\n const styles: Record<string, string> = {\n success: 'bg-green-100 text-green-800 px-2 py-1 rounded text-xs',\n error: 'bg-red-100 text-red-800 px-2 py-1 rounded text-xs',\n partial: 'bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs',\n }\n\n return (\n <span className={styles[status] || styles.error}>\n {status}\n </span>\n )\n }\n\n function getProviderName(provider: string): string {\n if (provider === 'NBP') return t('currencies.fetch.provider_nbp')\n if (provider === 'Raiffeisen Bank Polska') return t('currencies.fetch.provider_raiffeisen')\n return provider\n }\n\n function getProviderDescription(provider: string): string {\n if (provider === 'NBP') {\n return t('currencies.fetch.provider_nbp_description')\n }\n if (provider === 'Raiffeisen Bank Polska') {\n return t('currencies.fetch.provider_raiffeisen_description')\n }\n return ''\n }\n\n if (loading || initializing) {\n return (\n <section className=\"space-y-3 rounded-lg border bg-background p-4\">\n <header className=\"space-y-2\">\n <h2 className=\"text-xl font-semibold\">{t('currencies.fetch.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('currencies.fetch.description')}\n </p>\n </header>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {initializing ? t('currencies.fetch.initializing_providers') : t('currencies.fetch.loading')}\n </div>\n </section>\n )\n }\n\n return (\n <section className=\"space-y-6 rounded-lg border bg-background p-4\">\n <header className=\"space-y-2\">\n <h2 className=\"text-xl font-semibold\">{t('currencies.fetch.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('currencies.fetch.description')}\n </p>\n </header>\n\n <div className=\"space-y-3\">\n {configs.map((config) => (\n <div\n key={config.id}\n className=\"rounded-lg border bg-card p-4\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1\">\n <h3 className=\"text-sm font-medium\">\n {getProviderName(config.provider)}\n </h3>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {getProviderDescription(config.provider)}\n </p>\n </div>\n\n <Switch\n checked={config.isEnabled}\n onCheckedChange={() => toggleEnabled(config.id, config.isEnabled, config.updatedAt)}\n />\n </div>\n\n {config.isEnabled && (\n <>\n <div className=\"flex items-baseline gap-3 flex-wrap mt-3\">\n <div className=\"flex items-baseline gap-2\">\n <label className=\"text-xs text-muted-foreground whitespace-nowrap\">\n {t('currencies.fetch.sync_time')}:\n </label>\n <input\n type=\"time\"\n value={config.syncTime || '09:00'}\n onChange={(e) => updateSyncTime(config.id, e.target.value, config.updatedAt)}\n className=\"rounded border bg-background px-2 py-1.5 text-sm\"\n />\n </div>\n\n <div className=\"flex items-baseline gap-2\">\n <label className=\"text-xs text-muted-foreground whitespace-nowrap\">\n {t('currencies.fetch.last_sync')}:\n </label>\n <span className=\"text-xs text-muted-foreground\">\n {formatLastSync(config.lastSyncAt)}\n </span>\n {getStatusBadge(config.lastSyncStatus)}\n </div>\n\n <Button\n onClick={() => fetchNow(config.provider)}\n disabled={fetching === config.provider}\n size=\"default\"\n className=\"ml-auto min-w-32\"\n >\n {fetching === config.provider ? (\n <div>\n <Spinner className=\"mr-1.5\" size='sm' />\n {t('currencies.fetch.fetching')}\n </div>\n ) : (\n t('currencies.fetch.fetch_now')\n )}\n </Button>\n </div>\n\n {config.lastSyncCount !== null && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n {t('currencies.fetch.last_sync_count')}: <span className=\"font-medium\">{config.lastSyncCount}</span> rates\n </div>\n )}\n\n {config.lastSyncMessage && config.lastSyncStatus === 'error' && (\n <div className=\"rounded border border-red-200 bg-red-50 p-2 text-xs text-red-700 mt-2\">\n {config.lastSyncMessage}\n </div>\n )}\n </>\n )}\n </div>\n ))}\n\n {configs.length === 0 && (\n <div className=\"rounded border bg-background/80 p-6 text-center\">\n <p className=\"text-sm text-muted-foreground\">\n {t('currencies.fetch.no_providers')}\n </p>\n <Button\n onClick={() => initializeMissingProviders(availableProviders)}\n >\n {t('currencies.fetch.initialize_providers')}\n </Button>\n </div>\n )}\n </div>\n </section>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { TimeInput } from '@open-mercato/ui/backend/inputs/TimeInput'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ninterface FetchConfig {\n id: string\n provider: string\n isEnabled: boolean\n syncTime: string | null\n lastSyncAt: string | null\n lastSyncStatus: string | null\n lastSyncMessage: string | null\n lastSyncCount: number | null\n updatedAt?: string | null\n}\n\nconst STATUS_BADGE_VARIANT: Record<string, StatusBadgeVariant> = {\n success: 'success',\n error: 'error',\n partial: 'warning',\n}\n\nexport default function CurrencyFetchingConfig() {\n const t = useT()\n const [configs, setConfigs] = useState<FetchConfig[]>([])\n const [loading, setLoading] = useState(true)\n const [fetching, setFetching] = useState<string | null>(null)\n const [initializing, setInitializing] = useState(false)\n\n // Available providers that should be configured\n const availableProviders = useMemo(() => ['NBP', 'Raiffeisen Bank Polska'], [])\n\n const mutationContextId = 'currencies-fetch-configs:mutation'\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const createProviderConfig = useCallback(async (provider: string) => {\n try {\n await runMutation({\n operation: () => apiCall('/api/currencies/fetch-configs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n provider,\n isEnabled: false,\n syncTime: '09:00',\n }),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_config',\n retryLastMutation,\n },\n mutationPayload: { provider, isEnabled: false, syncTime: '09:00' },\n })\n } catch (err: any) {\n // Ignore errors for duplicate providers\n if (!err.message?.includes('already configured')) {\n throw err\n }\n }\n }, [mutationContextId, retryLastMutation, runMutation])\n\n const initializeMissingProviders = useCallback(async (providers: string[]) => {\n setInitializing(true)\n try {\n for (const provider of providers) {\n await createProviderConfig(provider)\n }\n // Reload configs after initialization\n const { result } = await apiCall('/api/currencies/fetch-configs')\n if (result?.configs) {\n setConfigs(result.configs as FetchConfig[])\n }\n } catch (err: any) {\n console.error('Failed to initialize providers:', err)\n } finally {\n setInitializing(false)\n }\n }, [createProviderConfig])\n\n const loadConfigs = useCallback(async () => {\n try {\n const { result } = await apiCall('/api/currencies/fetch-configs')\n\n if (result?.configs) {\n const existingConfigs = result.configs as FetchConfig[]\n setConfigs(existingConfigs)\n\n // Check if we need to initialize any missing providers\n const existingProviders = existingConfigs.map((c) => c.provider)\n const missingProviders = availableProviders.filter(\n (p) => !existingProviders.includes(p)\n )\n\n // Auto-initialize missing providers\n if (missingProviders.length > 0) {\n await initializeMissingProviders(missingProviders)\n }\n }\n } catch (err: any) {\n flash(err.message || t('currencies.fetch.error_load_configs'), 'error')\n } finally {\n setLoading(false)\n }\n }, [availableProviders, initializeMissingProviders, t])\n\n const toggleEnabled = useCallback(async (configId: string, currentValue: boolean, updatedAt?: string | null) => {\n try {\n const call = await runMutation({\n operation: async () => {\n const response = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () =>\n apiCall('/api/currencies/fetch-configs', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: configId,\n isEnabled: !currentValue,\n }),\n }),\n )\n if (!response.ok) {\n throw Object.assign(new Error('[internal] currencies.fetchConfig.toggle failed'), {\n status: response.status,\n ...((response.result as Record<string, unknown> | null) ?? {}),\n })\n }\n return response\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_config',\n resourceId: configId,\n retryLastMutation,\n },\n mutationPayload: { id: configId, isEnabled: !currentValue },\n })\n\n const result = call.result as { config?: FetchConfig } | null\n if (result?.config) {\n setConfigs((prev) =>\n prev.map((c) => (c.id === configId ? (result.config as FetchConfig) : c))\n )\n flash(\n currentValue\n ? t('currencies.fetch.provider_disabled')\n : t('currencies.fetch.provider_enabled'),\n 'success'\n )\n }\n } catch (err: any) {\n if (extractOptimisticLockConflict(err)) return\n flash(err.message || t('currencies.fetch.error_update_config'), 'error')\n }\n }, [mutationContextId, retryLastMutation, runMutation, t])\n\n const updateSyncTime = useCallback(async (configId: string, syncTime: string, updatedAt?: string | null) => {\n try {\n const call = await runMutation({\n operation: async () => {\n const response = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () =>\n apiCall('/api/currencies/fetch-configs', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: configId,\n syncTime: syncTime || null,\n }),\n }),\n )\n if (!response.ok) {\n throw Object.assign(new Error('[internal] currencies.fetchConfig.updateSyncTime failed'), {\n status: response.status,\n ...((response.result as Record<string, unknown> | null) ?? {}),\n })\n }\n return response\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_config',\n resourceId: configId,\n retryLastMutation,\n },\n mutationPayload: { id: configId, syncTime: syncTime || null },\n })\n\n const result = call.result as { config?: FetchConfig } | null\n if (result?.config) {\n setConfigs((prev) =>\n prev.map((c) => (c.id === configId ? (result.config as FetchConfig) : c))\n )\n }\n } catch (err: any) {\n if (extractOptimisticLockConflict(err)) return\n flash(err.message || t('currencies.fetch.error_update_sync_time'), 'error')\n }\n }, [mutationContextId, retryLastMutation, runMutation, t])\n\n const fetchNow = useCallback(async (provider: string) => {\n setFetching(provider)\n\n try {\n const call = await runMutation({\n operation: () => apiCall('/api/currencies/fetch-rates', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n providers: [provider],\n }),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.fetch_rates',\n retryLastMutation,\n },\n mutationPayload: { providers: [provider] },\n })\n\n const result = call.result as { byProvider?: Record<string, { count: number; errors?: string[] }> } | null\n if (result) {\n const byProvider = result.byProvider as Record<string, { count: number; errors?: string[] }>\n const count = byProvider?.[provider]?.count || 0\n\n if (count === 0) {\n flash(t('currencies.fetch.sync_no_rates'), 'warning')\n } else {\n flash(t('currencies.fetch.sync_success', { count }), 'success')\n }\n await loadConfigs()\n }\n } catch (err: any) {\n flash(err.message || t('currencies.fetch.sync_error'), 'error')\n } finally {\n setFetching(null)\n }\n }, [mutationContextId, retryLastMutation, runMutation, t, loadConfigs])\n\n useEffect(() => {\n loadConfigs()\n }, [loadConfigs])\n\n function formatLastSync(date: string | null): string {\n if (!date) return t('currencies.fetch.last_sync_never')\n return new Date(date).toLocaleString()\n }\n\n function getStatusBadge(status: string | null) {\n if (!status) return null\n\n return (\n <StatusBadge variant={STATUS_BADGE_VARIANT[status] ?? 'neutral'}>\n {status}\n </StatusBadge>\n )\n }\n\n function getProviderName(provider: string): string {\n if (provider === 'NBP') return t('currencies.fetch.provider_nbp')\n if (provider === 'Raiffeisen Bank Polska') return t('currencies.fetch.provider_raiffeisen')\n return provider\n }\n\n function getProviderDescription(provider: string): string {\n if (provider === 'NBP') {\n return t('currencies.fetch.provider_nbp_description')\n }\n if (provider === 'Raiffeisen Bank Polska') {\n return t('currencies.fetch.provider_raiffeisen_description')\n }\n return ''\n }\n\n if (loading || initializing) {\n return (\n <section className=\"space-y-3 rounded-lg border bg-background p-4\">\n <header className=\"space-y-2\">\n <h2 className=\"text-xl font-semibold\">{t('currencies.fetch.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('currencies.fetch.description')}\n </p>\n </header>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {initializing ? t('currencies.fetch.initializing_providers') : t('currencies.fetch.loading')}\n </div>\n </section>\n )\n }\n\n return (\n <section className=\"space-y-6 rounded-lg border bg-background p-4\">\n <header className=\"space-y-2\">\n <h2 className=\"text-xl font-semibold\">{t('currencies.fetch.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('currencies.fetch.description')}\n </p>\n </header>\n\n <div className=\"space-y-3\">\n {configs.map((config) => (\n <div\n key={config.id}\n className=\"rounded-lg border bg-card p-4\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1\">\n <h3 className=\"text-sm font-medium\">\n {getProviderName(config.provider)}\n </h3>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {getProviderDescription(config.provider)}\n </p>\n </div>\n\n <Switch\n checked={config.isEnabled}\n onCheckedChange={() => toggleEnabled(config.id, config.isEnabled, config.updatedAt)}\n />\n </div>\n\n {config.isEnabled && (\n <>\n <div className=\"flex items-baseline gap-3 flex-wrap mt-3\">\n <div className=\"flex items-baseline gap-2\">\n <label className=\"text-xs text-muted-foreground whitespace-nowrap\">\n {t('currencies.fetch.sync_time')}:\n </label>\n <TimeInput\n value={config.syncTime || '09:00'}\n onChange={(time) => updateSyncTime(config.id, time, config.updatedAt)}\n />\n </div>\n\n <div className=\"flex items-baseline gap-2\">\n <label className=\"text-xs text-muted-foreground whitespace-nowrap\">\n {t('currencies.fetch.last_sync')}:\n </label>\n <span className=\"text-xs text-muted-foreground\">\n {formatLastSync(config.lastSyncAt)}\n </span>\n {getStatusBadge(config.lastSyncStatus)}\n </div>\n\n <Button\n type=\"button\"\n onClick={() => fetchNow(config.provider)}\n disabled={fetching === config.provider}\n size=\"default\"\n className=\"ml-auto min-w-32\"\n >\n {fetching === config.provider ? (\n <div>\n <Spinner className=\"mr-1.5\" size='sm' />\n {t('currencies.fetch.fetching')}\n </div>\n ) : (\n t('currencies.fetch.fetch_now')\n )}\n </Button>\n </div>\n\n {config.lastSyncCount !== null && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n {t('currencies.fetch.last_sync_count')}: <span className=\"font-medium\">{config.lastSyncCount}</span>\n </div>\n )}\n\n {config.lastSyncMessage && config.lastSyncStatus === 'error' && (\n <Alert status=\"error\" style=\"lighter\" size=\"xs\" className=\"mt-2\">\n {config.lastSyncMessage}\n </Alert>\n )}\n </>\n )}\n </div>\n ))}\n\n {configs.length === 0 && (\n <div className=\"rounded border bg-background/80 p-6 text-center\">\n <p className=\"text-sm text-muted-foreground\">\n {t('currencies.fetch.no_providers')}\n </p>\n <Button\n type=\"button\"\n onClick={() => initializeMissingProviders(availableProviders)}\n >\n {t('currencies.fetch.initialize_providers')}\n </Button>\n </div>\n )}\n </div>\n </section>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAiRM,SAuEQ,UAvER,KAyBE,YAzBF;AA/QN,SAAS,UAAU,WAAW,aAAa,eAAe;AAC1D,SAAS,aAAa;AACtB,SAAS,SAAS,mCAAmC;AACrD,SAAS,2BAA2B,qCAAqC;AACzE,SAAS,0BAA0B;AACnC,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,mBAA4C;AACrD,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAC1B,SAAS,YAAY;AAcrB,MAAM,uBAA2D;AAAA,EAC/D,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AACX;AAEe,SAAR,yBAA0C;AAC/C,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,SAAS,UAAU,IAAI,SAAwB,CAAC,CAAC;AACxD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAC5D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AAGtD,QAAM,qBAAqB,QAAQ,MAAM,CAAC,OAAO,wBAAwB,GAAG,CAAC,CAAC;AAE9E,QAAM,oBAAoB;AAC1B,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAKxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AAED,QAAM,uBAAuB,YAAY,OAAO,aAAqB;AACnE,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,MAAM,QAAQ,iCAAiC;AAAA,UACxD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB;AAAA,YACA,WAAW;AAAA,YACX,UAAU;AAAA,UACZ,CAAC;AAAA,QACH,CAAC;AAAA,QACD,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd;AAAA,QACF;AAAA,QACA,iBAAiB,EAAE,UAAU,WAAW,OAAO,UAAU,QAAQ;AAAA,MACnE,CAAC;AAAA,IACH,SAAS,KAAU;AAEjB,UAAI,CAAC,IAAI,SAAS,SAAS,oBAAoB,GAAG;AAChD,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,GAAG,CAAC,mBAAmB,mBAAmB,WAAW,CAAC;AAEtD,QAAM,6BAA6B,YAAY,OAAO,cAAwB;AAC5E,oBAAgB,IAAI;AACpB,QAAI;AACF,iBAAW,YAAY,WAAW;AAChC,cAAM,qBAAqB,QAAQ;AAAA,MACrC;AAEA,YAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,+BAA+B;AAChE,UAAI,QAAQ,SAAS;AACnB,mBAAW,OAAO,OAAwB;AAAA,MAC5C;AAAA,IACF,SAAS,KAAU;AACjB,cAAQ,MAAM,mCAAmC,GAAG;AAAA,IACtD,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,oBAAoB,CAAC;AAEzB,QAAM,cAAc,YAAY,YAAY;AAC1C,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,+BAA+B;AAEhE,UAAI,QAAQ,SAAS;AACnB,cAAM,kBAAkB,OAAO;AAC/B,mBAAW,eAAe;AAG1B,cAAM,oBAAoB,gBAAgB,IAAI,CAAC,MAAM,EAAE,QAAQ;AAC/D,cAAM,mBAAmB,mBAAmB;AAAA,UAC1C,CAAC,MAAM,CAAC,kBAAkB,SAAS,CAAC;AAAA,QACtC;AAGA,YAAI,iBAAiB,SAAS,GAAG;AAC/B,gBAAM,2BAA2B,gBAAgB;AAAA,QACnD;AAAA,MACF;AAAA,IACF,SAAS,KAAU;AACjB,YAAM,IAAI,WAAW,EAAE,qCAAqC,GAAG,OAAO;AAAA,IACxE,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,oBAAoB,4BAA4B,CAAC,CAAC;AAEtD,QAAM,gBAAgB,YAAY,OAAO,UAAkB,cAAuB,cAA8B;AAC9G,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,YAAY;AACrB,gBAAM,WAAW,MAAM;AAAA,YACrB,0BAA0B,SAAS;AAAA,YACnC,MACE,QAAQ,iCAAiC;AAAA,cACvC,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU;AAAA,gBACnB,IAAI;AAAA,gBACJ,WAAW,CAAC;AAAA,cACd,CAAC;AAAA,YACH,CAAC;AAAA,UACL;AACA,cAAI,CAAC,SAAS,IAAI;AAChB,kBAAM,OAAO,OAAO,IAAI,MAAM,iDAAiD,GAAG;AAAA,cAChF,QAAQ,SAAS;AAAA,cACjB,GAAK,SAAS,UAA6C,CAAC;AAAA,YAC9D,CAAC;AAAA,UACH;AACA,iBAAO;AAAA,QACT;AAAA,QACA,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY;AAAA,UACZ;AAAA,QACF;AAAA,QACA,iBAAiB,EAAE,IAAI,UAAU,WAAW,CAAC,aAAa;AAAA,MAC5D,CAAC;AAED,YAAM,SAAS,KAAK;AACpB,UAAI,QAAQ,QAAQ;AAClB;AAAA,UAAW,CAAC,SACV,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,WAAY,OAAO,SAAyB,CAAE;AAAA,QAC1E;AACA;AAAA,UACE,eACI,EAAE,oCAAoC,IACtC,EAAE,mCAAmC;AAAA,UACzC;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAU;AACjB,UAAI,8BAA8B,GAAG,EAAG;AACxC,YAAM,IAAI,WAAW,EAAE,sCAAsC,GAAG,OAAO;AAAA,IACzE;AAAA,EACF,GAAG,CAAC,mBAAmB,mBAAmB,aAAa,CAAC,CAAC;AAEzD,QAAM,iBAAiB,YAAY,OAAO,UAAkB,UAAkB,cAA8B;AAC1G,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,YAAY;AACrB,gBAAM,WAAW,MAAM;AAAA,YACrB,0BAA0B,SAAS;AAAA,YACnC,MACE,QAAQ,iCAAiC;AAAA,cACvC,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU;AAAA,gBACnB,IAAI;AAAA,gBACJ,UAAU,YAAY;AAAA,cACxB,CAAC;AAAA,YACH,CAAC;AAAA,UACL;AACA,cAAI,CAAC,SAAS,IAAI;AAChB,kBAAM,OAAO,OAAO,IAAI,MAAM,yDAAyD,GAAG;AAAA,cACxF,QAAQ,SAAS;AAAA,cACjB,GAAK,SAAS,UAA6C,CAAC;AAAA,YAC9D,CAAC;AAAA,UACH;AACA,iBAAO;AAAA,QACT;AAAA,QACA,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY;AAAA,UACZ;AAAA,QACF;AAAA,QACA,iBAAiB,EAAE,IAAI,UAAU,UAAU,YAAY,KAAK;AAAA,MAC9D,CAAC;AAED,YAAM,SAAS,KAAK;AACpB,UAAI,QAAQ,QAAQ;AAClB;AAAA,UAAW,CAAC,SACV,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,WAAY,OAAO,SAAyB,CAAE;AAAA,QAC1E;AAAA,MACF;AAAA,IACF,SAAS,KAAU;AACjB,UAAI,8BAA8B,GAAG,EAAG;AACxC,YAAM,IAAI,WAAW,EAAE,yCAAyC,GAAG,OAAO;AAAA,IAC5E;AAAA,EACF,GAAG,CAAC,mBAAmB,mBAAmB,aAAa,CAAC,CAAC;AAEzD,QAAM,WAAW,YAAY,OAAO,aAAqB;AACvD,gBAAY,QAAQ;AAEpB,QAAI;AACF,YAAM,OAAO,MAAM,YAAY;AAAA,QAC7B,WAAW,MAAM,QAAQ,+BAA+B;AAAA,UACtD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,WAAW,CAAC,QAAQ;AAAA,UACtB,CAAC;AAAA,QACH,CAAC;AAAA,QACD,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd;AAAA,QACF;AAAA,QACA,iBAAiB,EAAE,WAAW,CAAC,QAAQ,EAAE;AAAA,MAC3C,CAAC;AAED,YAAM,SAAS,KAAK;AACpB,UAAI,QAAQ;AACV,cAAM,aAAa,OAAO;AAC1B,cAAM,QAAQ,aAAa,QAAQ,GAAG,SAAS;AAE/C,YAAI,UAAU,GAAG;AACf,gBAAM,EAAE,gCAAgC,GAAG,SAAS;AAAA,QACtD,OAAO;AACL,gBAAM,EAAE,iCAAiC,EAAE,MAAM,CAAC,GAAG,SAAS;AAAA,QAChE;AACA,cAAM,YAAY;AAAA,MACpB;AAAA,IACF,SAAS,KAAU;AACjB,YAAM,IAAI,WAAW,EAAE,6BAA6B,GAAG,OAAO;AAAA,IAChE,UAAE;AACA,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,mBAAmB,mBAAmB,aAAa,GAAG,WAAW,CAAC;AAEtE,YAAU,MAAM;AACd,gBAAY;AAAA,EACd,GAAG,CAAC,WAAW,CAAC;AAEhB,WAAS,eAAe,MAA6B;AACnD,QAAI,CAAC,KAAM,QAAO,EAAE,kCAAkC;AACtD,WAAO,IAAI,KAAK,IAAI,EAAE,eAAe;AAAA,EACvC;AAEA,WAAS,eAAe,QAAuB;AAC7C,QAAI,CAAC,OAAQ,QAAO;AAEpB,WACE,oBAAC,eAAY,SAAS,qBAAqB,MAAM,KAAK,WACnD,kBACH;AAAA,EAEJ;AAEA,WAAS,gBAAgB,UAA0B;AACjD,QAAI,aAAa,MAAO,QAAO,EAAE,+BAA+B;AAChE,QAAI,aAAa,yBAA0B,QAAO,EAAE,sCAAsC;AAC1F,WAAO;AAAA,EACT;AAEA,WAAS,uBAAuB,UAA0B;AACxD,QAAI,aAAa,OAAO;AACtB,aAAO,EAAE,2CAA2C;AAAA,IACtD;AACA,QAAI,aAAa,0BAA0B;AACzC,aAAO,EAAE,kDAAkD;AAAA,IAC7D;AACA,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,cAAc;AAC3B,WACE,qBAAC,aAAQ,WAAU,iDACjB;AAAA,2BAAC,YAAO,WAAU,aAChB;AAAA,4BAAC,QAAG,WAAU,yBAAyB,YAAE,wBAAwB,GAAE;AAAA,QACnE,oBAAC,OAAE,WAAU,iCACV,YAAE,8BAA8B,GACnC;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,yDACb;AAAA,4BAAC,WAAQ,WAAU,WAAU;AAAA,QAC5B,eAAe,EAAE,yCAAyC,IAAI,EAAE,0BAA0B;AAAA,SAC7F;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,qBAAC,aAAQ,WAAU,iDACjB;AAAA,yBAAC,YAAO,WAAU,aAChB;AAAA,0BAAC,QAAG,WAAU,yBAAyB,YAAE,wBAAwB,GAAE;AAAA,MACnE,oBAAC,OAAE,WAAU,iCACV,YAAE,8BAA8B,GACnC;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,aACZ;AAAA,cAAQ,IAAI,CAAC,WACZ;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,iCAAC,SAAI,WAAU,0CACb;AAAA,mCAAC,SAAI,WAAU,UACb;AAAA,oCAAC,QAAG,WAAU,uBACX,0BAAgB,OAAO,QAAQ,GAClC;AAAA,gBACA,oBAAC,OAAE,WAAU,wCACV,iCAAuB,OAAO,QAAQ,GACzC;AAAA,iBACF;AAAA,cAEA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAS,OAAO;AAAA,kBAChB,iBAAiB,MAAM,cAAc,OAAO,IAAI,OAAO,WAAW,OAAO,SAAS;AAAA;AAAA,cACpF;AAAA,eACF;AAAA,YAEC,OAAO,aACN,iCACE;AAAA,mCAAC,SAAI,WAAU,4CACb;AAAA,qCAAC,SAAI,WAAU,6BACb;AAAA,uCAAC,WAAM,WAAU,mDACd;AAAA,sBAAE,4BAA4B;AAAA,oBAAE;AAAA,qBACnC;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAO,OAAO,YAAY;AAAA,sBAC1B,UAAU,CAAC,SAAS,eAAe,OAAO,IAAI,MAAM,OAAO,SAAS;AAAA;AAAA,kBACtE;AAAA,mBACF;AAAA,gBAEA,qBAAC,SAAI,WAAU,6BACb;AAAA,uCAAC,WAAM,WAAU,mDACd;AAAA,sBAAE,4BAA4B;AAAA,oBAAE;AAAA,qBACnC;AAAA,kBACA,oBAAC,UAAK,WAAU,iCACb,yBAAe,OAAO,UAAU,GACnC;AAAA,kBACC,eAAe,OAAO,cAAc;AAAA,mBACvC;AAAA,gBAEA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAS,MAAM,SAAS,OAAO,QAAQ;AAAA,oBACvC,UAAU,aAAa,OAAO;AAAA,oBAC9B,MAAK;AAAA,oBACL,WAAU;AAAA,oBAET,uBAAa,OAAO,WACnB,qBAAC,SACC;AAAA,0CAAC,WAAQ,WAAU,UAAS,MAAK,MAAK;AAAA,sBACrC,EAAE,2BAA2B;AAAA,uBAChC,IAEA,EAAE,4BAA4B;AAAA;AAAA,gBAElC;AAAA,iBACF;AAAA,cAEC,OAAO,kBAAkB,QACxB,qBAAC,SAAI,WAAU,sCACZ;AAAA,kBAAE,kCAAkC;AAAA,gBAAE;AAAA,gBAAE,oBAAC,UAAK,WAAU,eAAe,iBAAO,eAAc;AAAA,iBAC/F;AAAA,cAGD,OAAO,mBAAmB,OAAO,mBAAmB,WACnD,oBAAC,SAAM,QAAO,SAAQ,OAAM,WAAU,MAAK,MAAK,WAAU,QACvD,iBAAO,iBACV;AAAA,eAEJ;AAAA;AAAA;AAAA,QAvEG,OAAO;AAAA,MAyEd,CACD;AAAA,MAEA,QAAQ,WAAW,KAClB,qBAAC,SAAI,WAAU,mDACb;AAAA,4BAAC,OAAE,WAAU,iCACV,YAAE,+BAA+B,GACpC;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,2BAA2B,kBAAkB;AAAA,YAE3D,YAAE,uCAAuC;AAAA;AAAA,QAC5C;AAAA,SACF;AAAA,OAEJ;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|