@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 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.
Files changed (198) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
  3. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  4. package/dist/modules/currencies/backend/currencies/page.js +63 -27
  5. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  6. package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
  7. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  8. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
  9. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  10. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
  11. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  12. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
  13. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  14. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
  15. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  16. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
  17. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  18. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
  19. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  20. package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
  21. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  22. package/dist/modules/customers/components/DictionarySettings.js +57 -31
  23. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  24. package/dist/modules/customers/components/PipelineSettings.js +156 -76
  25. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  26. package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
  27. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  28. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
  29. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  30. package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
  31. package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
  32. package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
  33. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  34. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
  35. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
  36. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
  37. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  38. package/dist/modules/customers/lib/calendar/labels.js +33 -0
  39. package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
  40. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
  41. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  42. package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
  43. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  44. package/dist/modules/data_sync/api/mappings/route.js +44 -0
  45. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  46. package/dist/modules/data_sync/api/run.js +31 -0
  47. package/dist/modules/data_sync/api/run.js.map +2 -2
  48. package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
  49. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  50. package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
  51. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  52. package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
  53. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  54. package/dist/modules/data_sync/api/schedules/route.js +33 -4
  55. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  56. package/dist/modules/directory/api/organization-branding/route.js +54 -2
  57. package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
  58. package/dist/modules/directory/backend/directory/branding/page.js +15 -9
  59. package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
  60. package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
  61. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  62. package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
  63. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  64. package/dist/modules/entities/api/definitions.batch.js +15 -0
  65. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  66. package/dist/modules/entities/api/definitions.js +26 -0
  67. package/dist/modules/entities/api/definitions.js.map +2 -2
  68. package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
  69. package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
  70. package/dist/modules/entities/api/definitions.restore.js +15 -0
  71. package/dist/modules/entities/api/definitions.restore.js.map +2 -2
  72. package/dist/modules/entities/api/entities.js +31 -3
  73. package/dist/modules/entities/api/entities.js.map +2 -2
  74. package/dist/modules/entities/api/records.js +18 -0
  75. package/dist/modules/entities/api/records.js.map +2 -2
  76. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
  77. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  78. package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
  79. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  80. package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
  81. package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
  82. package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
  83. package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
  84. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
  85. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
  86. package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
  87. package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
  88. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
  89. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  90. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
  91. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  92. package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
  93. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  94. package/dist/modules/payment_gateways/api/capture/route.js +37 -0
  95. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  96. package/dist/modules/payment_gateways/api/guards.js +31 -0
  97. package/dist/modules/payment_gateways/api/guards.js.map +7 -0
  98. package/dist/modules/payment_gateways/api/refund/route.js +37 -0
  99. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  100. package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
  101. package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
  102. package/dist/modules/planner/api/availability-date-specific.js +11 -1
  103. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  104. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
  105. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  106. package/dist/modules/planner/commands/availability-date-specific.js +16 -0
  107. package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
  108. package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
  109. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  110. package/dist/modules/query_index/api/purge.js +35 -3
  111. package/dist/modules/query_index/api/purge.js.map +2 -2
  112. package/dist/modules/query_index/api/reindex.js +41 -3
  113. package/dist/modules/query_index/api/reindex.js.map +2 -2
  114. package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
  115. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  116. package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
  117. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  118. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
  119. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
  120. package/dist/modules/shipping_carriers/data/validators.js +6 -1
  121. package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
  122. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
  123. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
  124. package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
  125. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
  126. package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
  127. package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
  128. package/dist/modules/translations/components/TranslationManager.js +49 -20
  129. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  130. package/package.json +7 -7
  131. package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
  132. package/src/modules/currencies/backend/currencies/page.tsx +68 -29
  133. package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
  134. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
  135. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
  136. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
  137. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
  138. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
  139. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
  140. package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
  141. package/src/modules/customers/components/DictionarySettings.tsx +63 -29
  142. package/src/modules/customers/components/PipelineSettings.tsx +165 -80
  143. package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
  144. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
  145. package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
  146. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
  147. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
  148. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
  149. package/src/modules/customers/i18n/de.json +11 -7
  150. package/src/modules/customers/i18n/en.json +6 -2
  151. package/src/modules/customers/i18n/es.json +11 -7
  152. package/src/modules/customers/i18n/pl.json +11 -7
  153. package/src/modules/customers/lib/calendar/labels.ts +42 -0
  154. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
  155. package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
  156. package/src/modules/data_sync/api/mappings/route.ts +48 -0
  157. package/src/modules/data_sync/api/run.ts +33 -0
  158. package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
  159. package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
  160. package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
  161. package/src/modules/data_sync/api/schedules/route.ts +36 -4
  162. package/src/modules/directory/api/organization-branding/route.ts +61 -0
  163. package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
  164. package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
  165. package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
  166. package/src/modules/entities/api/definitions.batch.ts +17 -0
  167. package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
  168. package/src/modules/entities/api/definitions.restore.ts +17 -0
  169. package/src/modules/entities/api/definitions.ts +30 -0
  170. package/src/modules/entities/api/entities.ts +35 -3
  171. package/src/modules/entities/api/records.ts +20 -0
  172. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
  173. package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
  174. package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
  175. package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
  176. package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
  177. package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
  178. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
  179. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
  180. package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
  181. package/src/modules/payment_gateways/api/capture/route.ts +39 -0
  182. package/src/modules/payment_gateways/api/guards.ts +59 -0
  183. package/src/modules/payment_gateways/api/refund/route.ts +39 -0
  184. package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
  185. package/src/modules/planner/api/availability-date-specific.ts +10 -0
  186. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
  187. package/src/modules/planner/commands/availability-date-specific.ts +24 -0
  188. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
  189. package/src/modules/query_index/api/purge.ts +37 -3
  190. package/src/modules/query_index/api/reindex.ts +43 -3
  191. package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
  192. package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
  193. package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
  194. package/src/modules/shipping_carriers/data/validators.ts +5 -0
  195. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
  196. package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
  197. package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
  198. package/src/modules/translations/components/TranslationManager.tsx +65 -21
@@ -3,6 +3,10 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexPurgeRequestSchema } from "./openapi.js";
5
5
  import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
