@open-mercato/core 0.6.6-develop.6204.1.30b1f58642 → 0.6.6-develop.6227.1.2695efff30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +63 -27
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +57 -31
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +156 -76
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/lib/calendar/labels.js +33 -0
- package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +44 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +31 -0
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +33 -4
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +7 -4
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +54 -2
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -9
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +15 -0
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +26 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
- package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
- package/dist/modules/entities/api/definitions.restore.js +15 -0
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/api/entities.js +31 -3
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +37 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +31 -0
- package/dist/modules/payment_gateways/api/guards.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +37 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +11 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +16 -0
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/purge.js +35 -3
- package/dist/modules/query_index/api/purge.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +41 -3
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +6 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +49 -20
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
- package/src/modules/currencies/backend/currencies/page.tsx +68 -29
- package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
- package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
- package/src/modules/customers/components/DictionarySettings.tsx +63 -29
- package/src/modules/customers/components/PipelineSettings.tsx +165 -80
- package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
- package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
- package/src/modules/customers/i18n/de.json +11 -7
- package/src/modules/customers/i18n/en.json +6 -2
- package/src/modules/customers/i18n/es.json +11 -7
- package/src/modules/customers/i18n/pl.json +11 -7
- package/src/modules/customers/lib/calendar/labels.ts +42 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
- package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
- package/src/modules/data_sync/api/mappings/route.ts +48 -0
- package/src/modules/data_sync/api/run.ts +33 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
- package/src/modules/data_sync/api/schedules/route.ts +36 -4
- package/src/modules/dictionaries/components/DictionariesManager.tsx +7 -4
- package/src/modules/directory/api/organization-branding/route.ts +61 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
- package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
- package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
- package/src/modules/entities/api/definitions.batch.ts +17 -0
- package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
- package/src/modules/entities/api/definitions.restore.ts +17 -0
- package/src/modules/entities/api/definitions.ts +30 -0
- package/src/modules/entities/api/entities.ts +35 -3
- package/src/modules/entities/api/records.ts +20 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
- package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
- package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
- package/src/modules/payment_gateways/api/capture/route.ts +39 -0
- package/src/modules/payment_gateways/api/guards.ts +59 -0
- package/src/modules/payment_gateways/api/refund/route.ts +39 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
- package/src/modules/planner/api/availability-date-specific.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
- package/src/modules/planner/commands/availability-date-specific.ts +24 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
- package/src/modules/query_index/api/purge.ts +37 -3
- package/src/modules/query_index/api/reindex.ts +43 -3
- package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
- package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
- package/src/modules/shipping_carriers/data/validators.ts +5 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
- package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
- package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
- package/src/modules/translations/components/TranslationManager.tsx +65 -21
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/customers/lib/calendar/labels.ts"],
|
|
4
|
+
"sourcesContent": ["const MS_PER_DAY = 24 * 60 * 60 * 1000\n\nexport function pluralCategory(locale: string, count: number): Intl.LDMLPluralRule {\n try {\n return new Intl.PluralRules(locale).select(count)\n } catch {\n return count === 1 ? 'one' : 'other'\n }\n}\n\nexport function eventDisplayTitle(title: string | null | undefined, fallback: string): string {\n return title && title.trim().length > 0 ? title : fallback\n}\n\nexport function composeAccessibleName(\n parts: Array<string | null | undefined>,\n separator = ', ',\n): string {\n return parts\n .map((part) => (typeof part === 'string' ? part.trim() : ''))\n .filter((part) => part.length > 0)\n .join(separator)\n}\n\nfunction parseDateInput(value: string): number {\n const parsed = new Date(`${value}T00:00:00`)\n return parsed.getTime()\n}\n\n/**\n * Number of calendar days a scheduled event spans when its end date falls on a\n * later day than its start date. Returns 0 for same-day, end-before-start, or\n * unparseable inputs, so callers can treat 0 as \"not multi-day\".\n */\nexport function multiDayEventSpan(startDate: string, endDate: string): number {\n const start = parseDateInput(startDate)\n const end = parseDateInput(endDate)\n if (Number.isNaN(start) || Number.isNaN(end)) return 0\n const dayDiff = Math.round((end - start) / MS_PER_DAY)\n if (dayDiff < 1) return 0\n return dayDiff + 1\n}\n"],
|
|
5
|
+
"mappings": "AAAA,MAAM,aAAa,KAAK,KAAK,KAAK;AAE3B,SAAS,eAAe,QAAgB,OAAoC;AACjF,MAAI;AACF,WAAO,IAAI,KAAK,YAAY,MAAM,EAAE,OAAO,KAAK;AAAA,EAClD,QAAQ;AACN,WAAO,UAAU,IAAI,QAAQ;AAAA,EAC/B;AACF;AAEO,SAAS,kBAAkB,OAAkC,UAA0B;AAC5F,SAAO,SAAS,MAAM,KAAK,EAAE,SAAS,IAAI,QAAQ;AACpD;AAEO,SAAS,sBACd,OACA,YAAY,MACJ;AACR,SAAO,MACJ,IAAI,CAAC,SAAU,OAAO,SAAS,WAAW,KAAK,KAAK,IAAI,EAAG,EAC3D,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,KAAK,SAAS;AACnB;AAEA,SAAS,eAAe,OAAuB;AAC7C,QAAM,SAAS,oBAAI,KAAK,GAAG,KAAK,WAAW;AAC3C,SAAO,OAAO,QAAQ;AACxB;AAOO,SAAS,kBAAkB,WAAmB,SAAyB;AAC5E,QAAM,QAAQ,eAAe,SAAS;AACtC,QAAM,MAAM,eAAe,OAAO;AAClC,MAAI,OAAO,MAAM,KAAK,KAAK,OAAO,MAAM,GAAG,EAAG,QAAO;AACrD,QAAM,UAAU,KAAK,OAAO,MAAM,SAAS,UAAU;AACrD,MAAI,UAAU,EAAG,QAAO;AACxB,SAAO,UAAU;AACnB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -6,8 +6,10 @@ import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
|
6
6
|
import { RadioGroup } from "@open-mercato/ui/primitives/radio";
|
|
7
7
|
import { RadioField } from "@open-mercato/ui/primitives/radio-field";
|
|
8
8
|
import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
9
10
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
10
11
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
12
|
+
const WIDGET_VISIBILITY_CONTEXT_ID = "dashboards-widget-visibility:save";
|
|
11
13
|
const EMPTY = [];
|
|
12
14
|
function resolveWidgetText(t, id, field, fallback) {
|
|
13
15
|
const key1 = `${id}.${field}`;
|
|
@@ -39,6 +41,9 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
|
|
|
39
41
|
[t]
|
|
40
42
|
);
|
|
41
43
|
const { kind, targetId, tenantId, organizationId, preserveOnTenantChange = false } = props;
|
|
44
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
45
|
+
contextId: WIDGET_VISIBILITY_CONTEXT_ID
|
|
46
|
+
});
|
|
42
47
|
const [catalog, setCatalog] = React.useState([]);
|
|
43
48
|
const [loading, setLoading] = React.useState(true);
|
|
44
49
|
const [saving, setSaving] = React.useState(false);
|
|
@@ -180,11 +185,19 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
|
|
|
180
185
|
organizationId: organizationId ?? null,
|
|
181
186
|
widgetIds: selected
|
|
182
187
|
};
|
|
183
|
-
await
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
await runMutation({
|
|
189
|
+
operation: () => apiCallOrThrow("/api/dashboards/roles/widgets", {
|
|
190
|
+
method: "PUT",
|
|
191
|
+
headers: { "content-type": "application/json" },
|
|
192
|
+
body: JSON.stringify(payload)
|
|
193
|
+
}, { errorMessage: saveError }),
|
|
194
|
+
context: {
|
|
195
|
+
formId: WIDGET_VISIBILITY_CONTEXT_ID,
|
|
196
|
+
resourceKind: "dashboards.role_widget_visibility",
|
|
197
|
+
retryLastMutation
|
|
198
|
+
},
|
|
199
|
+
mutationPayload: payload
|
|
200
|
+
});
|
|
188
201
|
setOriginal(selected);
|
|
189
202
|
setOriginalMode("override");
|
|
190
203
|
setEffective(selected);
|
|
@@ -196,11 +209,19 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
|
|
|
196
209
|
mode,
|
|
197
210
|
widgetIds: selected
|
|
198
211
|
};
|
|
199
|
-
await
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
await runMutation({
|
|
213
|
+
operation: () => apiCallOrThrow("/api/dashboards/users/widgets", {
|
|
214
|
+
method: "PUT",
|
|
215
|
+
headers: { "content-type": "application/json" },
|
|
216
|
+
body: JSON.stringify(payload)
|
|
217
|
+
}, { errorMessage: saveError }),
|
|
218
|
+
context: {
|
|
219
|
+
formId: WIDGET_VISIBILITY_CONTEXT_ID,
|
|
220
|
+
resourceKind: "dashboards.user_widget_visibility",
|
|
221
|
+
retryLastMutation
|
|
222
|
+
},
|
|
223
|
+
mutationPayload: payload
|
|
224
|
+
});
|
|
204
225
|
setOriginal(selected);
|
|
205
226
|
if (mode === "inherit") {
|
|
206
227
|
const refreshed = await readApiResultOrThrow(
|
|
@@ -225,7 +246,7 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
|
|
|
225
246
|
} finally {
|
|
226
247
|
setSaving(false);
|
|
227
248
|
}
|
|
228
|
-
}, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId]);
|
|
249
|
+
}, [catalog.length, dirty, error, kind, loading, mode, organizationId, retryLastMutation, runMutation, selected, t, targetId, tenantId]);
|
|
229
250
|
React.useImperativeHandle(ref, () => ({ save }), [save]);
|
|
230
251
|
if (loading) {
|
|
231
252
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dashboards/components/WidgetVisibilityEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype WidgetCatalogItem = {\n id: string\n title: string\n description: string | null\n}\n\ntype RoleResponse = {\n widgetIds: string[]\n hasCustom: boolean\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype UserResponse = {\n mode: 'inherit' | 'override'\n widgetIds: string[]\n hasCustom: boolean\n effectiveWidgetIds: string[]\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype BaseProps = {\n tenantId?: string | null\n organizationId?: string | null\n preserveOnTenantChange?: boolean\n}\n\ntype RoleProps = BaseProps & {\n kind: 'role'\n targetId: string\n}\n\ntype UserProps = BaseProps & {\n kind: 'user'\n targetId: string\n}\n\ntype WidgetVisibilityEditorProps = RoleProps | UserProps\n\nexport type WidgetVisibilityEditorHandle = {\n save: () => Promise<void>\n}\n\nconst EMPTY: string[] = []\n\nfunction resolveWidgetText(\n t: (key: string, fallback: string) => string,\n id: string,\n field: 'title' | 'description',\n fallback: string,\n): string {\n const key1 = `${id}.${field}`\n const result1 = t(key1, '')\n if (result1 && result1 !== key1) return result1\n\n const key2 = `dashboard.widgets.${id}.${field}`\n const result2 = t(key2, '')\n if (result2 && result2 !== key2) return result2\n\n const dotIndex = id.lastIndexOf('.')\n if (dotIndex > 0) {\n const prefix = id.slice(0, dotIndex)\n const lastPart = id.slice(dotIndex + 1)\n const key3 = `${prefix}.widgets.${lastPart}.${field}`\n const result3 = t(key3, '')\n if (result3 && result3 !== key3) return result3\n }\n\n return fallback\n}\n\nexport const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHandle, WidgetVisibilityEditorProps>(function WidgetVisibilityEditor(props, ref) {\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n\n const resolveTitle = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'title', widget.title),\n [t],\n )\n\n const resolveDescription = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'description', widget.description || ''),\n [t],\n )\n const { kind, targetId, tenantId, organizationId, preserveOnTenantChange = false } = props\n const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const [selected, setSelected] = React.useState<string[]>(EMPTY)\n const [original, setOriginal] = React.useState<string[]>(EMPTY)\n const [mode, setMode] = React.useState<'inherit' | 'override'>('inherit')\n const [originalMode, setOriginalMode] = React.useState<'inherit' | 'override'>('inherit')\n const [effective, setEffective] = React.useState<string[]>(EMPTY)\n\n const dirty = React.useMemo(() => {\n if (kind === 'user') {\n if (mode !== originalMode) return true\n if (mode === 'override') return selected.join('|') !== original.join('|')\n return false\n }\n return selected.join('|') !== original.join('|')\n }, [kind, mode, original, originalMode, selected])\n\n const loadCatalog = React.useCallback(async () => {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/dashboards/widgets/catalog',\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const mapped = items\n .map((item: unknown): WidgetCatalogItem | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const id = typeof entry.id === 'string' ? entry.id : null\n if (!id || !id.length) return null\n const title =\n typeof entry.title === 'string' && entry.title.length ? entry.title : id\n const description =\n typeof entry.description === 'string' && entry.description.length ? entry.description : null\n return { id, title, description }\n })\n .filter((item: WidgetCatalogItem | null): item is WidgetCatalogItem => item !== null)\n setCatalog(mapped)\n }, [])\n\n const tenantIdRef = React.useRef(tenantId)\n React.useEffect(() => { tenantIdRef.current = tenantId }, [tenantId])\n const organizationIdRef = React.useRef(organizationId)\n React.useEffect(() => { organizationIdRef.current = organizationId }, [organizationId])\n const hasMountedRef = React.useRef(false)\n\n const loadRoleData = React.useCallback(async (forTenantId: string | null | undefined, forOrganizationId: string | null | undefined) => {\n const params = new URLSearchParams({ roleId: targetId })\n if (forTenantId) params.set('tenantId', forTenantId)\n if (forOrganizationId) params.set('organizationId', forOrganizationId)\n const data = await readApiResultOrThrow<RoleResponse>(\n `/api/dashboards/roles/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode('override')\n setOriginalMode('override')\n setEffective(ids)\n }, [targetId])\n\n const loadUserData = React.useCallback(async (forTenantId: string | null | undefined, forOrganizationId: string | null | undefined) => {\n const params = new URLSearchParams({ userId: targetId })\n if (forTenantId) params.set('tenantId', forTenantId)\n if (forOrganizationId) params.set('organizationId', forOrganizationId)\n const data = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode(data.mode || 'inherit')\n setOriginalMode(data.mode || 'inherit')\n setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : [])\n }, [targetId])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n await loadCatalog()\n if (kind === 'role') await loadRoleData(tenantIdRef.current, organizationIdRef.current)\n else await loadUserData(tenantIdRef.current, organizationIdRef.current)\n } catch (err) {\n console.error('Failed to load widget visibility data', err)\n if (!cancelled) {\n setError(tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.'))\n }\n } finally {\n if (!cancelled) {\n setLoading(false)\n hasMountedRef.current = true\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [kind, loadCatalog, loadRoleData, loadUserData])\n\n React.useEffect(() => {\n if (!hasMountedRef.current) return\n if (preserveOnTenantChange) return\n let cancelled = false\n async function refetch() {\n try {\n if (kind === 'role') await loadRoleData(tenantId, organizationId)\n else await loadUserData(tenantId, organizationId)\n } catch (err) {\n console.error('Failed to reload widget visibility data', err)\n }\n }\n refetch()\n return () => { cancelled = true }\n }, [tenantId, organizationId, kind, loadRoleData, loadUserData, preserveOnTenantChange])\n\n const toggle = React.useCallback((id: string) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n const resetSelections = React.useCallback(() => {\n setSelected(original)\n setMode(originalMode)\n }, [original, originalMode])\n\n const save = React.useCallback(async () => {\n if (loading) return\n if (error && catalog.length === 0) return\n if (!dirty) return\n setSaving(true)\n setError(null)\n try {\n const saveError = t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.')\n if (kind === 'role') {\n const payload = {\n roleId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n widgetIds: selected,\n }\n // optimistic-lock-exempt: per-role widget visibility preference\n await apiCallOrThrow('/api/dashboards/roles/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n setOriginalMode('override')\n setEffective(selected)\n } else {\n const payload = {\n userId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n mode,\n widgetIds: selected,\n }\n // optimistic-lock-exempt: per-user widget visibility preference\n await apiCallOrThrow('/api/dashboards/users/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n if (mode === 'inherit') {\n const refreshed = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?userId=${encodeURIComponent(targetId)}`,\n undefined,\n { errorMessage: saveError },\n )\n setEffective(Array.isArray(refreshed.effectiveWidgetIds) ? refreshed.effectiveWidgetIds : [])\n } else {\n setEffective(selected)\n }\n setOriginal(selected)\n setOriginalMode(mode)\n }\n try { flash(t('dashboards.widgets.flash.saved', 'Dashboard widgets updated'), 'success') } catch {}\n } catch (err) {\n console.error('Failed to save widget visibility', err)\n setError(t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.'))\n } finally {\n setSaving(false)\n }\n }, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId])\n\n React.useImperativeHandle(ref, () => ({ save }), [save])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('dashboards.widgets.loading', 'Loading widget options\u2026')}\n </div>\n )\n }\n\n if (error && catalog.length === 0) {\n return <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n }\n\n return (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n\n {kind === 'user' && (\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n name=\"widgetOverride\"\n value={mode}\n onValueChange={(next) => setMode(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('dashboards.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('dashboards.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n )}\n\n {kind === 'user' && mode === 'inherit' && (\n <div className=\"rounded-md border border-muted bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.mode.hint', 'This user currently inherits widgets from their assigned roles. Switch to override to customize.')}\n </div>\n )}\n\n {(kind === 'role' || mode === 'override') && (\n <div className=\"space-y-3\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => toggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{resolveTitle(widget)}</div>\n {widget.description ? <div className=\"mt-1 text-xs text-muted-foreground\">{resolveDescription(widget)}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {kind === 'user' && effective.length > 0 && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.effective', 'Effective widgets:')} {effective.map((id) => { const meta = catalog.find((m) => m.id === id); return meta ? resolveTitle(meta) : id }).join(', ')}\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={save} disabled={saving || !dirty}>\n {saving ? t('dashboards.widgets.saving', 'Saving\u2026') : t('dashboards.widgets.save', 'Save widgets')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={resetSelections} disabled={!dirty}>\n {t('dashboards.widgets.reset', 'Reset')}\n </Button>\n </div>\n </div>\n )\n})\n\nWidgetVisibilityEditor.displayName = 'WidgetVisibilityEditor'\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nconst WIDGET_VISIBILITY_CONTEXT_ID = 'dashboards-widget-visibility:save'\n\ntype WidgetCatalogItem = {\n id: string\n title: string\n description: string | null\n}\n\ntype RoleResponse = {\n widgetIds: string[]\n hasCustom: boolean\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype UserResponse = {\n mode: 'inherit' | 'override'\n widgetIds: string[]\n hasCustom: boolean\n effectiveWidgetIds: string[]\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype BaseProps = {\n tenantId?: string | null\n organizationId?: string | null\n preserveOnTenantChange?: boolean\n}\n\ntype RoleProps = BaseProps & {\n kind: 'role'\n targetId: string\n}\n\ntype UserProps = BaseProps & {\n kind: 'user'\n targetId: string\n}\n\ntype WidgetVisibilityEditorProps = RoleProps | UserProps\n\nexport type WidgetVisibilityEditorHandle = {\n save: () => Promise<void>\n}\n\nconst EMPTY: string[] = []\n\nfunction resolveWidgetText(\n t: (key: string, fallback: string) => string,\n id: string,\n field: 'title' | 'description',\n fallback: string,\n): string {\n const key1 = `${id}.${field}`\n const result1 = t(key1, '')\n if (result1 && result1 !== key1) return result1\n\n const key2 = `dashboard.widgets.${id}.${field}`\n const result2 = t(key2, '')\n if (result2 && result2 !== key2) return result2\n\n const dotIndex = id.lastIndexOf('.')\n if (dotIndex > 0) {\n const prefix = id.slice(0, dotIndex)\n const lastPart = id.slice(dotIndex + 1)\n const key3 = `${prefix}.widgets.${lastPart}.${field}`\n const result3 = t(key3, '')\n if (result3 && result3 !== key3) return result3\n }\n\n return fallback\n}\n\nexport const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHandle, WidgetVisibilityEditorProps>(function WidgetVisibilityEditor(props, ref) {\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n\n const resolveTitle = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'title', widget.title),\n [t],\n )\n\n const resolveDescription = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'description', widget.description || ''),\n [t],\n )\n const { kind, targetId, tenantId, organizationId, preserveOnTenantChange = false } = props\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: WIDGET_VISIBILITY_CONTEXT_ID,\n })\n const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const [selected, setSelected] = React.useState<string[]>(EMPTY)\n const [original, setOriginal] = React.useState<string[]>(EMPTY)\n const [mode, setMode] = React.useState<'inherit' | 'override'>('inherit')\n const [originalMode, setOriginalMode] = React.useState<'inherit' | 'override'>('inherit')\n const [effective, setEffective] = React.useState<string[]>(EMPTY)\n\n const dirty = React.useMemo(() => {\n if (kind === 'user') {\n if (mode !== originalMode) return true\n if (mode === 'override') return selected.join('|') !== original.join('|')\n return false\n }\n return selected.join('|') !== original.join('|')\n }, [kind, mode, original, originalMode, selected])\n\n const loadCatalog = React.useCallback(async () => {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/dashboards/widgets/catalog',\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const mapped = items\n .map((item: unknown): WidgetCatalogItem | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const id = typeof entry.id === 'string' ? entry.id : null\n if (!id || !id.length) return null\n const title =\n typeof entry.title === 'string' && entry.title.length ? entry.title : id\n const description =\n typeof entry.description === 'string' && entry.description.length ? entry.description : null\n return { id, title, description }\n })\n .filter((item: WidgetCatalogItem | null): item is WidgetCatalogItem => item !== null)\n setCatalog(mapped)\n }, [])\n\n const tenantIdRef = React.useRef(tenantId)\n React.useEffect(() => { tenantIdRef.current = tenantId }, [tenantId])\n const organizationIdRef = React.useRef(organizationId)\n React.useEffect(() => { organizationIdRef.current = organizationId }, [organizationId])\n const hasMountedRef = React.useRef(false)\n\n const loadRoleData = React.useCallback(async (forTenantId: string | null | undefined, forOrganizationId: string | null | undefined) => {\n const params = new URLSearchParams({ roleId: targetId })\n if (forTenantId) params.set('tenantId', forTenantId)\n if (forOrganizationId) params.set('organizationId', forOrganizationId)\n const data = await readApiResultOrThrow<RoleResponse>(\n `/api/dashboards/roles/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode('override')\n setOriginalMode('override')\n setEffective(ids)\n }, [targetId])\n\n const loadUserData = React.useCallback(async (forTenantId: string | null | undefined, forOrganizationId: string | null | undefined) => {\n const params = new URLSearchParams({ userId: targetId })\n if (forTenantId) params.set('tenantId', forTenantId)\n if (forOrganizationId) params.set('organizationId', forOrganizationId)\n const data = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode(data.mode || 'inherit')\n setOriginalMode(data.mode || 'inherit')\n setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : [])\n }, [targetId])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n await loadCatalog()\n if (kind === 'role') await loadRoleData(tenantIdRef.current, organizationIdRef.current)\n else await loadUserData(tenantIdRef.current, organizationIdRef.current)\n } catch (err) {\n console.error('Failed to load widget visibility data', err)\n if (!cancelled) {\n setError(tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.'))\n }\n } finally {\n if (!cancelled) {\n setLoading(false)\n hasMountedRef.current = true\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [kind, loadCatalog, loadRoleData, loadUserData])\n\n React.useEffect(() => {\n if (!hasMountedRef.current) return\n if (preserveOnTenantChange) return\n let cancelled = false\n async function refetch() {\n try {\n if (kind === 'role') await loadRoleData(tenantId, organizationId)\n else await loadUserData(tenantId, organizationId)\n } catch (err) {\n console.error('Failed to reload widget visibility data', err)\n }\n }\n refetch()\n return () => { cancelled = true }\n }, [tenantId, organizationId, kind, loadRoleData, loadUserData, preserveOnTenantChange])\n\n const toggle = React.useCallback((id: string) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n const resetSelections = React.useCallback(() => {\n setSelected(original)\n setMode(originalMode)\n }, [original, originalMode])\n\n const save = React.useCallback(async () => {\n if (loading) return\n if (error && catalog.length === 0) return\n if (!dirty) return\n setSaving(true)\n setError(null)\n try {\n const saveError = t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.')\n if (kind === 'role') {\n const payload = {\n roleId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n widgetIds: selected,\n }\n // optimistic-lock-exempt: per-role widget visibility preference\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/dashboards/roles/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError }),\n context: {\n formId: WIDGET_VISIBILITY_CONTEXT_ID,\n resourceKind: 'dashboards.role_widget_visibility',\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n setOriginal(selected)\n setOriginalMode('override')\n setEffective(selected)\n } else {\n const payload = {\n userId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n mode,\n widgetIds: selected,\n }\n // optimistic-lock-exempt: per-user widget visibility preference\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/dashboards/users/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError }),\n context: {\n formId: WIDGET_VISIBILITY_CONTEXT_ID,\n resourceKind: 'dashboards.user_widget_visibility',\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n setOriginal(selected)\n if (mode === 'inherit') {\n const refreshed = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?userId=${encodeURIComponent(targetId)}`,\n undefined,\n { errorMessage: saveError },\n )\n setEffective(Array.isArray(refreshed.effectiveWidgetIds) ? refreshed.effectiveWidgetIds : [])\n } else {\n setEffective(selected)\n }\n setOriginal(selected)\n setOriginalMode(mode)\n }\n try { flash(t('dashboards.widgets.flash.saved', 'Dashboard widgets updated'), 'success') } catch {}\n } catch (err) {\n console.error('Failed to save widget visibility', err)\n setError(t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.'))\n } finally {\n setSaving(false)\n }\n }, [catalog.length, dirty, error, kind, loading, mode, organizationId, retryLastMutation, runMutation, selected, t, targetId, tenantId])\n\n React.useImperativeHandle(ref, () => ({ save }), [save])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('dashboards.widgets.loading', 'Loading widget options\u2026')}\n </div>\n )\n }\n\n if (error && catalog.length === 0) {\n return <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n }\n\n return (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n\n {kind === 'user' && (\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n name=\"widgetOverride\"\n value={mode}\n onValueChange={(next) => setMode(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('dashboards.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('dashboards.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n )}\n\n {kind === 'user' && mode === 'inherit' && (\n <div className=\"rounded-md border border-muted bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.mode.hint', 'This user currently inherits widgets from their assigned roles. Switch to override to customize.')}\n </div>\n )}\n\n {(kind === 'role' || mode === 'override') && (\n <div className=\"space-y-3\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => toggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{resolveTitle(widget)}</div>\n {widget.description ? <div className=\"mt-1 text-xs text-muted-foreground\">{resolveDescription(widget)}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {kind === 'user' && effective.length > 0 && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.effective', 'Effective widgets:')} {effective.map((id) => { const meta = catalog.find((m) => m.id === id); return meta ? resolveTitle(meta) : id }).join(', ')}\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={save} disabled={saving || !dirty}>\n {saving ? t('dashboards.widgets.saving', 'Saving\u2026') : t('dashboards.widgets.save', 'Save widgets')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={resetSelections} disabled={!dirty}>\n {t('dashboards.widgets.reset', 'Reset')}\n </Button>\n </div>\n </div>\n )\n})\n\nWidgetVisibilityEditor.displayName = 'WidgetVisibilityEditor'\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["WidgetVisibilityEditor"]
|
|
7
7
|
}
|
|
@@ -4,6 +4,10 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
6
|
import { SyncMapping } from "@open-mercato/core/modules/data_sync/data/entities";
|
|
7
|
+
import {
|
|
8
|
+
runCrudMutationGuardAfterSuccess,
|
|
9
|
+
validateCrudMutationGuard
|
|
10
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
7
11
|
const idParamsSchema = z.object({ id: z.string().uuid() });
|
|
8
12
|
const updateMappingSchema = z.object({
|
|
9
13
|
mapping: z.record(z.string(), z.unknown())
|
|
@@ -87,8 +91,35 @@ async function PUT(req, ctx) {
|
|
|
87
91
|
if (!mapping) {
|
|
88
92
|
return NextResponse.json({ error: "Mapping not found" }, { status: 404 });
|
|
89
93
|
}
|
|
94
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
95
|
+
tenantId: scope.tenantId,
|
|
96
|
+
organizationId: scope.organizationId,
|
|
97
|
+
userId: auth.sub,
|
|
98
|
+
resourceKind: "data_sync.mapping",
|
|
99
|
+
resourceId: mapping.id,
|
|
100
|
+
operation: "update",
|
|
101
|
+
requestMethod: req.method,
|
|
102
|
+
requestHeaders: req.headers,
|
|
103
|
+
mutationPayload: parsedBody.data
|
|
104
|
+
});
|
|
105
|
+
if (guardResult && !guardResult.ok) {
|
|
106
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
107
|
+
}
|
|
90
108
|
mapping.mapping = parsedBody.data.mapping;
|
|
91
109
|
await em.flush();
|
|
110
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
111
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
112
|
+
tenantId: scope.tenantId,
|
|
113
|
+
organizationId: scope.organizationId,
|
|
114
|
+
userId: auth.sub,
|
|
115
|
+
resourceKind: "data_sync.mapping",
|
|
116
|
+
resourceId: mapping.id,
|
|
117
|
+
operation: "update",
|
|
118
|
+
requestMethod: req.method,
|
|
119
|
+
requestHeaders: req.headers,
|
|
120
|
+
metadata: guardResult.metadata ?? null
|
|
121
|
+
});
|
|
122
|
+
}
|
|
92
123
|
return NextResponse.json({
|
|
93
124
|
id: mapping.id,
|
|
94
125
|
integrationId: mapping.integrationId,
|
|
@@ -123,8 +154,36 @@ async function DELETE(req, ctx) {
|
|
|
123
154
|
if (!mapping) {
|
|
124
155
|
return NextResponse.json({ error: "Mapping not found" }, { status: 404 });
|
|
125
156
|
}
|
|
157
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
158
|
+
tenantId: scope.tenantId,
|
|
159
|
+
organizationId: scope.organizationId,
|
|
160
|
+
userId: auth.sub,
|
|
161
|
+
resourceKind: "data_sync.mapping",
|
|
162
|
+
resourceId: mapping.id,
|
|
163
|
+
operation: "delete",
|
|
164
|
+
requestMethod: req.method,
|
|
165
|
+
requestHeaders: req.headers,
|
|
166
|
+
mutationPayload: null
|
|
167
|
+
});
|
|
168
|
+
if (guardResult && !guardResult.ok) {
|
|
169
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
170
|
+
}
|
|
171
|
+
const mappingId = mapping.id;
|
|
126
172
|
em.remove(mapping);
|
|
127
173
|
await em.flush();
|
|
174
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
175
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
176
|
+
tenantId: scope.tenantId,
|
|
177
|
+
organizationId: scope.organizationId,
|
|
178
|
+
userId: auth.sub,
|
|
179
|
+
resourceKind: "data_sync.mapping",
|
|
180
|
+
resourceId: mappingId,
|
|
181
|
+
operation: "delete",
|
|
182
|
+
requestMethod: req.method,
|
|
183
|
+
requestHeaders: req.headers,
|
|
184
|
+
metadata: guardResult.metadata ?? null
|
|
185
|
+
});
|
|
186
|
+
}
|
|
128
187
|
return NextResponse.json({ deleted: true });
|
|
129
188
|
}
|
|
130
189
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/data_sync/api/mappings/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'\n\nconst idParamsSchema = z.object({ id: z.string().uuid() })\n\nconst updateMappingSchema = z.object({\n mapping: z.record(z.string(), z.unknown()),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n PUT: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n DELETE: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Get, update, or delete a field mapping',\n}\n\nasync function resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n return idParamsSchema.safeParse(rawParams)\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedParams = await resolveParams(ctx)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid mapping id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const mapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n id: parsedParams.data.id,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!mapping) {\n return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n id: mapping.id,\n integrationId: mapping.integrationId,\n entityType: mapping.entityType,\n mapping: mapping.mapping,\n createdAt: mapping.createdAt.toISOString(),\n updatedAt: mapping.updatedAt.toISOString(),\n })\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedParams = await resolveParams(ctx)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid mapping id' }, { status: 400 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsedBody = updateMappingSchema.safeParse(payload)\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const mapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n id: parsedParams.data.id,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!mapping) {\n return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })\n }\n\n mapping.mapping = parsedBody.data.mapping\n await em.flush()\n\n return NextResponse.json({\n id: mapping.id,\n integrationId: mapping.integrationId,\n entityType: mapping.entityType,\n mapping: mapping.mapping,\n updatedAt: mapping.updatedAt.toISOString(),\n })\n}\n\nexport async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedParams = await resolveParams(ctx)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid mapping id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const mapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n id: parsedParams.data.id,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!mapping) {\n return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })\n }\n\n em.remove(mapping)\n await em.flush()\n\n return NextResponse.json({ deleted: true })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,6BAA6B;AACtC,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nconst idParamsSchema = z.object({ id: z.string().uuid() })\n\nconst updateMappingSchema = z.object({\n mapping: z.record(z.string(), z.unknown()),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n PUT: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n DELETE: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Get, update, or delete a field mapping',\n}\n\nasync function resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n return idParamsSchema.safeParse(rawParams)\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedParams = await resolveParams(ctx)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid mapping id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const mapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n id: parsedParams.data.id,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!mapping) {\n return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n id: mapping.id,\n integrationId: mapping.integrationId,\n entityType: mapping.entityType,\n mapping: mapping.mapping,\n createdAt: mapping.createdAt.toISOString(),\n updatedAt: mapping.updatedAt.toISOString(),\n })\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedParams = await resolveParams(ctx)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid mapping id' }, { status: 400 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsedBody = updateMappingSchema.safeParse(payload)\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const mapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n id: parsedParams.data.id,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!mapping) {\n return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: mapping.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsedBody.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n mapping.mapping = parsedBody.data.mapping\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: mapping.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: mapping.id,\n integrationId: mapping.integrationId,\n entityType: mapping.entityType,\n mapping: mapping.mapping,\n updatedAt: mapping.updatedAt.toISOString(),\n })\n}\n\nexport async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const parsedParams = await resolveParams(ctx)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid mapping id' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const mapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n id: parsedParams.data.id,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!mapping) {\n return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: mapping.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const mappingId = mapping.id\n em.remove(mapping)\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: mappingId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ deleted: true })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,10 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { findAndCountWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
6
|
import { SyncMapping } from "@open-mercato/core/modules/data_sync/data/entities";
|
|
7
|
+
import {
|
|
8
|
+
runCrudMutationGuardAfterSuccess,
|
|
9
|
+
validateCrudMutationGuard
|
|
10
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
7
11
|
const listMappingsQuerySchema = z.object({
|
|
8
12
|
integrationId: z.string().min(1).optional(),
|
|
9
13
|
entityType: z.string().min(1).optional(),
|
|
@@ -98,9 +102,36 @@ async function POST(req) {
|
|
|
98
102
|
void 0,
|
|
99
103
|
scope
|
|
100
104
|
);
|
|
105
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
106
|
+
tenantId: scope.tenantId,
|
|
107
|
+
organizationId: scope.organizationId,
|
|
108
|
+
userId: auth.sub,
|
|
109
|
+
resourceKind: "data_sync.mapping",
|
|
110
|
+
resourceId: existing?.id ?? scope.organizationId,
|
|
111
|
+
operation: existing ? "update" : "create",
|
|
112
|
+
requestMethod: req.method,
|
|
113
|
+
requestHeaders: req.headers,
|
|
114
|
+
mutationPayload: parsed.data
|
|
115
|
+
});
|
|
116
|
+
if (guardResult && !guardResult.ok) {
|
|
117
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
118
|
+
}
|
|
101
119
|
if (existing) {
|
|
102
120
|
existing.mapping = parsed.data.mapping;
|
|
103
121
|
await em.flush();
|
|
122
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
123
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
124
|
+
tenantId: scope.tenantId,
|
|
125
|
+
organizationId: scope.organizationId,
|
|
126
|
+
userId: auth.sub,
|
|
127
|
+
resourceKind: "data_sync.mapping",
|
|
128
|
+
resourceId: existing.id,
|
|
129
|
+
operation: "update",
|
|
130
|
+
requestMethod: req.method,
|
|
131
|
+
requestHeaders: req.headers,
|
|
132
|
+
metadata: guardResult.metadata ?? null
|
|
133
|
+
});
|
|
134
|
+
}
|
|
104
135
|
return NextResponse.json({
|
|
105
136
|
id: existing.id,
|
|
106
137
|
integrationId: existing.integrationId,
|
|
@@ -116,6 +147,19 @@ async function POST(req) {
|
|
|
116
147
|
tenantId: scope.tenantId
|
|
117
148
|
});
|
|
118
149
|
await em.persist(created).flush();
|
|
150
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
151
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
152
|
+
tenantId: scope.tenantId,
|
|
153
|
+
organizationId: scope.organizationId,
|
|
154
|
+
userId: auth.sub,
|
|
155
|
+
resourceKind: "data_sync.mapping",
|
|
156
|
+
resourceId: created.id,
|
|
157
|
+
operation: "create",
|
|
158
|
+
requestMethod: req.method,
|
|
159
|
+
requestHeaders: req.headers,
|
|
160
|
+
metadata: guardResult.metadata ?? null
|
|
161
|
+
});
|
|
162
|
+
}
|
|
119
163
|
return NextResponse.json({
|
|
120
164
|
id: created.id,
|
|
121
165
|
integrationId: created.integrationId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/data_sync/api/mappings/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'\n\nconst listMappingsQuerySchema = z.object({\n integrationId: z.string().min(1).optional(),\n entityType: z.string().min(1).optional(),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\nconst createMappingSchema = z.object({\n integrationId: z.string().min(1),\n entityType: z.string().min(1),\n mapping: z.record(z.string(), z.unknown()),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'List or create field mappings',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listMappingsQuerySchema.safeParse({\n integrationId: url.searchParams.get('integrationId') ?? undefined,\n entityType: url.searchParams.get('entityType') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const where: FilterQuery<SyncMapping> = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n if (parsed.data.integrationId) where.integrationId = parsed.data.integrationId\n if (parsed.data.entityType) where.entityType = parsed.data.entityType\n\n const [items, total] = await findAndCountWithDecryption(\n em,\n SyncMapping,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: parsed.data.pageSize,\n offset: (parsed.data.page - 1) * parsed.data.pageSize,\n },\n scope,\n )\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n integrationId: item.integrationId,\n entityType: item.entityType,\n mapping: item.mapping,\n createdAt: item.createdAt.toISOString(),\n updatedAt: item.updatedAt.toISOString(),\n })),\n total,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsed = createMappingSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const existing = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (existing) {\n existing.mapping = parsed.data.mapping\n await em.flush()\n return NextResponse.json({\n id: existing.id,\n integrationId: existing.integrationId,\n entityType: existing.entityType,\n mapping: existing.mapping,\n })\n }\n\n const created = em.create(SyncMapping, {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n mapping: parsed.data.mapping,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persist(created).flush()\n\n return NextResponse.json({\n id: created.id,\n integrationId: created.integrationId,\n entityType: created.entityType,\n mapping: created.mapping,\n }, { status: 201 })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,4BAA4B,6BAA6B;AAClE,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nconst listMappingsQuerySchema = z.object({\n integrationId: z.string().min(1).optional(),\n entityType: z.string().min(1).optional(),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\nconst createMappingSchema = z.object({\n integrationId: z.string().min(1),\n entityType: z.string().min(1),\n mapping: z.record(z.string(), z.unknown()),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'List or create field mappings',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listMappingsQuerySchema.safeParse({\n integrationId: url.searchParams.get('integrationId') ?? undefined,\n entityType: url.searchParams.get('entityType') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const where: FilterQuery<SyncMapping> = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n if (parsed.data.integrationId) where.integrationId = parsed.data.integrationId\n if (parsed.data.entityType) where.entityType = parsed.data.entityType\n\n const [items, total] = await findAndCountWithDecryption(\n em,\n SyncMapping,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: parsed.data.pageSize,\n offset: (parsed.data.page - 1) * parsed.data.pageSize,\n },\n scope,\n )\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n integrationId: item.integrationId,\n entityType: item.entityType,\n mapping: item.mapping,\n createdAt: item.createdAt.toISOString(),\n updatedAt: item.updatedAt.toISOString(),\n })),\n total,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n totalPages: Math.max(1, Math.ceil(total / parsed.data.pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsed = createMappingSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const existing = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: existing?.id ?? scope.organizationId,\n operation: existing ? 'update' : 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (existing) {\n existing.mapping = parsed.data.mapping\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: existing.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: existing.id,\n integrationId: existing.integrationId,\n entityType: existing.entityType,\n mapping: existing.mapping,\n })\n }\n\n const created = em.create(SyncMapping, {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n mapping: parsed.data.mapping,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persist(created).flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.mapping',\n resourceId: created.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: created.id,\n integrationId: created.integrationId,\n entityType: created.entityType,\n mapping: created.mapping,\n }, { status: 201 })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,10 @@ import { getIntegration } from "@open-mercato/shared/modules/integrations/types"
|
|
|
6
6
|
import { runSyncSchema } from "../data/validators.js";
|
|
7
7
|
import { startDataSyncRun } from "../lib/start-run.js";
|
|
8
8
|
import { getDataSyncAdapter } from "../lib/adapter-registry.js";
|
|
9
|
+
import {
|
|
10
|
+
runCrudMutationGuardAfterSuccess,
|
|
11
|
+
validateCrudMutationGuard
|
|
12
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
9
13
|
const metadata = {
|
|
10
14
|
POST: { requireAuth: true, requireFeatures: ["data_sync.run"] }
|
|
11
15
|
};
|
|
@@ -65,6 +69,20 @@ async function POST(req) {
|
|
|
65
69
|
if (overlap) {
|
|
66
70
|
return NextResponse.json({ error: "A sync run is already in progress for this integration and entity direction" }, { status: 409 });
|
|
67
71
|
}
|
|
72
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
73
|
+
tenantId: auth.tenantId,
|
|
74
|
+
organizationId: scope.organizationId,
|
|
75
|
+
userId: auth.sub,
|
|
76
|
+
resourceKind: "data_sync.run",
|
|
77
|
+
resourceId: parsed.data.integrationId,
|
|
78
|
+
operation: "custom",
|
|
79
|
+
requestMethod: req.method,
|
|
80
|
+
requestHeaders: req.headers,
|
|
81
|
+
mutationPayload: parsed.data
|
|
82
|
+
});
|
|
83
|
+
if (guardResult && !guardResult.ok) {
|
|
84
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
85
|
+
}
|
|
68
86
|
const cursor = parsed.data.fullSync ? null : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope);
|
|
69
87
|
const { run, progressJob } = await startDataSyncRun({
|
|
70
88
|
syncRunService,
|
|
@@ -82,6 +100,19 @@ async function POST(req) {
|
|
|
82
100
|
batchSize: parsed.data.batchSize
|
|
83
101
|
}
|
|
84
102
|
});
|
|
103
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
104
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
105
|
+
tenantId: auth.tenantId,
|
|
106
|
+
organizationId: scope.organizationId,
|
|
107
|
+
userId: auth.sub,
|
|
108
|
+
resourceKind: "data_sync.run",
|
|
109
|
+
resourceId: run.id,
|
|
110
|
+
operation: "custom",
|
|
111
|
+
requestMethod: req.method,
|
|
112
|
+
requestHeaders: req.headers,
|
|
113
|
+
metadata: guardResult.metadata ?? null
|
|
114
|
+
});
|
|
115
|
+
}
|
|
85
116
|
return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 });
|
|
86
117
|
} catch (error) {
|
|
87
118
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/data_sync/api/run.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { runSyncSchema } from '../data/validators'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { getDataSyncAdapter } from '../lib/adapter-registry'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Start a data sync run',\n}\n\nexport async function POST(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = runSyncSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n\n const scope = {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }\n\n const integration = getIntegration(parsed.data.integrationId)\n if (!integration?.providerKey) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const adapter = getDataSyncAdapter(integration.providerKey)\n if (!adapter) {\n return NextResponse.json({ error: 'No registered sync adapter for provider' }, { status: 404 })\n }\n if (adapter.runMode === 'provider') {\n return NextResponse.json(\n {\n error: 'This integration must be started from its provider-specific import flow.',\n settingsPath: `/backend/integrations/${encodeURIComponent(parsed.data.integrationId)}`,\n },\n { status: 422 },\n )\n }\n\n if (!adapter.supportedEntities.includes(parsed.data.entityType)) {\n return NextResponse.json({ error: 'Unsupported entity type for this integration' }, { status: 422 })\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(parsed.data.integrationId, scope)\n if (!integrationEnabled) {\n return NextResponse.json({ error: 'Integration is disabled' }, { status: 409 })\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n parsed.data.integrationId,\n parsed.data.entityType,\n parsed.data.direction,\n scope,\n )\n if (overlap) {\n return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })\n }\n\n const cursor = parsed.data.fullSync\n ? null\n : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope)\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n direction: parsed.data.direction,\n cursor,\n triggeredBy: parsed.data.triggeredBy ?? auth.sub,\n batchSize: parsed.data.batchSize,\n },\n })\n\n return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n console.error('[data_sync.run] unhandled error', { message, stack })\n return NextResponse.json(\n { error: 'Failed to start data sync run.' },\n { status: 500 },\n )\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAI/B,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { runSyncSchema } from '../data/validators'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { getDataSyncAdapter } from '../lib/adapter-registry'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Start a data sync run',\n}\n\nexport async function POST(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = runSyncSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n\n const scope = {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }\n\n const integration = getIntegration(parsed.data.integrationId)\n if (!integration?.providerKey) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const adapter = getDataSyncAdapter(integration.providerKey)\n if (!adapter) {\n return NextResponse.json({ error: 'No registered sync adapter for provider' }, { status: 404 })\n }\n if (adapter.runMode === 'provider') {\n return NextResponse.json(\n {\n error: 'This integration must be started from its provider-specific import flow.',\n settingsPath: `/backend/integrations/${encodeURIComponent(parsed.data.integrationId)}`,\n },\n { status: 422 },\n )\n }\n\n if (!adapter.supportedEntities.includes(parsed.data.entityType)) {\n return NextResponse.json({ error: 'Unsupported entity type for this integration' }, { status: 422 })\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(parsed.data.integrationId, scope)\n if (!integrationEnabled) {\n return NextResponse.json({ error: 'Integration is disabled' }, { status: 409 })\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n parsed.data.integrationId,\n parsed.data.entityType,\n parsed.data.direction,\n scope,\n )\n if (overlap) {\n return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.run',\n resourceId: parsed.data.integrationId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const cursor = parsed.data.fullSync\n ? null\n : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope)\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n direction: parsed.data.direction,\n cursor,\n triggeredBy: parsed.data.triggeredBy ?? auth.sub,\n batchSize: parsed.data.batchSize,\n },\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: 'data_sync.run',\n resourceId: run.id,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n console.error('[data_sync.run] unhandled error', { message, stack })\n return NextResponse.json(\n { error: 'Failed to start data sync run.' },\n { status: 500 },\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,10 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import {
|
|
6
|
+
runCrudMutationGuardAfterSuccess,
|
|
7
|
+
validateCrudMutationGuard
|
|
8
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
5
9
|
const paramsSchema = z.object({ id: z.string().uuid() });
|
|
6
10
|
const metadata = {
|
|
7
11
|
POST: { requireAuth: true, requireFeatures: ["data_sync.run"] }
|
|
@@ -33,6 +37,20 @@ async function POST(req, ctx) {
|
|
|
33
37
|
if (run.status !== "running" && run.status !== "pending") {
|
|
34
38
|
return NextResponse.json({ error: "Only pending or running runs can be cancelled" }, { status: 409 });
|
|
35
39
|
}
|
|
40
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
41
|
+
tenantId: auth.tenantId,
|
|
42
|
+
organizationId: scope.organizationId,
|
|
43
|
+
userId: auth.sub,
|
|
44
|
+
resourceKind: "data_sync.run",
|
|
45
|
+
resourceId: run.id,
|
|
46
|
+
operation: "custom",
|
|
47
|
+
requestMethod: req.method,
|
|
48
|
+
requestHeaders: req.headers,
|
|
49
|
+
mutationPayload: { action: "cancel" }
|
|
50
|
+
});
|
|
51
|
+
if (guardResult && !guardResult.ok) {
|
|
52
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
53
|
+
}
|
|
36
54
|
const progressCtx = {
|
|
37
55
|
tenantId: auth.tenantId,
|
|
38
56
|
organizationId: auth.orgId,
|
|
@@ -64,6 +82,19 @@ async function POST(req, ctx) {
|
|
|
64
82
|
summary: "The sync run was cancelled before completion."
|
|
65
83
|
}
|
|
66
84
|
}, scope);
|
|
85
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
86
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
87
|
+
tenantId: auth.tenantId,
|
|
88
|
+
organizationId: scope.organizationId,
|
|
89
|
+
userId: auth.sub,
|
|
90
|
+
resourceKind: "data_sync.run",
|
|
91
|
+
resourceId: run.id,
|
|
92
|
+
operation: "custom",
|
|
93
|
+
requestMethod: req.method,
|
|
94
|
+
requestHeaders: req.headers,
|
|
95
|
+
metadata: guardResult.metadata ?? null
|
|
96
|
+
});
|
|
97
|
+
}
|
|
67
98
|
return NextResponse.json({ ok: true });
|
|
68
99
|
}
|
|
69
100
|
export {
|