6
+ import {
7
+ runCrudMutationGuardAfterSuccess,
8
+ validateCrudMutationGuard
9
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
6
10
  const metadata = {
7
11
  POST: { requireAuth: true, requireFeatures: ["query_index.purge"] }
8
12
  };
@@ -12,13 +16,28 @@ async function POST(req) {
12
16
  const body = await req.json().catch(() => ({}));
13
17
  const entityType = String(body?.entityType || "");
14
18
  if (!entityType) return NextResponse.json({ error: "Missing entityType" }, { status: 400 });
15
- const { resolve } = await createRequestContainer();
19
+ const container = await createRequestContainer();
16
20
  let em = null;
17
21
  try {
18
- em = resolve("em");
22
+ em = container.resolve("em");
19
23
  } catch {
20
24
  }
21
- const bus = resolve("eventBus");
25
+ const bus = container.resolve("eventBus");
26
+ const guardUserId = typeof auth.sub === "string" ? auth.sub : "";
27
+ const guardResult = await validateCrudMutationGuard(container, {
28
+ tenantId: auth.tenantId,
29
+ organizationId: auth.orgId,
30
+ userId: guardUserId,
31
+ resourceKind: "query_index",
32
+ resourceId: entityType,
33
+ operation: "custom",
34
+ requestMethod: req.method,
35
+ requestHeaders: req.headers,
36
+ mutationPayload: { entityType }
37
+ });
38
+ if (guardResult && !guardResult.ok) {
39
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
40
+ }
22
41
  await recordIndexerLog(
23
42
  { em: em ?? void 0 },
24
43
  {
@@ -47,6 +66,19 @@ async function POST(req) {
47
66
  organizationId: auth.orgId ?? null
48
67
  }
49
68
  ).catch(() => void 0);
69
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
70
+ await runCrudMutationGuardAfterSuccess(container, {
71
+ tenantId: auth.tenantId,
72
+ organizationId: auth.orgId,
73
+ userId: guardUserId,
74
+ resourceKind: "query_index",
75
+ resourceId: entityType,
76
+ operation: "custom",
77
+ requestMethod: req.method,
78
+ requestHeaders: req.headers,
79
+ metadata: guardResult.metadata ?? null
80
+ });
81
+ }
50
82
  } catch (error) {
51
83
  await recordIndexerLog(
52
84
  { em: em ?? void 0 },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/api/purge.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 type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexPurgeRequestSchema } from './openapi'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['query_index.purge'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({})) as any\n const entityType = String(body?.entityType || '')\n if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })\n\n const { resolve } = await createRequestContainer()\n let em: any | null = null\n try {\n em = resolve('em')\n } catch {}\n const bus = resolve('eventBus') as any\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.purge',\n message: `Purge requested for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n },\n ).catch(() => undefined)\n try {\n await bus.emitEvent(\n 'query_index.purge',\n { entityType, organizationId: auth.orgId, tenantId: auth.tenantId },\n { persistent: true, deliverInline: false },\n )\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.purge',\n message: `Purge queued for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n },\n ).catch(() => undefined)\n } catch (error) {\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.purge',\n level: 'warn',\n message: `Failed to queue purge for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: { error: error instanceof Error ? error.message : String(error) },\n },\n ).catch(() => undefined)\n throw error\n }\n return NextResponse.json({ ok: true })\n}\n\nconst queryIndexPurgeDoc: OpenApiMethodDoc = {\n summary: 'Purge query index records',\n description: 'Queues a purge job to remove indexed records for an entity type within the active scope.',\n tags: [queryIndexTag],\n requestBody: {\n contentType: 'application/json',\n schema: queryIndexPurgeRequestSchema,\n description: 'Entity identifier whose index entries should be removed.',\n },\n responses: [\n { status: 200, description: 'Purge job accepted.', schema: queryIndexOkSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity type', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Queue a query index purge',\n methods: {\n POST: queryIndexPurgeDoc,\n },\n}\n"],
5
- "mappings": "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",
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 type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexPurgeRequestSchema } from './openapi'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['query_index.purge'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({})) as any\n const entityType = String(body?.entityType || '')\n if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })\n\n const container = await createRequestContainer()\n let em: any | null = null\n try {\n em = container.resolve('em')\n } catch {}\n const bus = container.resolve('eventBus') as any\n\n const guardUserId = typeof auth.sub === 'string' ? auth.sub : ''\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { entityType },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.purge',\n message: `Purge requested for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n },\n ).catch(() => undefined)\n try {\n await bus.emitEvent(\n 'query_index.purge',\n { entityType, organizationId: auth.orgId, tenantId: auth.tenantId },\n { persistent: true, deliverInline: false },\n )\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.purge',\n message: `Purge queued for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n },\n ).catch(() => undefined)\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n } catch (error) {\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.purge',\n level: 'warn',\n message: `Failed to queue purge for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: { error: error instanceof Error ? error.message : String(error) },\n },\n ).catch(() => undefined)\n throw error\n }\n return NextResponse.json({ ok: true })\n}\n\nconst queryIndexPurgeDoc: OpenApiMethodDoc = {\n summary: 'Purge query index records',\n description: 'Queues a purge job to remove indexed records for an entity type within the active scope.',\n tags: [queryIndexTag],\n requestBody: {\n contentType: 'application/json',\n schema: queryIndexPurgeRequestSchema,\n description: 'Entity identifier whose index entries should be removed.',\n },\n responses: [\n { status: 200, description: 'Purge job accepted.', schema: queryIndexOkSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity type', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Queue a query index purge',\n methods: {\n POST: queryIndexPurgeDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,10 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexReindexRequestSchema } from "./openapi.js";
5
5
  import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
6
6
  import { isValidEntityIdShape } from "@open-mercato/shared/lib/query/engine";
7
+ import {
8
+ runCrudMutationGuardAfterSuccess,
9
+ validateCrudMutationGuard
10
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
7
11
  const metadata = {
8
12
  POST: { requireAuth: true, requireFeatures: ["query_index.reindex"] }
9
13
  };
@@ -25,15 +29,36 @@ async function POST(req) {
25
29
  if (partitionIndex !== void 0 && partitionIndex >= partitionCount) {
26
30
  return NextResponse.json({ error: "partitionIndex must be < partitionCount" }, { status: 400 });
27
31
  }
28
- const { resolve } = await createRequestContainer();
32
+ const container = await createRequestContainer();
29
33
  let em = null;
30
34
  try {
31
- em = resolve("em");
35
+ em = container.resolve("em");
32
36
  } catch {
33
37
  }
34
- const bus = resolve("eventBus");
38
+ const bus = container.resolve("eventBus");
35
39
  const partitions = partitionIndex !== void 0 ? [partitionIndex] : Array.from({ length: partitionCount }, (_, idx) => idx);
36
40
  const firstPartition = partitions[0] ?? 0;
41
+ const guardUserId = typeof auth.sub === "string" ? auth.sub : "";
42
+ const guardResult = await validateCrudMutationGuard(container, {
43
+ tenantId: auth.tenantId,
44
+ organizationId: auth.orgId,
45
+ userId: guardUserId,
46
+ resourceKind: "query_index",
47
+ resourceId: entityType,
48
+ operation: "custom",
49
+ requestMethod: req.method,
50
+ requestHeaders: req.headers,
51
+ mutationPayload: {
52
+ entityType,
53
+ force,
54
+ batchSize: batchSize ?? null,
55
+ partitionCount,
56
+ partitionIndex: partitionIndex ?? null
57
+ }
58
+ });
59
+ if (guardResult && !guardResult.ok) {
60
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
61
+ }
37
62
  await recordIndexerLog(
38
63
  { em: em ?? void 0 },
39
64
  {
@@ -95,6 +120,19 @@ async function POST(req) {
95
120
  }
96
121
  }
97
122
  ).catch(() => void 0);
123
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
124
+ await runCrudMutationGuardAfterSuccess(container, {
125
+ tenantId: auth.tenantId,
126
+ organizationId: auth.orgId,
127
+ userId: guardUserId,
128
+ resourceKind: "query_index",
129
+ resourceId: entityType,
130
+ operation: "custom",
131
+ requestMethod: req.method,
132
+ requestHeaders: req.headers,
133
+ metadata: guardResult.metadata ?? null
134
+ });
135
+ }
98
136
  } catch (error) {
99
137
  await recordIndexerLog(
100
138
  { em: em ?? void 0 },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/api/reindex.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 type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexReindexRequestSchema } from './openapi'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['query_index.reindex'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({})) as any\n const entityType = String(body?.entityType || '')\n if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })\n if (!isValidEntityIdShape(entityType)) {\n return NextResponse.json({ error: 'Invalid entityType' }, { status: 400 })\n }\n const force = Boolean(body?.force)\n const batchSize = Number.isFinite(body?.batchSize) ? Math.max(1, Math.trunc(body.batchSize)) : undefined\n const partitionCountInput = Number(body?.partitionCount)\n const partitionCount = Number.isFinite(partitionCountInput)\n ? Math.max(1, Math.trunc(partitionCountInput))\n : 1\n const partitionIndexInput = Number(body?.partitionIndex)\n const partitionIndex = Number.isFinite(partitionIndexInput) ? Math.max(0, Math.trunc(partitionIndexInput)) : undefined\n if (partitionIndex !== undefined && partitionIndex >= partitionCount) {\n return NextResponse.json({ error: 'partitionIndex must be < partitionCount' }, { status: 400 })\n }\n\n const { resolve } = await createRequestContainer()\n let em: any | null = null\n try {\n em = resolve('em')\n } catch {}\n const bus = resolve('eventBus') as any\n const partitions = partitionIndex !== undefined\n ? [partitionIndex]\n : Array.from({ length: partitionCount }, (_, idx) => idx)\n const firstPartition = partitions[0] ?? 0\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex requested for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n try {\n await Promise.all(\n partitions.map((part) => {\n const payload: Record<string, unknown> = {\n entityType,\n force,\n batchSize,\n partitionCount,\n partitionIndex: part,\n resetCoverage: part === firstPartition,\n }\n if (auth.tenantId !== undefined) {\n payload.tenantId = auth.tenantId ?? null\n }\n if (auth.orgId !== undefined) {\n payload.organizationId = auth.orgId ?? null\n }\n if (typeof auth.sub === 'string' && auth.sub.length > 0) {\n payload.requestedByUserId = auth.sub\n }\n return bus.emitEvent(\n 'query_index.reindex',\n payload,\n { persistent: true, deliverInline: false },\n )\n }),\n )\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex queued for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n } catch (error) {\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n level: 'warn',\n message: `Failed to queue reindex for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n error: error instanceof Error ? error.message : String(error),\n },\n },\n ).catch(() => undefined)\n throw error\n }\n return NextResponse.json({ ok: true })\n}\n\nconst queryIndexReindexDoc: OpenApiMethodDoc = {\n summary: 'Trigger query index rebuild',\n description: 'Queues a reindex job for the specified entity type within the current tenant scope.',\n tags: [queryIndexTag],\n requestBody: {\n contentType: 'application/json',\n schema: queryIndexReindexRequestSchema,\n description: 'Entity identifier and optional force flag.',\n },\n responses: [\n { status: 200, description: 'Reindex job accepted.', schema: queryIndexOkSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity type', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Queue a query index rebuild',\n methods: {\n POST: queryIndexReindexDoc,\n },\n}\n"],
5
- "mappings": "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",
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 type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexReindexRequestSchema } from './openapi'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['query_index.reindex'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({})) as any\n const entityType = String(body?.entityType || '')\n if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })\n if (!isValidEntityIdShape(entityType)) {\n return NextResponse.json({ error: 'Invalid entityType' }, { status: 400 })\n }\n const force = Boolean(body?.force)\n const batchSize = Number.isFinite(body?.batchSize) ? Math.max(1, Math.trunc(body.batchSize)) : undefined\n const partitionCountInput = Number(body?.partitionCount)\n const partitionCount = Number.isFinite(partitionCountInput)\n ? Math.max(1, Math.trunc(partitionCountInput))\n : 1\n const partitionIndexInput = Number(body?.partitionIndex)\n const partitionIndex = Number.isFinite(partitionIndexInput) ? Math.max(0, Math.trunc(partitionIndexInput)) : undefined\n if (partitionIndex !== undefined && partitionIndex >= partitionCount) {\n return NextResponse.json({ error: 'partitionIndex must be < partitionCount' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n let em: any | null = null\n try {\n em = container.resolve('em')\n } catch {}\n const bus = container.resolve('eventBus') as any\n const partitions = partitionIndex !== undefined\n ? [partitionIndex]\n : Array.from({ length: partitionCount }, (_, idx) => idx)\n const firstPartition = partitions[0] ?? 0\n\n const guardUserId = typeof auth.sub === 'string' ? auth.sub : ''\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: {\n entityType,\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex requested for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n try {\n await Promise.all(\n partitions.map((part) => {\n const payload: Record<string, unknown> = {\n entityType,\n force,\n batchSize,\n partitionCount,\n partitionIndex: part,\n resetCoverage: part === firstPartition,\n }\n if (auth.tenantId !== undefined) {\n payload.tenantId = auth.tenantId ?? null\n }\n if (auth.orgId !== undefined) {\n payload.organizationId = auth.orgId ?? null\n }\n if (typeof auth.sub === 'string' && auth.sub.length > 0) {\n payload.requestedByUserId = auth.sub\n }\n return bus.emitEvent(\n 'query_index.reindex',\n payload,\n { persistent: true, deliverInline: false },\n )\n }),\n )\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n message: `Reindex queued for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n force,\n batchSize: batchSize ?? null,\n partitionCount,\n partitionIndex: partitionIndex ?? null,\n },\n },\n ).catch(() => undefined)\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: guardUserId,\n resourceKind: 'query_index',\n resourceId: entityType,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n } catch (error) {\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'query_index',\n handler: 'api:query_index.reindex',\n level: 'warn',\n message: `Failed to queue reindex for ${entityType}`,\n entityType,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n details: {\n error: error instanceof Error ? error.message : String(error),\n },\n },\n ).catch(() => undefined)\n throw error\n }\n return NextResponse.json({ ok: true })\n}\n\nconst queryIndexReindexDoc: OpenApiMethodDoc = {\n summary: 'Trigger query index rebuild',\n description: 'Queues a reindex job for the specified entity type within the current tenant scope.',\n tags: [queryIndexTag],\n requestBody: {\n contentType: 'application/json',\n schema: queryIndexReindexRequestSchema,\n description: 'Entity identifier and optional force flag.',\n },\n responses: [\n { status: 200, description: 'Reindex job accepted.', schema: queryIndexOkSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity type', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Queue a query index rebuild',\n methods: {\n POST: queryIndexReindexDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,6 +10,8 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
10
10
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
11
11
  import { useT } from "@open-mercato/shared/lib/i18n/context";
12
12
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
13
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
14
+ const MUTATION_CONTEXT_ID = "query_index.status.list:actions";
13
15
  function formatCount(value) {
14
16
  if (value == null) return "\u2014";
15
17
  return value.toLocaleString();
@@ -139,6 +141,10 @@ function QueryIndexesTable() {
139
141
  const t = useT();
140
142
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
141
143
  const columns = React.useMemo(() => createColumns(t), [t]);
144
+ const { runMutation, retryLastMutation } = useGuardedMutation({
145
+ contextId: MUTATION_CONTEXT_ID,
146
+ blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
147
+ });
142
148
  const { data, isLoading } = useQuery({
143
149
  queryKey: ["query-index-status", scopeVersion, refreshSeq],
144
150
  queryFn: async () => {
@@ -165,11 +171,20 @@ function QueryIndexesTable() {
165
171
  const actionLabel = action === "purge" ? t("query_index.table.actions.purge") : t("query_index.table.actions.reindex");
166
172
  const errorMessage = t("query_index.table.errors.actionFailed", { action: actionLabel });
167
173
  try {
168
- await apiCallOrThrow(`/api/query_index/${action}`, {
169
- method: "POST",
170
- headers: { "content-type": "application/json" },
171
- body: JSON.stringify(body)
172
- }, { errorMessage });
174
+ await runMutation({
175
+ operation: () => apiCallOrThrow(`/api/query_index/${action}`, {
176
+ method: "POST",
177
+ headers: { "content-type": "application/json" },
178
+ body: JSON.stringify(body)
179
+ }, { errorMessage }),
180
+ context: {
181
+ formId: MUTATION_CONTEXT_ID,
182
+ resourceKind: "query_index",
183
+ resourceId: entityId,
184
+ retryLastMutation
185
+ },
186
+ mutationPayload: { action, entityType: entityId, force: Boolean(opts?.force) }
187
+ });
173
188
  } catch (err) {
174
189
  const message = err instanceof Error && err.message ? err.message : errorMessage;
175
190
  console.warn("[query_index] index action failed", message);
@@ -177,7 +192,7 @@ function QueryIndexesTable() {
177
192
  }
178
193
  qc.invalidateQueries({ queryKey: ["query-index-status"] });
179
194
  },
180
- [qc, t]
195
+ [qc, t, runMutation, retryLastMutation]
181
196
  );
182
197
  const triggerVector = React.useCallback(
183
198
  async (action, entityId) => {
@@ -191,14 +206,23 @@ function QueryIndexesTable() {
191
206
  const actionLabel = action === "purge" ? t("query_index.table.actions.vectorPurge") : t("query_index.table.actions.vectorReindex");
192
207
  const errorMessage = t("query_index.table.errors.actionFailed", { action: actionLabel });
193
208
  try {
194
- await apiCallOrThrow("/api/search/embeddings/reindex", {
195
- method: "POST",
196
- headers: { "content-type": "application/json" },
197
- body: JSON.stringify({
198
- entityId,
199
- purgeFirst: action === "purge"
200
- })
201
- }, { errorMessage });
209
+ await runMutation({
210
+ operation: () => apiCallOrThrow("/api/search/embeddings/reindex", {
211
+ method: "POST",
212
+ headers: { "content-type": "application/json" },
213
+ body: JSON.stringify({
214
+ entityId,
215
+ purgeFirst: action === "purge"
216
+ })
217
+ }, { errorMessage }),
218
+ context: {
219
+ formId: MUTATION_CONTEXT_ID,
220
+ resourceKind: "query_index.vector",
221
+ resourceId: entityId,
222
+ retryLastMutation
223
+ },
224
+ mutationPayload: { action, entityId, purgeFirst: action === "purge" }
225
+ });
202
226
  } catch (err) {
203
227
  const message = err instanceof Error && err.message ? err.message : errorMessage;
204
228
  console.warn("[query_index] vector action failed", message);
@@ -206,7 +230,7 @@ function QueryIndexesTable() {
206
230
  }
207
231
  qc.invalidateQueries({ queryKey: ["query-index-status"] });
208
232
  },
209
- [confirm, qc, t]
233
+ [confirm, qc, t, runMutation, retryLastMutation]
210
234
  );
211
235
  const triggerFulltext = React.useCallback(
212
236
  async (action, entityId) => {
@@ -220,14 +244,23 @@ function QueryIndexesTable() {
220
244
  const actionLabel = action === "purge" ? t("query_index.table.actions.fulltextPurge") : t("query_index.table.actions.fulltextReindex");
221
245
  const errorMessage = t("query_index.table.errors.actionFailed", { action: actionLabel });
222
246
  try {
223
- await apiCallOrThrow("/api/search/reindex", {
224
- method: "POST",
225
- headers: { "content-type": "application/json" },
226
- body: JSON.stringify({
227
- action: action === "purge" ? "clear" : "reindex",
228
- entityId
229
- })
230
- }, { errorMessage });
247
+ await runMutation({
248
+ operation: () => apiCallOrThrow("/api/search/reindex", {
249
+ method: "POST",
250
+ headers: { "content-type": "application/json" },
251
+ body: JSON.stringify({
252
+ action: action === "purge" ? "clear" : "reindex",
253
+ entityId
254
+ })
255
+ }, { errorMessage }),
256
+ context: {
257
+ formId: MUTATION_CONTEXT_ID,
258
+ resourceKind: "query_index.fulltext",
259
+ resourceId: entityId,
260
+ retryLastMutation
261
+ },
262
+ mutationPayload: { action, entityId }
263
+ });
231
264
  } catch (err) {
232
265
  const message = err instanceof Error && err.message ? err.message : errorMessage;
233
266
  console.warn("[query_index] fulltext action failed", message);
@@ -235,7 +268,7 @@ function QueryIndexesTable() {
235
268
  }
236
269
  qc.invalidateQueries({ queryKey: ["query-index-status"] });
237
270
  },
238
- [confirm, qc, t]
271
+ [confirm, qc, t, runMutation, retryLastMutation]
239
272
  );
240
273
  return /* @__PURE__ */ jsxs(Fragment, { children: [
241
274
  /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/components/QueryIndexesTable.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\n\ntype Translator = (key: string, params?: Record<string, string | number>) => string\n\ntype PartitionStatus = {\n partitionIndex: number | null\n partitionCount: number | null\n status: 'reindexing' | 'purging' | 'stalled' | 'completed'\n processedCount?: number | null\n totalCount?: number | null\n heartbeatAt?: string | null\n startedAt?: string | null\n finishedAt?: string | null\n}\n\ntype JobStatus = {\n status: 'idle' | 'reindexing' | 'purging' | 'stalled'\n startedAt?: string | null\n finishedAt?: string | null\n heartbeatAt?: string | null\n processedCount?: number | null\n totalCount?: number | null\n partitions?: PartitionStatus[]\n scope?: {\n status?: 'reindexing' | 'purging' | 'stalled' | 'completed' | null\n processedCount?: number | null\n totalCount?: number | null\n } | null\n}\n\ntype Row = {\n entityId: string\n label: string\n baseCount: number | null\n indexCount: number | null\n vectorCount: number | null\n vectorEnabled: boolean\n fulltextCount: number | null\n fulltextEnabled: boolean\n ok: boolean\n job?: JobStatus\n}\n\ntype Resp = { items: Row[] }\n\nfunction formatCount(value: number | null): string {\n if (value == null) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatNumeric(value: number | null | undefined): string | null {\n if (value == null) return null\n return Number(value).toLocaleString()\n}\n\nfunction formatProgressLabel(\n processed: number | null | undefined,\n total: number | null | undefined,\n t: Translator,\n): string | null {\n const processedText = formatNumeric(processed)\n if (!processedText) return null\n const totalText = formatNumeric(total)\n if (totalText) return t('query_index.table.status.progress', { processed: processedText, total: totalText })\n return t('query_index.table.status.progressSingle', { processed: processedText })\n}\n\nfunction translateJobStatus(t: Translator, status: JobStatus['status'] | undefined, ok: boolean): string {\n if (!status || status === 'idle') {\n return ok ? t('query_index.table.status.in_sync') : t('query_index.table.status.out_of_sync')\n }\n if (status === 'reindexing') return t('query_index.table.status.reindexing')\n if (status === 'purging') return t('query_index.table.status.purging')\n if (status === 'stalled') return t('query_index.table.status.stalled')\n return ok ? t('query_index.table.status.in_sync') : t('query_index.table.status.out_of_sync')\n}\n\nfunction translateScopeStatus(\n t: Translator,\n status: PartitionStatus['status'] | JobStatus['status'] | undefined | null,\n): string {\n if (status === 'reindexing') return t('query_index.table.status.scope.reindexing')\n if (status === 'purging') return t('query_index.table.status.scope.purging')\n if (status === 'stalled') return t('query_index.table.status.scope.stalled')\n return t('query_index.table.status.scope.completed')\n}\n\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n { id: 'entityId', header: () => t('query_index.table.columns.entity'), accessorKey: 'entityId', meta: { priority: 1 } },\n { id: 'label', header: () => t('query_index.table.columns.label'), accessorKey: 'label', meta: { priority: 2 } },\n {\n id: 'baseCount',\n header: () => t('query_index.table.columns.records'),\n accessorFn: (row) => row.baseCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.baseCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'indexCount',\n header: () => t('query_index.table.columns.indexed'),\n accessorFn: (row) => row.indexCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.indexCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'vectorCount',\n header: () => t('query_index.table.columns.vector'),\n accessorFn: (row) => (row.vectorEnabled ? row.vectorCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!record.vectorEnabled) return <span>\u2014</span>\n const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount\n const display = formatCount(record.vectorCount)\n const className = ok ? 'text-green-600' : 'text-orange-600'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'fulltextCount',\n header: () => t('query_index.table.columns.fulltext'),\n accessorFn: (row) => (row.fulltextEnabled ? row.fulltextCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!record.fulltextEnabled) return <span>\u2014</span>\n const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount\n const display = formatCount(record.fulltextCount)\n const className = ok ? 'text-green-600' : 'text-orange-600'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'status',\n header: () => t('query_index.table.columns.status'),\n cell: ({ row }) => {\n const record = row.original\n const job = record.job\n const partitions = job?.partitions ?? []\n const ok = record.ok && (!job || job.status === 'idle')\n const statusText = translateJobStatus(t, job?.status, ok)\n const jobProgress = job ? formatProgressLabel(job.processedCount ?? null, job.totalCount ?? null, t) : null\n const label = jobProgress\n ? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })\n : statusText\n const className = job\n ? job.status === 'stalled'\n ? 'text-red-600'\n : job.status === 'reindexing' || job.status === 'purging'\n ? 'text-orange-600'\n : ok\n ? 'text-green-600'\n : 'text-muted-foreground'\n : ok\n ? 'text-green-600'\n : 'text-muted-foreground'\n\n const lines: string[] = []\n\n if (job?.scope && partitions.length <= 1) {\n const scopeStatus = translateScopeStatus(t, job.scope.status ?? null)\n const scopeProgress = formatProgressLabel(job.scope.processedCount ?? null, job.scope.totalCount ?? null, t)\n const scopeLabel = t('query_index.table.status.scopeLabel')\n lines.push(`${scopeLabel}: ${scopeStatus}${scopeProgress ? ` (${scopeProgress})` : ''}`)\n }\n\n if (partitions.length > 1) {\n for (const part of partitions) {\n const partitionLabel =\n part.partitionIndex != null\n ? t('query_index.table.status.partitionLabel', { index: Number(part.partitionIndex) + 1 })\n : t('query_index.table.status.scopeLabel')\n const partitionStatus = translateScopeStatus(t, part.status)\n const partitionProgress = formatProgressLabel(part.processedCount ?? null, part.totalCount ?? null, t)\n lines.push(`${partitionLabel}: ${partitionStatus}${partitionProgress ? ` (${partitionProgress})` : ''}`)\n }\n }\n\n if (record.vectorEnabled) {\n const vectorLabel = t('query_index.table.status.vectorLabel')\n const vectorCount = formatCount(record.vectorCount)\n const vectorTotal = record.baseCount != null ? formatCount(record.baseCount) : null\n const vectorValue = vectorTotal\n ? t('query_index.table.status.vectorValue', { count: vectorCount, total: vectorTotal })\n : vectorCount\n lines.push(`${vectorLabel}: ${vectorValue}`)\n }\n\n return (\n <div className=\"space-y-1\">\n <span className={className}>{label}</span>\n {lines.length > 0 && (\n <div className=\"text-xs text-muted-foreground\">\n {lines.map((line, idx) => (\n <div key={idx}>{line}</div>\n ))}\n </div>\n )}\n </div>\n )\n },\n meta: { priority: 1 },\n },\n ]\n}\n\nexport default function QueryIndexesTable() {\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const qc = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const [refreshSeq, setRefreshSeq] = React.useState(0)\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const columns = React.useMemo(() => createColumns(t), [t])\n\n const { data, isLoading } = useQuery<Resp>({\n queryKey: ['query-index-status', scopeVersion, refreshSeq],\n queryFn: async () => {\n const baseUrl = '/api/query_index/status'\n const url = refreshSeq > 0 ? `${baseUrl}?refresh=${refreshSeq}` : baseUrl\n return readApiResultOrThrow<Resp>(\n url,\n undefined,\n { errorMessage: t('query_index.table.errors.loadFailed') },\n )\n },\n refetchInterval: 4000,\n })\n\n const rowsAll = data?.items || []\n const rows = React.useMemo(() => {\n if (!search) return rowsAll\n const q = search.toLowerCase()\n return rowsAll.filter((r) => r.entityId.toLowerCase().includes(q) || r.label.toLowerCase().includes(q))\n }, [rowsAll, search])\n\n const trigger = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string, opts?: { force?: boolean }) => {\n const body: Record<string, unknown> = { entityType: entityId }\n if (opts?.force) body.force = true\n const actionLabel =\n action === 'purge' ? t('query_index.table.actions.purge') : t('query_index.table.actions.reindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await apiCallOrThrow(`/api/query_index/${action}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n }, { errorMessage })\n } catch (err) {\n // Expected operational failures (e.g. a 503 when a search backend is not\n // configured) \u2014 surface a flash toast, not an alert or an error-level\n // stack dump that reads like an unhandled exception.\n const message = err instanceof Error && err.message ? err.message : errorMessage\n console.warn('[query_index] index action failed', message)\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [qc, t],\n )\n\n const triggerVector = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.vectorPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.vectorPurge')\n : t('query_index.table.actions.vectorReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await apiCallOrThrow('/api/search/embeddings/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId,\n purgeFirst: action === 'purge',\n }),\n }, { errorMessage })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n console.warn('[query_index] vector action failed', message)\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t],\n )\n\n const triggerFulltext = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.fulltextPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.fulltextPurge')\n : t('query_index.table.actions.fulltextReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await apiCallOrThrow('/api/search/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n action: action === 'purge' ? 'clear' : 'reindex',\n entityId,\n }),\n }, { errorMessage })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n console.warn('[query_index] fulltext action failed', message)\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t],\n )\n\n return (\n <>\n <DataTable\n title={t('query_index.nav.queryIndexes')}\n actions={(\n <>\n <Button\n variant=\"outline\"\n onClick={() => {\n setRefreshSeq((v) => v + 1)\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n }}\n >\n {t('query_index.table.refresh')}\n </Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n searchPlaceholder={t('query_index.table.searchPlaceholder')}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n perspective={{ tableId: 'query_index.status.list' }}\n rowActions={(row) => {\n const items: Array<{ id: string; label: string; onSelect: () => void; destructive?: boolean }> = [\n { id: 'reindex', label: t('query_index.table.actions.reindex'), onSelect: () => void trigger('reindex', row.entityId) },\n {\n id: 'reindex-force',\n label: t('query_index.table.actions.reindexForce'),\n onSelect: () => void trigger('reindex', row.entityId, { force: true }),\n },\n {\n id: 'purge',\n label: t('query_index.table.actions.purge'),\n destructive: true,\n onSelect: () => void trigger('purge', row.entityId),\n },\n ]\n\n if (row.vectorEnabled) {\n items.push(\n {\n id: 'vector-reindex',\n label: t('query_index.table.actions.vectorReindex'),\n onSelect: () => void triggerVector('reindex', row.entityId),\n },\n {\n id: 'vector-purge',\n label: t('query_index.table.actions.vectorPurge'),\n destructive: true,\n onSelect: () => void triggerVector('purge', row.entityId),\n },\n )\n }\n\n if (row.fulltextEnabled) {\n items.push(\n {\n id: 'fulltext-reindex',\n label: t('query_index.table.actions.fulltextReindex'),\n onSelect: () => void triggerFulltext('reindex', row.entityId),\n },\n {\n id: 'fulltext-purge',\n label: t('query_index.table.actions.fulltextPurge'),\n destructive: true,\n onSelect: () => void triggerFulltext('purge', row.entityId),\n },\n )\n }\n\n return <RowActions items={items} />\n }}\n pagination={{ page, pageSize: 50, total: rows.length, totalPages: 1, onPageChange: setPage }}\n isLoading={isLoading}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
- "mappings": ";AA2GyB,SAiPf,UAjPe,KA+Ff,YA/Fe;AA1GzB,YAAY,WAAW;AACvB,SAAS,UAAU,sBAAsB;AAEzC,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,aAAa;AACtB,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,wBAAwB;AA6CjC,SAAS,YAAY,OAA8B;AACjD,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,MAAM,eAAe;AAC9B;AAEA,SAAS,cAAc,OAAiD;AACtE,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,OAAO,KAAK,EAAE,eAAe;AACtC;AAEA,SAAS,oBACP,WACA,OACA,GACe;AACf,QAAM,gBAAgB,cAAc,SAAS;AAC7C,MAAI,CAAC,cAAe,QAAO;AAC3B,QAAM,YAAY,cAAc,KAAK;AACrC,MAAI,UAAW,QAAO,EAAE,qCAAqC,EAAE,WAAW,eAAe,OAAO,UAAU,CAAC;AAC3G,SAAO,EAAE,2CAA2C,EAAE,WAAW,cAAc,CAAC;AAClF;AAEA,SAAS,mBAAmB,GAAe,QAAyC,IAAqB;AACvG,MAAI,CAAC,UAAU,WAAW,QAAQ;AAChC,WAAO,KAAK,EAAE,kCAAkC,IAAI,EAAE,sCAAsC;AAAA,EAC9F;AACA,MAAI,WAAW,aAAc,QAAO,EAAE,qCAAqC;AAC3E,MAAI,WAAW,UAAW,QAAO,EAAE,kCAAkC;AACrE,MAAI,WAAW,UAAW,QAAO,EAAE,kCAAkC;AACrE,SAAO,KAAK,EAAE,kCAAkC,IAAI,EAAE,sCAAsC;AAC9F;AAEA,SAAS,qBACP,GACA,QACQ;AACR,MAAI,WAAW,aAAc,QAAO,EAAE,2CAA2C;AACjF,MAAI,WAAW,UAAW,QAAO,EAAE,wCAAwC;AAC3E,MAAI,WAAW,UAAW,QAAO,EAAE,wCAAwC;AAC3E,SAAO,EAAE,0CAA0C;AACrD;AAGA,SAAS,cAAc,GAAiC;AACtD,SAAO;AAAA,IACL,EAAE,IAAI,YAAY,QAAQ,MAAM,EAAE,kCAAkC,GAAG,aAAa,YAAY,MAAM,EAAE,UAAU,EAAE,EAAE;AAAA,IACtH,EAAE,IAAI,SAAS,QAAQ,MAAM,EAAE,iCAAiC,GAAG,aAAa,SAAS,MAAM,EAAE,UAAU,EAAE,EAAE;AAAA,IAC/G;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ,MAAM,EAAE,mCAAmC;AAAA,MACnD,YAAY,CAAC,QAAQ,IAAI,aAAa;AAAA,MACtC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,sBAAY,IAAI,SAAS,SAAS,GAAE;AAAA,MAC9D,MAAM,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ,MAAM,EAAE,mCAAmC;AAAA,MACnD,YAAY,CAAC,QAAQ,IAAI,cAAc;AAAA,MACvC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,sBAAY,IAAI,SAAS,UAAU,GAAE;AAAA,MAC/D,MAAM,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ,MAAM,EAAE,kCAAkC;AAAA,MAClD,YAAY,CAAC,QAAS,IAAI,gBAAgB,IAAI,eAAe,IAAI;AAAA,MACjE,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,SAAS,IAAI;AACnB,YAAI,CAAC,OAAO,cAAe,QAAO,oBAAC,UAAK,oBAAC;AACzC,cAAM,KAAK,OAAO,eAAe,QAAQ,OAAO,aAAa,QAAQ,OAAO,gBAAgB,OAAO;AACnG,cAAM,UAAU,YAAY,OAAO,WAAW;AAC9C,cAAM,YAAY,KAAK,mBAAmB;AAC1C,eAAO,oBAAC,UAAK,WAAuB,mBAAQ;AAAA,MAC9C;AAAA,MACA,MAAM,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ,MAAM,EAAE,oCAAoC;AAAA,MACpD,YAAY,CAAC,QAAS,IAAI,kBAAkB,IAAI,iBAAiB,IAAI;AAAA,MACrE,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,SAAS,IAAI;AACnB,YAAI,CAAC,OAAO,gBAAiB,QAAO,oBAAC,UAAK,oBAAC;AAC3C,cAAM,KAAK,OAAO,iBAAiB,QAAQ,OAAO,aAAa,QAAQ,OAAO,kBAAkB,OAAO;AACvG,cAAM,UAAU,YAAY,OAAO,aAAa;AAChD,cAAM,YAAY,KAAK,mBAAmB;AAC1C,eAAO,oBAAC,UAAK,WAAuB,mBAAQ;AAAA,MAC9C;AAAA,MACA,MAAM,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ,MAAM,EAAE,kCAAkC;AAAA,MAClD,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,SAAS,IAAI;AACnB,cAAM,MAAM,OAAO;AACnB,cAAM,aAAa,KAAK,cAAc,CAAC;AACvC,cAAM,KAAK,OAAO,OAAO,CAAC,OAAO,IAAI,WAAW;AAChD,cAAM,aAAa,mBAAmB,GAAG,KAAK,QAAQ,EAAE;AACxD,cAAM,cAAc,MAAM,oBAAoB,IAAI,kBAAkB,MAAM,IAAI,cAAc,MAAM,CAAC,IAAI;AACvG,cAAM,QAAQ,cACV,EAAE,yCAAyC,EAAE,QAAQ,YAAY,UAAU,YAAY,CAAC,IACxF;AACJ,cAAM,YAAY,MACd,IAAI,WAAW,YACb,iBACA,IAAI,WAAW,gBAAgB,IAAI,WAAW,YAC5C,oBACA,KACE,mBACA,0BACN,KACE,mBACA;AAEN,cAAM,QAAkB,CAAC;AAEzB,YAAI,KAAK,SAAS,WAAW,UAAU,GAAG;AACxC,gBAAM,cAAc,qBAAqB,GAAG,IAAI,MAAM,UAAU,IAAI;AACpE,gBAAM,gBAAgB,oBAAoB,IAAI,MAAM,kBAAkB,MAAM,IAAI,MAAM,cAAc,MAAM,CAAC;AAC3G,gBAAM,aAAa,EAAE,qCAAqC;AAC1D,gBAAM,KAAK,GAAG,UAAU,KAAK,WAAW,GAAG,gBAAgB,KAAK,aAAa,MAAM,EAAE,EAAE;AAAA,QACzF;AAEA,YAAI,WAAW,SAAS,GAAG;AACzB,qBAAW,QAAQ,YAAY;AAC7B,kBAAM,iBACJ,KAAK,kBAAkB,OACnB,EAAE,2CAA2C,EAAE,OAAO,OAAO,KAAK,cAAc,IAAI,EAAE,CAAC,IACvF,EAAE,qCAAqC;AAC7C,kBAAM,kBAAkB,qBAAqB,GAAG,KAAK,MAAM;AAC3D,kBAAM,oBAAoB,oBAAoB,KAAK,kBAAkB,MAAM,KAAK,cAAc,MAAM,CAAC;AACrG,kBAAM,KAAK,GAAG,cAAc,KAAK,eAAe,GAAG,oBAAoB,KAAK,iBAAiB,MAAM,EAAE,EAAE;AAAA,UACzG;AAAA,QACF;AAEA,YAAI,OAAO,eAAe;AACxB,gBAAM,cAAc,EAAE,sCAAsC;AAC5D,gBAAM,cAAc,YAAY,OAAO,WAAW;AAClD,gBAAM,cAAc,OAAO,aAAa,OAAO,YAAY,OAAO,SAAS,IAAI;AAC/E,gBAAM,cAAc,cAChB,EAAE,wCAAwC,EAAE,OAAO,aAAa,OAAO,YAAY,CAAC,IACpF;AACJ,gBAAM,KAAK,GAAG,WAAW,KAAK,WAAW,EAAE;AAAA,QAC7C;AAEA,eACE,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,UAAK,WAAuB,iBAAM;AAAA,UAClC,MAAM,SAAS,KACd,oBAAC,SAAI,WAAU,iCACZ,gBAAM,IAAI,CAAC,MAAM,QAChB,oBAAC,SAAe,kBAAN,GAAW,CACtB,GACH;AAAA,WAEJ;AAAA,MAEJ;AAAA,MACA,MAAM,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,EACF;AACF;AAEe,SAAR,oBAAqC;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,EAAE,IAAI,YAAY,MAAM,MAAM,CAAC,CAAC;AAC5F,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,KAAK,eAAe;AAC1B,QAAM,eAAe,4BAA4B;AACjD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,UAAU,MAAM,QAAQ,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AAEzD,QAAM,EAAE,MAAM,UAAU,IAAI,SAAe;AAAA,IACzC,UAAU,CAAC,sBAAsB,cAAc,UAAU;AAAA,IACzD,SAAS,YAAY;AACnB,YAAM,UAAU;AAChB,YAAM,MAAM,aAAa,IAAI,GAAG,OAAO,YAAY,UAAU,KAAK;AAClE,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,EAAE,cAAc,EAAE,qCAAqC,EAAE;AAAA,MAC3D;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,EACnB,CAAC;AAED,QAAM,UAAU,MAAM,SAAS,CAAC;AAChC,QAAM,OAAO,MAAM,QAAQ,MAAM;AAC/B,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,IAAI,OAAO,YAAY;AAC7B,WAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,YAAY,EAAE,SAAS,CAAC,CAAC;AAAA,EACxG,GAAG,CAAC,SAAS,MAAM,CAAC;AAEpB,QAAM,UAAU,MAAM;AAAA,IACpB,OAAO,QAA6B,UAAkB,SAA+B;AACnF,YAAM,OAAgC,EAAE,YAAY,SAAS;AAC7D,UAAI,MAAM,MAAO,MAAK,QAAQ;AAC9B,YAAM,cACJ,WAAW,UAAU,EAAE,iCAAiC,IAAI,EAAE,mCAAmC;AACnG,YAAM,eAAe,EAAE,yCAAyC,EAAE,QAAQ,YAAY,CAAC;AACvF,UAAI;AACF,cAAM,eAAe,oBAAoB,MAAM,IAAI;AAAA,UACjD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,QAC3B,GAAG,EAAE,aAAa,CAAC;AAAA,MACrB,SAAS,KAAK;AAIZ,cAAM,UAAU,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU;AACpE,gBAAQ,KAAK,qCAAqC,OAAO;AACzD,cAAM,SAAS,OAAO;AAAA,MACxB;AACA,SAAG,kBAAkB,EAAE,UAAU,CAAC,oBAAoB,EAAE,CAAC;AAAA,IAC3D;AAAA,IACA,CAAC,IAAI,CAAC;AAAA,EACR;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,OAAO,QAA6B,aAAqB;AACvD,UAAI,WAAW,SAAS;AACtB,cAAM,YAAY,MAAM,QAAQ;AAAA,UAC9B,OAAO,EAAE,uCAAuC;AAAA,UAChD,SAAS;AAAA,QACX,CAAC;AACD,YAAI,CAAC,UAAW;AAAA,MAClB;AAEA,YAAM,cAAc,WAAW,UAC3B,EAAE,uCAAuC,IACzC,EAAE,yCAAyC;AAC/C,YAAM,eAAe,EAAE,yCAAyC,EAAE,QAAQ,YAAY,CAAC;AACvF,UAAI;AACF,cAAM,eAAe,kCAAkC;AAAA,UACrD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB;AAAA,YACA,YAAY,WAAW;AAAA,UACzB,CAAC;AAAA,QACH,GAAG,EAAE,aAAa,CAAC;AAAA,MACrB,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU;AACpE,gBAAQ,KAAK,sCAAsC,OAAO;AAC1D,cAAM,SAAS,OAAO;AAAA,MACxB;AACA,SAAG,kBAAkB,EAAE,UAAU,CAAC,oBAAoB,EAAE,CAAC;AAAA,IAC3D;AAAA,IACA,CAAC,SAAS,IAAI,CAAC;AAAA,EACjB;AAEA,QAAM,kBAAkB,MAAM;AAAA,IAC5B,OAAO,QAA6B,aAAqB;AACvD,UAAI,WAAW,SAAS;AACtB,cAAM,YAAY,MAAM,QAAQ;AAAA,UAC9B,OAAO,EAAE,yCAAyC;AAAA,UAClD,SAAS;AAAA,QACX,CAAC;AACD,YAAI,CAAC,UAAW;AAAA,MAClB;AAEA,YAAM,cAAc,WAAW,UAC3B,EAAE,yCAAyC,IAC3C,EAAE,2CAA2C;AACjD,YAAM,eAAe,EAAE,yCAAyC,EAAE,QAAQ,YAAY,CAAC;AACvF,UAAI;AACF,cAAM,eAAe,uBAAuB;AAAA,UAC1C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,QAAQ,WAAW,UAAU,UAAU;AAAA,YACvC;AAAA,UACF,CAAC;AAAA,QACH,GAAG,EAAE,aAAa,CAAC;AAAA,MACrB,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU;AACpE,gBAAQ,KAAK,wCAAwC,OAAO;AAC5D,cAAM,SAAS,OAAO;AAAA,MACxB;AACA,SAAG,kBAAkB,EAAE,UAAU,CAAC,oBAAoB,EAAE,CAAC;AAAA,IAC3D;AAAA,IACA,CAAC,SAAS,IAAI,CAAC;AAAA,EACjB;AAEA,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,8BAA8B;AAAA,QACvC,SACE,gCACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,SAAS,MAAM;AACb,4BAAc,CAAC,MAAM,IAAI,CAAC;AAC1B,iBAAG,kBAAkB,EAAE,UAAU,CAAC,oBAAoB,EAAE,CAAC;AAAA,YAC3D;AAAA,YAEC,YAAE,2BAA2B;AAAA;AAAA,QAChC,GACF;AAAA,QAEF;AAAA,QACA,MAAM;AAAA,QACN,aAAa;AAAA,QACb,mBAAmB,EAAE,qCAAqC;AAAA,QAC1D,gBAAgB,CAAC,UAAU;AACzB,oBAAU,KAAK;AACf,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,UAAQ;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,QACjB,aAAa,EAAE,SAAS,0BAA0B;AAAA,QAClD,YAAY,CAAC,QAAQ;AACnB,gBAAM,QAA2F;AAAA,YAC/F,EAAE,IAAI,WAAW,OAAO,EAAE,mCAAmC,GAAG,UAAU,MAAM,KAAK,QAAQ,WAAW,IAAI,QAAQ,EAAE;AAAA,YACtH;AAAA,cACE,IAAI;AAAA,cACJ,OAAO,EAAE,wCAAwC;AAAA,cACjD,UAAU,MAAM,KAAK,QAAQ,WAAW,IAAI,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,YACvE;AAAA,YACA;AAAA,cACE,IAAI;AAAA,cACJ,OAAO,EAAE,iCAAiC;AAAA,cAC1C,aAAa;AAAA,cACb,UAAU,MAAM,KAAK,QAAQ,SAAS,IAAI,QAAQ;AAAA,YACpD;AAAA,UACF;AAEA,cAAI,IAAI,eAAe;AACrB,kBAAM;AAAA,cACJ;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,EAAE,yCAAyC;AAAA,gBAClD,UAAU,MAAM,KAAK,cAAc,WAAW,IAAI,QAAQ;AAAA,cAC5D;AAAA,cACA;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,EAAE,uCAAuC;AAAA,gBAChD,aAAa;AAAA,gBACb,UAAU,MAAM,KAAK,cAAc,SAAS,IAAI,QAAQ;AAAA,cAC1D;AAAA,YACF;AAAA,UACF;AAEA,cAAI,IAAI,iBAAiB;AACvB,kBAAM;AAAA,cACJ;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,EAAE,2CAA2C;AAAA,gBACpD,UAAU,MAAM,KAAK,gBAAgB,WAAW,IAAI,QAAQ;AAAA,cAC9D;AAAA,cACA;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,EAAE,yCAAyC;AAAA,gBAClD,aAAa;AAAA,gBACb,UAAU,MAAM,KAAK,gBAAgB,SAAS,IAAI,QAAQ;AAAA,cAC5D;AAAA,YACF;AAAA,UACF;AAEA,iBAAO,oBAAC,cAAW,OAAc;AAAA,QACnC;AAAA,QACA,YAAY,EAAE,MAAM,UAAU,IAAI,OAAO,KAAK,QAAQ,YAAY,GAAG,cAAc,QAAQ;AAAA,QAC3F;AAAA;AAAA,IACF;AAAA,IACC;AAAA,KACH;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\nconst MUTATION_CONTEXT_ID = 'query_index.status.list:actions'\n\ntype Translator = (key: string, params?: Record<string, string | number>) => string\n\ntype PartitionStatus = {\n partitionIndex: number | null\n partitionCount: number | null\n status: 'reindexing' | 'purging' | 'stalled' | 'completed'\n processedCount?: number | null\n totalCount?: number | null\n heartbeatAt?: string | null\n startedAt?: string | null\n finishedAt?: string | null\n}\n\ntype JobStatus = {\n status: 'idle' | 'reindexing' | 'purging' | 'stalled'\n startedAt?: string | null\n finishedAt?: string | null\n heartbeatAt?: string | null\n processedCount?: number | null\n totalCount?: number | null\n partitions?: PartitionStatus[]\n scope?: {\n status?: 'reindexing' | 'purging' | 'stalled' | 'completed' | null\n processedCount?: number | null\n totalCount?: number | null\n } | null\n}\n\ntype Row = {\n entityId: string\n label: string\n baseCount: number | null\n indexCount: number | null\n vectorCount: number | null\n vectorEnabled: boolean\n fulltextCount: number | null\n fulltextEnabled: boolean\n ok: boolean\n job?: JobStatus\n}\n\ntype Resp = { items: Row[] }\n\nfunction formatCount(value: number | null): string {\n if (value == null) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatNumeric(value: number | null | undefined): string | null {\n if (value == null) return null\n return Number(value).toLocaleString()\n}\n\nfunction formatProgressLabel(\n processed: number | null | undefined,\n total: number | null | undefined,\n t: Translator,\n): string | null {\n const processedText = formatNumeric(processed)\n if (!processedText) return null\n const totalText = formatNumeric(total)\n if (totalText) return t('query_index.table.status.progress', { processed: processedText, total: totalText })\n return t('query_index.table.status.progressSingle', { processed: processedText })\n}\n\nfunction translateJobStatus(t: Translator, status: JobStatus['status'] | undefined, ok: boolean): string {\n if (!status || status === 'idle') {\n return ok ? t('query_index.table.status.in_sync') : t('query_index.table.status.out_of_sync')\n }\n if (status === 'reindexing') return t('query_index.table.status.reindexing')\n if (status === 'purging') return t('query_index.table.status.purging')\n if (status === 'stalled') return t('query_index.table.status.stalled')\n return ok ? t('query_index.table.status.in_sync') : t('query_index.table.status.out_of_sync')\n}\n\nfunction translateScopeStatus(\n t: Translator,\n status: PartitionStatus['status'] | JobStatus['status'] | undefined | null,\n): string {\n if (status === 'reindexing') return t('query_index.table.status.scope.reindexing')\n if (status === 'purging') return t('query_index.table.status.scope.purging')\n if (status === 'stalled') return t('query_index.table.status.scope.stalled')\n return t('query_index.table.status.scope.completed')\n}\n\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n { id: 'entityId', header: () => t('query_index.table.columns.entity'), accessorKey: 'entityId', meta: { priority: 1 } },\n { id: 'label', header: () => t('query_index.table.columns.label'), accessorKey: 'label', meta: { priority: 2 } },\n {\n id: 'baseCount',\n header: () => t('query_index.table.columns.records'),\n accessorFn: (row) => row.baseCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.baseCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'indexCount',\n header: () => t('query_index.table.columns.indexed'),\n accessorFn: (row) => row.indexCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.indexCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'vectorCount',\n header: () => t('query_index.table.columns.vector'),\n accessorFn: (row) => (row.vectorEnabled ? row.vectorCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!record.vectorEnabled) return <span>\u2014</span>\n const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount\n const display = formatCount(record.vectorCount)\n const className = ok ? 'text-green-600' : 'text-orange-600'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'fulltextCount',\n header: () => t('query_index.table.columns.fulltext'),\n accessorFn: (row) => (row.fulltextEnabled ? row.fulltextCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!record.fulltextEnabled) return <span>\u2014</span>\n const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount\n const display = formatCount(record.fulltextCount)\n const className = ok ? 'text-green-600' : 'text-orange-600'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'status',\n header: () => t('query_index.table.columns.status'),\n cell: ({ row }) => {\n const record = row.original\n const job = record.job\n const partitions = job?.partitions ?? []\n const ok = record.ok && (!job || job.status === 'idle')\n const statusText = translateJobStatus(t, job?.status, ok)\n const jobProgress = job ? formatProgressLabel(job.processedCount ?? null, job.totalCount ?? null, t) : null\n const label = jobProgress\n ? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })\n : statusText\n const className = job\n ? job.status === 'stalled'\n ? 'text-red-600'\n : job.status === 'reindexing' || job.status === 'purging'\n ? 'text-orange-600'\n : ok\n ? 'text-green-600'\n : 'text-muted-foreground'\n : ok\n ? 'text-green-600'\n : 'text-muted-foreground'\n\n const lines: string[] = []\n\n if (job?.scope && partitions.length <= 1) {\n const scopeStatus = translateScopeStatus(t, job.scope.status ?? null)\n const scopeProgress = formatProgressLabel(job.scope.processedCount ?? null, job.scope.totalCount ?? null, t)\n const scopeLabel = t('query_index.table.status.scopeLabel')\n lines.push(`${scopeLabel}: ${scopeStatus}${scopeProgress ? ` (${scopeProgress})` : ''}`)\n }\n\n if (partitions.length > 1) {\n for (const part of partitions) {\n const partitionLabel =\n part.partitionIndex != null\n ? t('query_index.table.status.partitionLabel', { index: Number(part.partitionIndex) + 1 })\n : t('query_index.table.status.scopeLabel')\n const partitionStatus = translateScopeStatus(t, part.status)\n const partitionProgress = formatProgressLabel(part.processedCount ?? null, part.totalCount ?? null, t)\n lines.push(`${partitionLabel}: ${partitionStatus}${partitionProgress ? ` (${partitionProgress})` : ''}`)\n }\n }\n\n if (record.vectorEnabled) {\n const vectorLabel = t('query_index.table.status.vectorLabel')\n const vectorCount = formatCount(record.vectorCount)\n const vectorTotal = record.baseCount != null ? formatCount(record.baseCount) : null\n const vectorValue = vectorTotal\n ? t('query_index.table.status.vectorValue', { count: vectorCount, total: vectorTotal })\n : vectorCount\n lines.push(`${vectorLabel}: ${vectorValue}`)\n }\n\n return (\n <div className=\"space-y-1\">\n <span className={className}>{label}</span>\n {lines.length > 0 && (\n <div className=\"text-xs text-muted-foreground\">\n {lines.map((line, idx) => (\n <div key={idx}>{line}</div>\n ))}\n </div>\n )}\n </div>\n )\n },\n meta: { priority: 1 },\n },\n ]\n}\n\nexport default function QueryIndexesTable() {\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const qc = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const [refreshSeq, setRefreshSeq] = React.useState(0)\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const columns = React.useMemo(() => createColumns(t), [t])\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const { data, isLoading } = useQuery<Resp>({\n queryKey: ['query-index-status', scopeVersion, refreshSeq],\n queryFn: async () => {\n const baseUrl = '/api/query_index/status'\n const url = refreshSeq > 0 ? `${baseUrl}?refresh=${refreshSeq}` : baseUrl\n return readApiResultOrThrow<Resp>(\n url,\n undefined,\n { errorMessage: t('query_index.table.errors.loadFailed') },\n )\n },\n refetchInterval: 4000,\n })\n\n const rowsAll = data?.items || []\n const rows = React.useMemo(() => {\n if (!search) return rowsAll\n const q = search.toLowerCase()\n return rowsAll.filter((r) => r.entityId.toLowerCase().includes(q) || r.label.toLowerCase().includes(q))\n }, [rowsAll, search])\n\n const trigger = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string, opts?: { force?: boolean }) => {\n const body: Record<string, unknown> = { entityType: entityId }\n if (opts?.force) body.force = true\n const actionLabel =\n action === 'purge' ? t('query_index.table.actions.purge') : t('query_index.table.actions.reindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/query_index/${action}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityType: entityId, force: Boolean(opts?.force) },\n })\n } catch (err) {\n // Expected operational failures (e.g. a 503 when a search backend is not\n // configured) \u2014 surface a flash toast, not an alert or an error-level\n // stack dump that reads like an unhandled exception.\n const message = err instanceof Error && err.message ? err.message : errorMessage\n console.warn('[query_index] index action failed', message)\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [qc, t, runMutation, retryLastMutation],\n )\n\n const triggerVector = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.vectorPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.vectorPurge')\n : t('query_index.table.actions.vectorReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/search/embeddings/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId,\n purgeFirst: action === 'purge',\n }),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index.vector',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityId, purgeFirst: action === 'purge' },\n })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n console.warn('[query_index] vector action failed', message)\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t, runMutation, retryLastMutation],\n )\n\n const triggerFulltext = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.fulltextPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.fulltextPurge')\n : t('query_index.table.actions.fulltextReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/search/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n action: action === 'purge' ? 'clear' : 'reindex',\n entityId,\n }),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index.fulltext',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityId },\n })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n console.warn('[query_index] fulltext action failed', message)\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t, runMutation, retryLastMutation],\n )\n\n return (\n <>\n <DataTable\n title={t('query_index.nav.queryIndexes')}\n actions={(\n <>\n <Button\n variant=\"outline\"\n onClick={() => {\n setRefreshSeq((v) => v + 1)\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n }}\n >\n {t('query_index.table.refresh')}\n </Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n searchPlaceholder={t('query_index.table.searchPlaceholder')}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n perspective={{ tableId: 'query_index.status.list' }}\n rowActions={(row) => {\n const items: Array<{ id: string; label: string; onSelect: () => void; destructive?: boolean }> = [\n { id: 'reindex', label: t('query_index.table.actions.reindex'), onSelect: () => void trigger('reindex', row.entityId) },\n {\n id: 'reindex-force',\n label: t('query_index.table.actions.reindexForce'),\n onSelect: () => void trigger('reindex', row.entityId, { force: true }),\n },\n {\n id: 'purge',\n label: t('query_index.table.actions.purge'),\n destructive: true,\n onSelect: () => void trigger('purge', row.entityId),\n },\n ]\n\n if (row.vectorEnabled) {\n items.push(\n {\n id: 'vector-reindex',\n label: t('query_index.table.actions.vectorReindex'),\n onSelect: () => void triggerVector('reindex', row.entityId),\n },\n {\n id: 'vector-purge',\n label: t('query_index.table.actions.vectorPurge'),\n destructive: true,\n onSelect: () => void triggerVector('purge', row.entityId),\n },\n )\n }\n\n if (row.fulltextEnabled) {\n items.push(\n {\n id: 'fulltext-reindex',\n label: t('query_index.table.actions.fulltextReindex'),\n onSelect: () => void triggerFulltext('reindex', row.entityId),\n },\n {\n id: 'fulltext-purge',\n label: t('query_index.table.actions.fulltextPurge'),\n destructive: true,\n onSelect: () => void triggerFulltext('purge', row.entityId),\n },\n )\n }\n\n return <RowActions items={items} />\n }}\n pagination={{ page, pageSize: 50, total: rows.length, totalPages: 1, onPageChange: setPage }}\n isLoading={isLoading}\n />\n {ConfirmDialogElement}\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 { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
5
+ import {
6
+ runCrudMutationGuardAfterSuccess,
7
+ validateCrudMutationGuard
8
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
5
9
  import { createShipmentSchema } from "../../data/validators.js";
6
10
  import { shippingCarriersTag } from "../openapi.js";
7
11
  const metadata = {
@@ -20,12 +24,39 @@ async function POST(req) {
20
24
  }
21
25
  const container = await createRequestContainer();
22
26
  const service = container.resolve("shippingCarrierService");
27
+ const guardResult = await validateCrudMutationGuard(container, {
28
+ tenantId: auth.tenantId,
29
+ organizationId: auth.orgId,
30
+ userId: auth.sub ?? "",
31
+ resourceKind: "shipping_carriers.shipment",
32
+ resourceId: parsed.data.orderId,
33
+ operation: "create",
34
+ requestMethod: req.method,
35
+ requestHeaders: req.headers,
36
+ mutationPayload: parsed.data
37
+ });
38
+ if (guardResult && !guardResult.ok) {
39
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
40
+ }
23
41
  try {
24
42
  const shipment = await service.createShipment({
25
43
  ...parsed.data,
26
44
  organizationId: auth.orgId,
27
45
  tenantId: auth.tenantId
28
46
  });
47
+ if (guardResult?.shouldRunAfterSuccess) {
48
+ await runCrudMutationGuardAfterSuccess(container, {
49
+ tenantId: auth.tenantId,
50
+ organizationId: auth.orgId,
51
+ userId: auth.sub ?? "",
52
+ resourceKind: "shipping_carriers.shipment",
53
+ resourceId: shipment.id,
54
+ operation: "create",
55
+ requestMethod: req.method,
56
+ requestHeaders: req.headers,
57
+ metadata: guardResult.metadata ?? null
58
+ });
59
+ }
29
60
  return NextResponse.json({
30
61
  shipmentId: shipment.id,
31
62
  carrierShipmentId: shipment.carrierShipmentId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/shipping_carriers/api/shipments/route.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 type { ShippingCarrierService } from '../../lib/shipping-service'\nimport { createShipmentSchema } from '../../data/validators'\nimport { shippingCarriersTag } from '../openapi'\n\nexport const metadata = {\n path: '/shipping-carriers/shipments',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\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 const payload = await readJsonSafe<unknown>(req)\n const parsed = createShipmentSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n try {\n const shipment = await service.createShipment({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n return NextResponse.json({\n shipmentId: shipment.id,\n carrierShipmentId: shipment.carrierShipmentId,\n trackingNumber: shipment.trackingNumber,\n status: shipment.unifiedStatus,\n labelUrl: shipment.labelUrl,\n }, { status: 201 })\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Failed to create shipment'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Create shipment',\n methods: {\n POST: {\n summary: 'Create shipment',\n tags: [shippingCarriersTag],\n responses: [\n { status: 201, description: 'Shipment created' },\n { status: 422, description: 'Validation failed' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,0BAA0B,EAAE;AAC3E;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AACA,QAAM,UAAU,MAAM,aAAsB,GAAG;AAC/C,QAAM,SAAS,qBAAqB,UAAU,OAAO;AACrD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzG;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,UAAU,UAAU,QAAQ,wBAAwB;AAC1D,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,eAAe;AAAA,MAC5C,GAAG,OAAO;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,IACjB,CAAC;AACD,WAAO,aAAa,KAAK;AAAA,MACvB,YAAY,SAAS;AAAA,MACrB,mBAAmB,SAAS;AAAA,MAC5B,gBAAgB,SAAS;AAAA,MACzB,QAAQ,SAAS;AAAA,MACjB,UAAU,SAAS;AAAA,IACrB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpB,SAAS,OAAgB;AACvB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,mBAAmB;AAAA,EAC1B,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,MAAM,CAAC,mBAAmB;AAAA,MAC1B,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,mBAAmB;AAAA,QAC/C,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;",
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 {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { ShippingCarrierService } from '../../lib/shipping-service'\nimport { createShipmentSchema } from '../../data/validators'\nimport { shippingCarriersTag } from '../openapi'\n\nexport const metadata = {\n path: '/shipping-carriers/shipments',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\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 const payload = await readJsonSafe<unknown>(req)\n const parsed = createShipmentSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'shipping_carriers.shipment',\n resourceId: parsed.data.orderId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n try {\n const shipment = await service.createShipment({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'shipping_carriers.shipment',\n resourceId: shipment.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({\n shipmentId: shipment.id,\n carrierShipmentId: shipment.carrierShipmentId,\n trackingNumber: shipment.trackingNumber,\n status: shipment.unifiedStatus,\n labelUrl: shipment.labelUrl,\n }, { status: 201 })\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Failed to create shipment'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Create shipment',\n methods: {\n POST: {\n summary: 'Create shipment',\n tags: [shippingCarriersTag],\n responses: [\n { status: 201, description: 'Shipment created' },\n { status: 422, description: 'Validation failed' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,0BAA0B,EAAE;AAC3E;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AACA,QAAM,UAAU,MAAM,aAAsB,GAAG;AAC/C,QAAM,SAAS,qBAAqB,UAAU,OAAO;AACrD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzG;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,UAAU,UAAU,QAAQ,wBAAwB;AAC1D,QAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,IAC7D,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,IACrB,QAAQ,KAAK,OAAO;AAAA,IACpB,cAAc;AAAA,IACd,YAAY,OAAO,KAAK;AAAA,IACxB,WAAW;AAAA,IACX,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,OAAO;AAAA,EAC1B,CAAC;AACD,MAAI,eAAe,CAAC,YAAY,IAAI;AAClC,WAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,EAC3E;AACA,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,eAAe;AAAA,MAC5C,GAAG,OAAO;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,IACjB,CAAC;AACD,QAAI,aAAa,uBAAuB;AACtC,YAAM,iCAAiC,WAAW;AAAA,QAChD,UAAU,KAAK;AAAA,QACf,gBAAgB,KAAK;AAAA,QACrB,QAAQ,KAAK,OAAO;AAAA,QACpB,cAAc;AAAA,QACd,YAAY,SAAS;AAAA,QACrB,WAAW;AAAA,QACX,eAAe,IAAI;AAAA,QACnB,gBAAgB,IAAI;AAAA,QACpB,UAAU,YAAY,YAAY;AAAA,MACpC,CAAC;AAAA,IACH;AACA,WAAO,aAAa,KAAK;AAAA,MACvB,YAAY,SAAS;AAAA,MACrB,mBAAmB,SAAS;AAAA,MAC5B,gBAAgB,SAAS;AAAA,MACzB,QAAQ,SAAS;AAAA,MACjB,UAAU,SAAS;AAAA,IACrB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpB,SAAS,OAAgB;AACvB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,mBAAmB;AAAA,EAC1B,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,MAAM,CAAC,mBAAmB;AAAA,MAC1B,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,mBAAmB;AAAA,QAC/C,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }