@open-mercato/core 0.4.6-develop-bbfd75b1c8 → 0.4.6-develop-79b76432fd

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 (28) hide show
  1. package/dist/modules/integrations/api/[id]/route.js +9 -1
  2. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  3. package/dist/modules/integrations/backend/integrations/[id]/page.js +5 -1
  4. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  5. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +5 -1
  6. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  7. package/dist/modules/integrations/lib/credentials-service.js +5 -1
  8. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  9. package/dist/modules/integrations/lib/health-service.js.map +2 -2
  10. package/dist/modules/integrations/lib/log-service.js.map +2 -2
  11. package/dist/modules/integrations/lib/state-service.js.map +2 -2
  12. package/dist/modules/sales/api/quotes/convert/route.js +31 -14
  13. package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
  14. package/dist/modules/sales/api/quotes/send/route.js +31 -14
  15. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  16. package/package.json +2 -2
  17. package/src/modules/integrations/api/[id]/route.ts +9 -1
  18. package/src/modules/integrations/backend/integrations/[id]/page.tsx +8 -2
  19. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +8 -2
  20. package/src/modules/integrations/lib/credentials-service.ts +6 -2
  21. package/src/modules/integrations/lib/health-service.ts +1 -2
  22. package/src/modules/integrations/lib/log-service.ts +1 -1
  23. package/src/modules/integrations/lib/state-service.ts +1 -1
  24. package/src/modules/sales/api/quotes/convert/route.ts +55 -14
  25. package/src/modules/sales/api/quotes/send/route.ts +55 -14
  26. package/dist/modules/integrations/lib/types.js +0 -1
  27. package/dist/modules/integrations/lib/types.js.map +0 -7
  28. package/src/modules/integrations/lib/types.ts +0 -4
@@ -37,9 +37,17 @@ async function GET(req, ctx) {
37
37
  const bundleIntegrations = integration.bundleId ? await Promise.all(
38
38
  getBundleIntegrations(integration.bundleId).map(async (item) => {
39
39
  const itemState = await stateService.get(item.id, scope);
40
+ const resolvedState = {
41
+ isEnabled: itemState?.isEnabled ?? true,
42
+ apiVersion: itemState?.apiVersion ?? null,
43
+ reauthRequired: itemState?.reauthRequired ?? false,
44
+ lastHealthStatus: itemState?.lastHealthStatus ?? null,
45
+ lastHealthCheckedAt: itemState?.lastHealthCheckedAt?.toISOString() ?? null
46
+ };
40
47
  return {
41
48
  ...item,
42
- isEnabled: itemState?.isEnabled ?? true
49
+ isEnabled: resolvedState.isEnabled,
50
+ state: resolvedState
43
51
  };
44
52
  })
45
53
  ) : [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/integrations/api/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getBundle, getBundleIntegrations, getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { CredentialsService } from '../../lib/credentials-service'\nimport type { IntegrationStateService } from '../../lib/state-service'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get integration detail',\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const [credentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, scope),\n stateService.get(integration.id, scope),\n ])\n\n const bundle = integration.bundleId ? getBundle(integration.bundleId) : undefined\n const bundleIntegrations = integration.bundleId\n ? await Promise.all(\n getBundleIntegrations(integration.bundleId).map(async (item) => {\n const itemState = await stateService.get(item.id, scope)\n return {\n ...item,\n isEnabled: itemState?.isEnabled ?? true,\n }\n }),\n )\n : []\n\n return NextResponse.json({\n integration,\n bundle,\n bundleIntegrations,\n state: {\n isEnabled: state?.isEnabled ?? true,\n apiVersion: state?.apiVersion ?? null,\n reauthRequired: state?.reauthRequired ?? false,\n lastHealthStatus: state?.lastHealthStatus ?? null,\n lastHealthCheckedAt: state?.lastHealthCheckedAt?.toISOString() ?? null,\n },\n hasCredentials: Boolean(credentials),\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,WAAW,uBAAuB,sBAAsB;AAIjE,MAAM,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAElD,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,cAAc;AAAA,EACrB,SAAS;AACX;AAEA,eAAsB,IAAI,KAAc,KAA8D;AACpG,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;AAEA,QAAM,YAAa,IAAI,UAAU,OAAQ,IAAI,OAA4B,SAAS,aAC9E,MAAO,IAAI,SACV,IAAI;AAET,QAAM,eAAe,eAAe,UAAU,SAAS;AACvD,MAAI,CAAC,aAAa,SAAS;AACzB,WAAO,aAAa,KAAK,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC/E;AAEA,QAAM,cAAc,eAAe,aAAa,KAAK,EAAE;AACvD,MAAI,CAAC,aAAa;AAChB,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,eAAe,UAAU,QAAQ,yBAAyB;AAChE,QAAM,QAAQ,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAS;AAE9E,QAAM,CAAC,aAAa,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7C,mBAAmB,QAAQ,YAAY,IAAI,KAAK;AAAA,IAChD,aAAa,IAAI,YAAY,IAAI,KAAK;AAAA,EACxC,CAAC;AAED,QAAM,SAAS,YAAY,WAAW,UAAU,YAAY,QAAQ,IAAI;AACxE,QAAM,qBAAqB,YAAY,WACnC,MAAM,QAAQ;AAAA,IACd,sBAAsB,YAAY,QAAQ,EAAE,IAAI,OAAO,SAAS;AAC9D,YAAM,YAAY,MAAM,aAAa,IAAI,KAAK,IAAI,KAAK;AACvD,aAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW,WAAW,aAAa;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH,IACE,CAAC;AAEL,SAAO,aAAa,KAAK;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,MACL,WAAW,OAAO,aAAa;AAAA,MAC/B,YAAY,OAAO,cAAc;AAAA,MACjC,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,kBAAkB,OAAO,oBAAoB;AAAA,MAC7C,qBAAqB,OAAO,qBAAqB,YAAY,KAAK;AAAA,IACpE;AAAA,IACA,gBAAgB,QAAQ,WAAW;AAAA,EACrC,CAAC;AACH;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getBundle, getBundleIntegrations, getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { CredentialsService } from '../../lib/credentials-service'\nimport type { IntegrationStateService } from '../../lib/state-service'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get integration detail',\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')\n ? await (ctx.params as Promise<{ id?: string }>)\n : (ctx.params as { id?: string } | undefined)\n\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const [credentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, scope),\n stateService.get(integration.id, scope),\n ])\n\n const bundle = integration.bundleId ? getBundle(integration.bundleId) : undefined\n const bundleIntegrations = integration.bundleId\n ? await Promise.all(\n getBundleIntegrations(integration.bundleId).map(async (item) => {\n const itemState = await stateService.get(item.id, scope)\n const resolvedState = {\n isEnabled: itemState?.isEnabled ?? true,\n apiVersion: itemState?.apiVersion ?? null,\n reauthRequired: itemState?.reauthRequired ?? false,\n lastHealthStatus: itemState?.lastHealthStatus ?? null,\n lastHealthCheckedAt: itemState?.lastHealthCheckedAt?.toISOString() ?? null,\n }\n return {\n ...item,\n isEnabled: resolvedState.isEnabled,\n state: resolvedState,\n }\n }),\n )\n : []\n\n return NextResponse.json({\n integration,\n bundle,\n bundleIntegrations,\n state: {\n isEnabled: state?.isEnabled ?? true,\n apiVersion: state?.apiVersion ?? null,\n reauthRequired: state?.reauthRequired ?? false,\n lastHealthStatus: state?.lastHealthStatus ?? null,\n lastHealthCheckedAt: state?.lastHealthCheckedAt?.toISOString() ?? null,\n },\n hasCredentials: Boolean(credentials),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,WAAW,uBAAuB,sBAAsB;AAIjE,MAAM,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAElD,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,cAAc;AAAA,EACrB,SAAS;AACX;AAEA,eAAsB,IAAI,KAAc,KAA8D;AACpG,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;AAEA,QAAM,YAAa,IAAI,UAAU,OAAQ,IAAI,OAA4B,SAAS,aAC9E,MAAO,IAAI,SACV,IAAI;AAET,QAAM,eAAe,eAAe,UAAU,SAAS;AACvD,MAAI,CAAC,aAAa,SAAS;AACzB,WAAO,aAAa,KAAK,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC/E;AAEA,QAAM,cAAc,eAAe,aAAa,KAAK,EAAE;AACvD,MAAI,CAAC,aAAa;AAChB,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,eAAe,UAAU,QAAQ,yBAAyB;AAChE,QAAM,QAAQ,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAS;AAE9E,QAAM,CAAC,aAAa,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7C,mBAAmB,QAAQ,YAAY,IAAI,KAAK;AAAA,IAChD,aAAa,IAAI,YAAY,IAAI,KAAK;AAAA,EACxC,CAAC;AAED,QAAM,SAAS,YAAY,WAAW,UAAU,YAAY,QAAQ,IAAI;AACxE,QAAM,qBAAqB,YAAY,WACnC,MAAM,QAAQ;AAAA,IACd,sBAAsB,YAAY,QAAQ,EAAE,IAAI,OAAO,SAAS;AAC9D,YAAM,YAAY,MAAM,aAAa,IAAI,KAAK,IAAI,KAAK;AACvD,YAAM,gBAAgB;AAAA,QACpB,WAAW,WAAW,aAAa;AAAA,QACnC,YAAY,WAAW,cAAc;AAAA,QACrC,gBAAgB,WAAW,kBAAkB;AAAA,QAC7C,kBAAkB,WAAW,oBAAoB;AAAA,QACjD,qBAAqB,WAAW,qBAAqB,YAAY,KAAK;AAAA,MACxE;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW,cAAc;AAAA,QACzB,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH,IACE,CAAC;AAEL,SAAO,aAAa,KAAK;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,MACL,WAAW,OAAO,aAAa;AAAA,MAC/B,YAAY,OAAO,cAAc;AAAA,MACjC,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,kBAAkB,OAAO,oBAAoB;AAAA,MAC7C,qBAAqB,OAAO,qBAAqB,YAAY,KAAK;AAAA,IACpE;AAAA,IACA,gBAAgB,QAAQ,WAAW;AAAA,EACrC,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -16,6 +16,10 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
16
16
  import { useT } from "@open-mercato/shared/lib/i18n/context";
17
17
  import { LoadingMessage } from "@open-mercato/ui/backend/detail";
18
18
  import { ErrorMessage } from "@open-mercato/ui/backend/detail";
19
+ const UNSUPPORTED_CREDENTIAL_FIELD_TYPES = /* @__PURE__ */ new Set(["oauth", "ssh_keypair"]);
20
+ function isEditableCredentialField(field) {
21
+ return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type);
22
+ }
19
23
  const LOG_LEVEL_STYLES = {
20
24
  info: "bg-blue-100 text-blue-800",
21
25
  warn: "bg-yellow-100 text-yellow-800",
@@ -190,7 +194,7 @@ function IntegrationDetailPage() {
190
194
  /* @__PURE__ */ jsxs(TabsContent, { value: "credentials", className: "space-y-4 mt-4", children: [
191
195
  detail.bundle && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800", children: t("integrations.detail.credentials.bundleShared", { bundle: detail.bundle.title }) }),
192
196
  credFields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: t("integrations.detail.credentials.notConfigured") }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "pt-6 space-y-4", children: [
193
- credFields.filter((f) => f.type !== "oauth" && f.type !== "ssh_keypair").map((field) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
197
+ credFields.filter(isEditableCredentialField).map((field) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
194
198
  /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium", children: [
195
199
  field.label,
196
200
  field.required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/integrations/backend/integrations/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\ntype ApiVersion = {\n id: string\n label?: string\n status: 'stable' | 'deprecated' | 'experimental'\n sunsetAt?: string\n migrationGuide?: string\n}\n\ntype IntegrationDetail = {\n integration: {\n id: string\n title: string\n description?: string\n category?: string\n hub?: string\n bundleId?: string\n docsUrl?: string\n apiVersions?: ApiVersion[]\n credentials?: { fields: CredentialField[] }\n }\n bundle?: { id: string; title: string; credentials?: { fields: CredentialField[] } }\n state: {\n isEnabled: boolean\n apiVersion: string | null\n reauthRequired: boolean\n lastHealthStatus: string | null\n lastHealthCheckedAt: string | null\n }\n hasCredentials: boolean\n}\n\ntype LogEntry = {\n id: string\n level: 'info' | 'warn' | 'error'\n message: string\n createdAt: string\n code?: string\n}\n\nconst LOG_LEVEL_STYLES: Record<string, string> = {\n info: 'bg-blue-100 text-blue-800',\n warn: 'bg-yellow-100 text-yellow-800',\n error: 'bg-red-100 text-red-800',\n}\n\nconst HEALTH_STATUS_STYLES: Record<string, string> = {\n healthy: 'bg-green-100 text-green-800',\n degraded: 'bg-yellow-100 text-yellow-800',\n unhealthy: 'bg-red-100 text-red-800',\n}\n\nexport default function IntegrationDetailPage() {\n const params = useParams<{ id: string }>()\n const integrationId = params.id\n const t = useT()\n\n const [detail, setDetail] = React.useState<IntegrationDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n\n const [logs, setLogs] = React.useState<LogEntry[]>([])\n const [logLevel, setLogLevel] = React.useState<string>('')\n const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)\n\n const [isCheckingHealth, setIsCheckingHealth] = React.useState(false)\n const [isTogglingState, setIsTogglingState] = React.useState(false)\n\n const loadDetail = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n const call = await apiCall<IntegrationDetail>(\n `/api/integrations/${encodeURIComponent(integrationId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n setIsLoading(false)\n }, [integrationId, t])\n\n const loadCredentials = React.useCallback(async () => {\n const call = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(integrationId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (call.ok && call.result?.credentials) {\n setCredValues(call.result.credentials)\n }\n }, [integrationId])\n\n const loadLogs = React.useCallback(async () => {\n setIsLoadingLogs(true)\n const params = new URLSearchParams({ integrationId, pageSize: '50' })\n if (logLevel) params.set('level', logLevel)\n const call = await apiCall<{ items: LogEntry[] }>(\n `/api/integrations/logs?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && call.result) {\n setLogs(call.result.items)\n }\n setIsLoadingLogs(false)\n }, [integrationId, logLevel])\n\n React.useEffect(() => { void loadDetail() }, [loadDetail])\n React.useEffect(() => { void loadCredentials() }, [loadCredentials])\n React.useEffect(() => { void loadLogs() }, [loadLogs])\n\n const handleToggleState = React.useCallback(async (enabled: boolean) => {\n setIsTogglingState(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => prev ? { ...prev, state: { ...prev.state, isEnabled: enabled } } : prev)\n flash(t('integrations.detail.stateUpdated'), 'success')\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setIsTogglingState(false)\n }, [integrationId, t])\n\n const handleSaveCredentials = React.useCallback(async () => {\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [integrationId, credValues, t])\n\n const handleVersionChange = React.useCallback(async (version: string) => {\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/version`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiVersion: version }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev)\n flash(t('integrations.detail.version.saved'), 'success')\n } else {\n flash(t('integrations.detail.version.saveError'), 'error')\n }\n }, [integrationId, t])\n\n const handleHealthCheck = React.useCallback(async () => {\n setIsCheckingHealth(true)\n const call = await apiCall<{ status: string; checkedAt: string }>(\n `/api/integrations/${encodeURIComponent(integrationId)}/health`,\n { method: 'POST' },\n { fallback: null },\n )\n if (call.ok && call.result) {\n setDetail((prev) => prev ? {\n ...prev,\n state: {\n ...prev.state,\n lastHealthStatus: call.result!.status,\n lastHealthCheckedAt: call.result!.checkedAt,\n },\n } : prev)\n } else {\n flash(t('integrations.detail.health.checkError'), 'error')\n }\n setIsCheckingHealth(false)\n }, [integrationId, t])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.detail.title')} /></PageBody></Page>\n if (error || !detail) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const { integration, state } = detail\n const credFields = integration.credentials?.fields ?? detail.bundle?.credentials?.fields ?? []\n const hasVersions = Boolean(integration.apiVersions?.length)\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{integration.title}</h1>\n {integration.description && (\n <p className=\"text-muted-foreground mt-1\">{integration.description}</p>\n )}\n <div className=\"flex gap-2 mt-2\">\n {integration.category && <Badge variant=\"secondary\">{integration.category}</Badge>}\n {integration.hub && <Badge variant=\"outline\">{integration.hub}</Badge>}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm text-muted-foreground\">\n {state.isEnabled ? t('integrations.detail.enable') : t('integrations.detail.disable')}\n </span>\n <Switch\n checked={state.isEnabled}\n disabled={isTogglingState}\n onCheckedChange={(checked) => void handleToggleState(checked)}\n />\n </div>\n </div>\n\n <Tabs defaultValue=\"credentials\">\n <TabsList>\n <TabsTrigger value=\"credentials\">{t('integrations.detail.tabs.credentials')}</TabsTrigger>\n {hasVersions && <TabsTrigger value=\"version\">{t('integrations.detail.tabs.version')}</TabsTrigger>}\n <TabsTrigger value=\"health\">{t('integrations.detail.tabs.health')}</TabsTrigger>\n <TabsTrigger value=\"logs\">{t('integrations.detail.tabs.logs')}</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"credentials\" className=\"space-y-4 mt-4\">\n {detail.bundle && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800\">\n {t('integrations.detail.credentials.bundleShared', { bundle: detail.bundle.title })}\n </div>\n )}\n {credFields.length === 0 ? (\n <p className=\"text-muted-foreground text-sm\">{t('integrations.detail.credentials.notConfigured')}</p>\n ) : (\n <Card>\n <CardContent className=\"pt-6 space-y-4\">\n {credFields.filter((f) => f.type !== 'oauth' && f.type !== 'ssh_keypair').map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <select\n className=\"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n >\n <option value=\"\">\u2014</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n </TabsContent>\n\n {hasVersions && (\n <TabsContent value=\"version\" className=\"space-y-4 mt-4\">\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.detail.version.select')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {integration.apiVersions!.map((v) => {\n const isSelected = (state.apiVersion ?? integration.apiVersions!.find((x) => x.status === 'stable')?.id) === v.id\n return (\n <div\n key={v.id}\n className={`flex items-center justify-between rounded-lg border p-3 cursor-pointer transition-colors ${isSelected ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'}`}\n onClick={() => void handleVersionChange(v.id)}\n >\n <div>\n <span className=\"font-medium text-sm\">{v.label ?? v.id}</span>\n <Badge\n variant={v.status === 'stable' ? 'default' : v.status === 'deprecated' ? 'destructive' : 'secondary'}\n className=\"ml-2\"\n >\n {t(`integrations.detail.version.${v.status}`)}\n </Badge>\n {v.status === 'deprecated' && v.sunsetAt && (\n <span className=\"text-xs text-muted-foreground ml-2\">\n {t('integrations.detail.version.sunsetAt', { date: new Date(v.sunsetAt).toLocaleDateString() })}\n </span>\n )}\n </div>\n {isSelected && <Badge variant=\"outline\">{t('integrations.detail.version.current')}</Badge>}\n </div>\n )\n })}\n </CardContent>\n </Card>\n </TabsContent>\n )}\n\n <TabsContent value=\"health\" className=\"space-y-4 mt-4\">\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.detail.health.title')}</CardTitle>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleHealthCheck()} disabled={isCheckingHealth}>\n {isCheckingHealth ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {isCheckingHealth ? t('integrations.detail.health.checking') : t('integrations.detail.health.check')}\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm font-medium\">{t('integrations.detail.health.title')}:</span>\n {state.lastHealthStatus ? (\n <Badge className={HEALTH_STATUS_STYLES[state.lastHealthStatus] ?? ''}>\n {t(`integrations.detail.health.${state.lastHealthStatus}`)}\n </Badge>\n ) : (\n <span className=\"text-sm text-muted-foreground\">{t('integrations.detail.health.unknown')}</span>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {state.lastHealthCheckedAt\n ? t('integrations.detail.health.lastChecked', { date: new Date(state.lastHealthCheckedAt).toLocaleString() })\n : t('integrations.detail.health.neverChecked')\n }\n </p>\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"logs\" className=\"space-y-4 mt-4\">\n <div className=\"flex items-center gap-3\">\n <select\n className=\"flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={logLevel}\n onChange={(e) => setLogLevel(e.target.value)}\n >\n <option value=\"\">{t('integrations.detail.logs.level.all')}</option>\n <option value=\"info\">{t('integrations.detail.logs.level.info')}</option>\n <option value=\"warn\">{t('integrations.detail.logs.level.warn')}</option>\n <option value=\"error\">{t('integrations.detail.logs.level.error')}</option>\n </select>\n </div>\n {isLoadingLogs ? (\n <div className=\"flex justify-center py-8\"><Spinner /></div>\n ) : logs.length === 0 ? (\n <p className=\"text-muted-foreground text-sm py-4\">{t('integrations.detail.logs.empty')}</p>\n ) : (\n <div className=\"rounded-lg border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.time')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.level')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.message')}</th>\n </tr>\n </thead>\n <tbody>\n {logs.map((log) => (\n <tr key={log.id} className=\"border-b last:border-0\">\n <td className=\"px-4 py-2 text-muted-foreground whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleString()}\n </td>\n <td className=\"px-4 py-2\">\n <Badge variant=\"secondary\" className={LOG_LEVEL_STYLES[log.level] ?? ''}>\n {log.level}\n </Badge>\n </td>\n <td className=\"px-4 py-2\">{log.message}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </TabsContent>\n </Tabs>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\nconst UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])\n\nfunction isEditableCredentialField(field: CredentialField): boolean {\n return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)\n}\n\ntype ApiVersion = {\n id: string\n label?: string\n status: 'stable' | 'deprecated' | 'experimental'\n sunsetAt?: string\n migrationGuide?: string\n}\n\ntype IntegrationDetail = {\n integration: {\n id: string\n title: string\n description?: string\n category?: string\n hub?: string\n bundleId?: string\n docsUrl?: string\n apiVersions?: ApiVersion[]\n credentials?: { fields: CredentialField[] }\n }\n bundle?: { id: string; title: string; credentials?: { fields: CredentialField[] } }\n state: {\n isEnabled: boolean\n apiVersion: string | null\n reauthRequired: boolean\n lastHealthStatus: string | null\n lastHealthCheckedAt: string | null\n }\n hasCredentials: boolean\n}\n\ntype LogEntry = {\n id: string\n level: 'info' | 'warn' | 'error'\n message: string\n createdAt: string\n code?: string\n}\n\nconst LOG_LEVEL_STYLES: Record<string, string> = {\n info: 'bg-blue-100 text-blue-800',\n warn: 'bg-yellow-100 text-yellow-800',\n error: 'bg-red-100 text-red-800',\n}\n\nconst HEALTH_STATUS_STYLES: Record<string, string> = {\n healthy: 'bg-green-100 text-green-800',\n degraded: 'bg-yellow-100 text-yellow-800',\n unhealthy: 'bg-red-100 text-red-800',\n}\n\nexport default function IntegrationDetailPage() {\n const params = useParams<{ id: string }>()\n const integrationId = params.id\n const t = useT()\n\n const [detail, setDetail] = React.useState<IntegrationDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n\n const [logs, setLogs] = React.useState<LogEntry[]>([])\n const [logLevel, setLogLevel] = React.useState<string>('')\n const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)\n\n const [isCheckingHealth, setIsCheckingHealth] = React.useState(false)\n const [isTogglingState, setIsTogglingState] = React.useState(false)\n\n const loadDetail = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n const call = await apiCall<IntegrationDetail>(\n `/api/integrations/${encodeURIComponent(integrationId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n setIsLoading(false)\n }, [integrationId, t])\n\n const loadCredentials = React.useCallback(async () => {\n const call = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(integrationId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (call.ok && call.result?.credentials) {\n setCredValues(call.result.credentials)\n }\n }, [integrationId])\n\n const loadLogs = React.useCallback(async () => {\n setIsLoadingLogs(true)\n const params = new URLSearchParams({ integrationId, pageSize: '50' })\n if (logLevel) params.set('level', logLevel)\n const call = await apiCall<{ items: LogEntry[] }>(\n `/api/integrations/logs?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && call.result) {\n setLogs(call.result.items)\n }\n setIsLoadingLogs(false)\n }, [integrationId, logLevel])\n\n React.useEffect(() => { void loadDetail() }, [loadDetail])\n React.useEffect(() => { void loadCredentials() }, [loadCredentials])\n React.useEffect(() => { void loadLogs() }, [loadLogs])\n\n const handleToggleState = React.useCallback(async (enabled: boolean) => {\n setIsTogglingState(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => prev ? { ...prev, state: { ...prev.state, isEnabled: enabled } } : prev)\n flash(t('integrations.detail.stateUpdated'), 'success')\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setIsTogglingState(false)\n }, [integrationId, t])\n\n const handleSaveCredentials = React.useCallback(async () => {\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [integrationId, credValues, t])\n\n const handleVersionChange = React.useCallback(async (version: string) => {\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/version`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiVersion: version }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev)\n flash(t('integrations.detail.version.saved'), 'success')\n } else {\n flash(t('integrations.detail.version.saveError'), 'error')\n }\n }, [integrationId, t])\n\n const handleHealthCheck = React.useCallback(async () => {\n setIsCheckingHealth(true)\n const call = await apiCall<{ status: string; checkedAt: string }>(\n `/api/integrations/${encodeURIComponent(integrationId)}/health`,\n { method: 'POST' },\n { fallback: null },\n )\n if (call.ok && call.result) {\n setDetail((prev) => prev ? {\n ...prev,\n state: {\n ...prev.state,\n lastHealthStatus: call.result!.status,\n lastHealthCheckedAt: call.result!.checkedAt,\n },\n } : prev)\n } else {\n flash(t('integrations.detail.health.checkError'), 'error')\n }\n setIsCheckingHealth(false)\n }, [integrationId, t])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.detail.title')} /></PageBody></Page>\n if (error || !detail) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const { integration, state } = detail\n const credFields = integration.credentials?.fields ?? detail.bundle?.credentials?.fields ?? []\n const hasVersions = Boolean(integration.apiVersions?.length)\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{integration.title}</h1>\n {integration.description && (\n <p className=\"text-muted-foreground mt-1\">{integration.description}</p>\n )}\n <div className=\"flex gap-2 mt-2\">\n {integration.category && <Badge variant=\"secondary\">{integration.category}</Badge>}\n {integration.hub && <Badge variant=\"outline\">{integration.hub}</Badge>}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm text-muted-foreground\">\n {state.isEnabled ? t('integrations.detail.enable') : t('integrations.detail.disable')}\n </span>\n <Switch\n checked={state.isEnabled}\n disabled={isTogglingState}\n onCheckedChange={(checked) => void handleToggleState(checked)}\n />\n </div>\n </div>\n\n <Tabs defaultValue=\"credentials\">\n <TabsList>\n <TabsTrigger value=\"credentials\">{t('integrations.detail.tabs.credentials')}</TabsTrigger>\n {hasVersions && <TabsTrigger value=\"version\">{t('integrations.detail.tabs.version')}</TabsTrigger>}\n <TabsTrigger value=\"health\">{t('integrations.detail.tabs.health')}</TabsTrigger>\n <TabsTrigger value=\"logs\">{t('integrations.detail.tabs.logs')}</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"credentials\" className=\"space-y-4 mt-4\">\n {detail.bundle && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800\">\n {t('integrations.detail.credentials.bundleShared', { bundle: detail.bundle.title })}\n </div>\n )}\n {credFields.length === 0 ? (\n <p className=\"text-muted-foreground text-sm\">{t('integrations.detail.credentials.notConfigured')}</p>\n ) : (\n <Card>\n <CardContent className=\"pt-6 space-y-4\">\n {credFields.filter(isEditableCredentialField).map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <select\n className=\"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n >\n <option value=\"\">\u2014</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n </TabsContent>\n\n {hasVersions && (\n <TabsContent value=\"version\" className=\"space-y-4 mt-4\">\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.detail.version.select')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {integration.apiVersions!.map((v) => {\n const isSelected = (state.apiVersion ?? integration.apiVersions!.find((x) => x.status === 'stable')?.id) === v.id\n return (\n <div\n key={v.id}\n className={`flex items-center justify-between rounded-lg border p-3 cursor-pointer transition-colors ${isSelected ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'}`}\n onClick={() => void handleVersionChange(v.id)}\n >\n <div>\n <span className=\"font-medium text-sm\">{v.label ?? v.id}</span>\n <Badge\n variant={v.status === 'stable' ? 'default' : v.status === 'deprecated' ? 'destructive' : 'secondary'}\n className=\"ml-2\"\n >\n {t(`integrations.detail.version.${v.status}`)}\n </Badge>\n {v.status === 'deprecated' && v.sunsetAt && (\n <span className=\"text-xs text-muted-foreground ml-2\">\n {t('integrations.detail.version.sunsetAt', { date: new Date(v.sunsetAt).toLocaleDateString() })}\n </span>\n )}\n </div>\n {isSelected && <Badge variant=\"outline\">{t('integrations.detail.version.current')}</Badge>}\n </div>\n )\n })}\n </CardContent>\n </Card>\n </TabsContent>\n )}\n\n <TabsContent value=\"health\" className=\"space-y-4 mt-4\">\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.detail.health.title')}</CardTitle>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleHealthCheck()} disabled={isCheckingHealth}>\n {isCheckingHealth ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {isCheckingHealth ? t('integrations.detail.health.checking') : t('integrations.detail.health.check')}\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm font-medium\">{t('integrations.detail.health.title')}:</span>\n {state.lastHealthStatus ? (\n <Badge className={HEALTH_STATUS_STYLES[state.lastHealthStatus] ?? ''}>\n {t(`integrations.detail.health.${state.lastHealthStatus}`)}\n </Badge>\n ) : (\n <span className=\"text-sm text-muted-foreground\">{t('integrations.detail.health.unknown')}</span>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {state.lastHealthCheckedAt\n ? t('integrations.detail.health.lastChecked', { date: new Date(state.lastHealthCheckedAt).toLocaleString() })\n : t('integrations.detail.health.neverChecked')\n }\n </p>\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"logs\" className=\"space-y-4 mt-4\">\n <div className=\"flex items-center gap-3\">\n <select\n className=\"flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={logLevel}\n onChange={(e) => setLogLevel(e.target.value)}\n >\n <option value=\"\">{t('integrations.detail.logs.level.all')}</option>\n <option value=\"info\">{t('integrations.detail.logs.level.info')}</option>\n <option value=\"warn\">{t('integrations.detail.logs.level.warn')}</option>\n <option value=\"error\">{t('integrations.detail.logs.level.error')}</option>\n </select>\n </div>\n {isLoadingLogs ? (\n <div className=\"flex justify-center py-8\"><Spinner /></div>\n ) : logs.length === 0 ? (\n <p className=\"text-muted-foreground text-sm py-4\">{t('integrations.detail.logs.empty')}</p>\n ) : (\n <div className=\"rounded-lg border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.time')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.level')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.message')}</th>\n </tr>\n </thead>\n <tbody>\n {logs.map((log) => (\n <tr key={log.id} className=\"border-b last:border-0\">\n <td className=\"px-4 py-2 text-muted-foreground whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleString()}\n </td>\n <td className=\"px-4 py-2\">\n <Badge variant=\"secondary\" className={LOG_LEVEL_STYLES[log.level] ?? ''}>\n {log.level}\n </Badge>\n </td>\n <td className=\"px-4 py-2\">{log.message}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </TabsContent>\n </Tabs>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["params"]
7
7
  }
@@ -15,6 +15,10 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
15
15
  import { useT } from "@open-mercato/shared/lib/i18n/context";
16
16
  import { LoadingMessage } from "@open-mercato/ui/backend/detail";
17
17
  import { ErrorMessage } from "@open-mercato/ui/backend/detail";
18
+ const UNSUPPORTED_CREDENTIAL_FIELD_TYPES = /* @__PURE__ */ new Set(["oauth", "ssh_keypair"]);
19
+ function isEditableCredentialField(field) {
20
+ return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type);
21
+ }
18
22
  function BundleConfigPage() {
19
23
  const params = useParams();
20
24
  const bundleId = params.id;
@@ -99,7 +103,7 @@ function BundleConfigPage() {
99
103
  }, [detail, handleToggle]);
100
104
  if (isLoading) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("integrations.bundle.title") }) }) });
101
105
  if (error || !detail?.bundle) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("integrations.detail.loadError") }) }) });
102
- const credFields = detail.bundle.credentials?.fields ?? [];
106
+ const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField);
103
107
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
104
108
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Link, { href: "/backend/integrations", className: "text-sm text-muted-foreground hover:underline", children: t("integrations.detail.back") }) }),
105
109
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/integrations/backend/integrations/bundle/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\ntype BundleIntegration = {\n id: string\n title: string\n description?: string\n category?: string\n isEnabled: boolean\n}\n\ntype BundleDetail = {\n integration: {\n id: string\n title: string\n description?: string\n bundleId?: string\n }\n bundle?: {\n id: string\n title: string\n description?: string\n credentials?: { fields: CredentialField[] }\n }\n bundleIntegrations: BundleIntegration[]\n state: { isEnabled: boolean }\n hasCredentials: boolean\n}\n\nexport default function BundleConfigPage() {\n const params = useParams<{ id: string }>()\n const bundleId = params.id\n const t = useT()\n\n const [detail, setDetail] = React.useState<BundleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n const call = await apiCall<BundleDetail>(\n `/api/integrations/${encodeURIComponent(bundleId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n\n const credCall = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(bundleId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (credCall.ok && credCall.result?.credentials) {\n setCredValues(credCall.result.credentials)\n }\n setIsLoading(false)\n }, [bundleId, t])\n\n React.useEffect(() => { void load() }, [load])\n\n const handleSaveCredentials = React.useCallback(async () => {\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(bundleId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [bundleId, credValues, t])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n bundleIntegrations: prev.bundleIntegrations.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const handleBulkToggle = React.useCallback(async (enabled: boolean) => {\n if (!detail) return\n const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled)\n await Promise.all(targets.map((item) => handleToggle(item.id, enabled)))\n }, [detail, handleToggle])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>\n if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const credFields = detail.bundle.credentials?.fields ?? []\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold\">{detail.bundle.title}</h1>\n {detail.bundle.description && (\n <p className=\"text-muted-foreground mt-1\">{detail.bundle.description}</p>\n )}\n </div>\n\n {credFields.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {credFields.map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <select\n className=\"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n >\n <option value=\"\">\u2014</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.bundle.integrationToggles')}</CardTitle>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(true)}>\n {t('integrations.marketplace.enableAll')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(false)}>\n {t('integrations.marketplace.disableAll')}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {detail.bundleIntegrations.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant=\"secondary\" className=\"ml-2 text-xs\">{item.category}</Badge>\n )}\n {item.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{item.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.bundle.configureIntegration')}\n </Link>\n </Button>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\nconst UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])\n\nfunction isEditableCredentialField(field: CredentialField): boolean {\n return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)\n}\n\ntype BundleIntegration = {\n id: string\n title: string\n description?: string\n category?: string\n isEnabled: boolean\n}\n\ntype BundleDetail = {\n integration: {\n id: string\n title: string\n description?: string\n bundleId?: string\n }\n bundle?: {\n id: string\n title: string\n description?: string\n credentials?: { fields: CredentialField[] }\n }\n bundleIntegrations: BundleIntegration[]\n state: { isEnabled: boolean }\n hasCredentials: boolean\n}\n\nexport default function BundleConfigPage() {\n const params = useParams<{ id: string }>()\n const bundleId = params.id\n const t = useT()\n\n const [detail, setDetail] = React.useState<BundleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n const call = await apiCall<BundleDetail>(\n `/api/integrations/${encodeURIComponent(bundleId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n\n const credCall = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(bundleId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (credCall.ok && credCall.result?.credentials) {\n setCredValues(credCall.result.credentials)\n }\n setIsLoading(false)\n }, [bundleId, t])\n\n React.useEffect(() => { void load() }, [load])\n\n const handleSaveCredentials = React.useCallback(async () => {\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(bundleId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [bundleId, credValues, t])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n bundleIntegrations: prev.bundleIntegrations.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const handleBulkToggle = React.useCallback(async (enabled: boolean) => {\n if (!detail) return\n const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled)\n await Promise.all(targets.map((item) => handleToggle(item.id, enabled)))\n }, [detail, handleToggle])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>\n if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold\">{detail.bundle.title}</h1>\n {detail.bundle.description && (\n <p className=\"text-muted-foreground mt-1\">{detail.bundle.description}</p>\n )}\n </div>\n\n {credFields.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {credFields.map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <select\n className=\"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n >\n <option value=\"\">\u2014</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.bundle.integrationToggles')}</CardTitle>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(true)}>\n {t('integrations.marketplace.enableAll')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(false)}>\n {t('integrations.marketplace.disableAll')}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {detail.bundleIntegrations.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant=\"secondary\" className=\"ml-2 text-xs\">{item.category}</Badge>\n )}\n {item.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{item.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.bundle.configureIntegration')}\n </Link>\n </Button>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,11 @@ import crypto from "node:crypto";
2
2
  import { decryptWithAesGcm, encryptWithAesGcm } from "@open-mercato/shared/lib/encryption/aes";
3
3
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
4
  import { createKmsService } from "@open-mercato/shared/lib/encryption/kms";
5
- import { getBundle, getIntegration, resolveIntegrationCredentialsSchema } from "@open-mercato/shared/modules/integrations/types";
5
+ import {
6
+ getBundle,
7
+ getIntegration,
8
+ resolveIntegrationCredentialsSchema
9
+ } from "@open-mercato/shared/modules/integrations/types";
6
10
  import { EncryptionMap } from "../../entities/data/entities.js";
7
11
  import { IntegrationCredentials } from "../data/entities.js";
8
12
  const ENCRYPTED_CREDENTIALS_BLOB_KEY = "__om_encrypted_credentials_blob_v1";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/lib/credentials-service.ts"],
4
- "sourcesContent": ["import crypto from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { getBundle, getIntegration, resolveIntegrationCredentialsSchema } from '@open-mercato/shared/modules/integrations/types'\nimport { EncryptionMap } from '../../entities/data/entities'\nimport { IntegrationCredentials } from '../data/entities'\nimport type { IntegrationScope } from './types'\n\nconst ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'\nconst DERIVED_KEY_CONTEXT = 'integrations.credentials'\n\nfunction resolveFallbackEncryptionSecret(): string {\n const candidates = [\n process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY,\n process.env.TENANT_DATA_ENCRYPTION_KEY,\n process.env.AUTH_SECRET,\n process.env.NEXTAUTH_SECRET,\n ]\n\n for (const value of candidates) {\n const normalized = value?.trim()\n if (normalized) return normalized\n }\n\n if (process.env.NODE_ENV !== 'production') return 'om-dev-tenant-encryption'\n\n console.warn(\n '[integrations.credentials] No encryption secret configured; using emergency fallback secret. Configure TENANT_DATA_ENCRYPTION_FALLBACK_KEY immediately.',\n )\n return 'om-emergency-fallback-rotate-me'\n}\n\nfunction deriveDekFromSecret(secret: string, tenantId: string): string {\n return crypto\n .createHash('sha256')\n .update(`${DERIVED_KEY_CONTEXT}:${tenantId}:${secret}`)\n .digest()\n .toString('base64')\n}\n\nexport function createCredentialsService(em: EntityManager) {\n const credentialsEncryptionSpec = [{ field: 'credentials' }]\n\n async function ensureCredentialsEncryptionMap(scope: IntegrationScope): Promise<void> {\n const existing = await findOneWithDecryption(\n em,\n EncryptionMap,\n {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (!existing) {\n const created = em.create(EncryptionMap, {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n fieldsJson: credentialsEncryptionSpec,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(created)\n return\n }\n\n existing.fieldsJson = credentialsEncryptionSpec\n existing.isActive = true\n }\n\n async function resolveCredentialsDek(scope: IntegrationScope): Promise<string> {\n const kms = createKmsService()\n const existing = await kms.getTenantDek(scope.tenantId)\n if (existing?.key) return existing.key\n\n const created = await kms.createTenantDek(scope.tenantId)\n if (created?.key) return created.key\n\n return deriveDekFromSecret(resolveFallbackEncryptionSecret(), scope.tenantId)\n }\n\n async function encryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const dek = await resolveCredentialsDek(scope)\n const payload = encryptWithAesGcm(JSON.stringify(credentials), dek)\n return { [ENCRYPTED_CREDENTIALS_BLOB_KEY]: payload.value }\n }\n\n async function decryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const encrypted = credentials[ENCRYPTED_CREDENTIALS_BLOB_KEY]\n if (typeof encrypted !== 'string' || !encrypted) return credentials\n\n const dek = await resolveCredentialsDek(scope)\n const decryptedRaw = decryptWithAesGcm(encrypted, dek)\n if (!decryptedRaw) return {}\n\n try {\n const parsed = JSON.parse(decryptedRaw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n\n return {\n async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!row) return null\n return decryptCredentialsBlob(row.credentials, scope)\n },\n\n async resolve(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const direct = await this.getRaw(integrationId, scope)\n if (direct) return direct\n\n const definition = getIntegration(integrationId)\n if (!definition?.bundleId) return null\n return this.getRaw(definition.bundleId, scope)\n },\n\n async save(integrationId: string, credentials: Record<string, unknown>, scope: IntegrationScope): Promise<void> {\n await ensureCredentialsEncryptionMap(scope)\n const encryptedCredentials = await encryptCredentialsBlob(credentials, scope)\n\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (row) {\n row.credentials = encryptedCredentials\n await em.flush()\n return\n }\n\n const created = em.create(IntegrationCredentials, {\n integrationId,\n credentials: encryptedCredentials,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persistAndFlush(created)\n },\n\n async saveField(\n integrationId: string,\n fieldKey: string,\n value: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const current = (await this.getRaw(integrationId, scope)) ?? {}\n const updated = { ...current, [fieldKey]: value }\n await this.save(integrationId, updated, scope)\n return updated\n },\n\n getSchema(integrationId: string) {\n const definition = getIntegration(integrationId)\n if (!definition) return undefined\n\n if (definition.bundleId) {\n const bundle = getBundle(definition.bundleId)\n return bundle?.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n }\n\n return definition.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n },\n }\n}\n\nexport type CredentialsService = ReturnType<typeof createCredentialsService>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import crypto from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport {\n getBundle,\n getIntegration,\n resolveIntegrationCredentialsSchema,\n type IntegrationScope,\n} from '@open-mercato/shared/modules/integrations/types'\nimport { EncryptionMap } from '../../entities/data/entities'\nimport { IntegrationCredentials } from '../data/entities'\n\nconst ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'\nconst DERIVED_KEY_CONTEXT = 'integrations.credentials'\n\nfunction resolveFallbackEncryptionSecret(): string {\n const candidates = [\n process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY,\n process.env.TENANT_DATA_ENCRYPTION_KEY,\n process.env.AUTH_SECRET,\n process.env.NEXTAUTH_SECRET,\n ]\n\n for (const value of candidates) {\n const normalized = value?.trim()\n if (normalized) return normalized\n }\n\n if (process.env.NODE_ENV !== 'production') return 'om-dev-tenant-encryption'\n\n console.warn(\n '[integrations.credentials] No encryption secret configured; using emergency fallback secret. Configure TENANT_DATA_ENCRYPTION_FALLBACK_KEY immediately.',\n )\n return 'om-emergency-fallback-rotate-me'\n}\n\nfunction deriveDekFromSecret(secret: string, tenantId: string): string {\n return crypto\n .createHash('sha256')\n .update(`${DERIVED_KEY_CONTEXT}:${tenantId}:${secret}`)\n .digest()\n .toString('base64')\n}\n\nexport function createCredentialsService(em: EntityManager) {\n const credentialsEncryptionSpec = [{ field: 'credentials' }]\n\n async function ensureCredentialsEncryptionMap(scope: IntegrationScope): Promise<void> {\n const existing = await findOneWithDecryption(\n em,\n EncryptionMap,\n {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (!existing) {\n const created = em.create(EncryptionMap, {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n fieldsJson: credentialsEncryptionSpec,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(created)\n return\n }\n\n existing.fieldsJson = credentialsEncryptionSpec\n existing.isActive = true\n }\n\n async function resolveCredentialsDek(scope: IntegrationScope): Promise<string> {\n const kms = createKmsService()\n const existing = await kms.getTenantDek(scope.tenantId)\n if (existing?.key) return existing.key\n\n const created = await kms.createTenantDek(scope.tenantId)\n if (created?.key) return created.key\n\n return deriveDekFromSecret(resolveFallbackEncryptionSecret(), scope.tenantId)\n }\n\n async function encryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const dek = await resolveCredentialsDek(scope)\n const payload = encryptWithAesGcm(JSON.stringify(credentials), dek)\n return { [ENCRYPTED_CREDENTIALS_BLOB_KEY]: payload.value }\n }\n\n async function decryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const encrypted = credentials[ENCRYPTED_CREDENTIALS_BLOB_KEY]\n if (typeof encrypted !== 'string' || !encrypted) return credentials\n\n const dek = await resolveCredentialsDek(scope)\n const decryptedRaw = decryptWithAesGcm(encrypted, dek)\n if (!decryptedRaw) return {}\n\n try {\n const parsed = JSON.parse(decryptedRaw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n\n return {\n async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!row) return null\n return decryptCredentialsBlob(row.credentials, scope)\n },\n\n async resolve(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const direct = await this.getRaw(integrationId, scope)\n if (direct) return direct\n\n const definition = getIntegration(integrationId)\n if (!definition?.bundleId) return null\n return this.getRaw(definition.bundleId, scope)\n },\n\n async save(integrationId: string, credentials: Record<string, unknown>, scope: IntegrationScope): Promise<void> {\n await ensureCredentialsEncryptionMap(scope)\n const encryptedCredentials = await encryptCredentialsBlob(credentials, scope)\n\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (row) {\n row.credentials = encryptedCredentials\n await em.flush()\n return\n }\n\n const created = em.create(IntegrationCredentials, {\n integrationId,\n credentials: encryptedCredentials,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persistAndFlush(created)\n },\n\n async saveField(\n integrationId: string,\n fieldKey: string,\n value: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const current = (await this.getRaw(integrationId, scope)) ?? {}\n const updated = { ...current, [fieldKey]: value }\n await this.save(integrationId, updated, scope)\n return updated\n },\n\n getSchema(integrationId: string) {\n const definition = getIntegration(integrationId)\n if (!definition) return undefined\n\n if (definition.bundleId) {\n const bundle = getBundle(definition.bundleId)\n return bundle?.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n }\n\n return definition.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n },\n }\n}\n\nexport type CredentialsService = ReturnType<typeof createCredentialsService>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/lib/health-service.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { IntegrationStateService } from './state-service'\nimport type { IntegrationLogService } from './log-service'\nimport { getIntegration, getBundle } from '@open-mercato/shared/modules/integrations/types'\nimport type { IntegrationScope } from './types'\n\ntype HealthCheckResult = {\n status: 'healthy' | 'degraded' | 'unhealthy'\n message?: string\n details?: Record<string, unknown>\n}\n\ntype HealthCheckService = {\n check: (credentials: Record<string, unknown> | null, scope: IntegrationScope) => Promise<HealthCheckResult>\n}\n\nexport function createHealthService(\n container: AwilixContainer,\n stateService: IntegrationStateService,\n logService: IntegrationLogService,\n) {\n return {\n async runHealthCheck(integrationId: string, scope: IntegrationScope): Promise<HealthCheckResult> {\n const definition = getIntegration(integrationId)\n const healthConfig = definition?.healthCheck ?? (definition?.bundleId ? getBundle(definition.bundleId)?.healthCheck : undefined)\n\n if (!healthConfig?.service) {\n return { status: 'unhealthy', message: 'No health check configured' }\n }\n\n let result: HealthCheckResult\n try {\n const checker = container.resolve<HealthCheckService>(healthConfig.service)\n const credentialsService = container.resolve<{ resolve: (id: string, scope: IntegrationScope) => Promise<Record<string, unknown> | null> }>('integrationCredentialsService')\n const credentials = await credentialsService.resolve(integrationId, scope)\n result = await checker.check(credentials, scope)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Health check failed'\n result = { status: 'unhealthy', message }\n }\n\n await stateService.upsert(integrationId, {\n lastHealthStatus: result.status,\n lastHealthCheckedAt: new Date(),\n }, scope)\n\n const logger = logService.scoped(integrationId, scope)\n if (result.status === 'healthy') {\n await logger.info(`Health check passed`, { status: result.status, ...result.details })\n } else {\n await logger.warn(`Health check: ${result.status}`, { status: result.status, message: result.message, ...result.details })\n }\n\n return result\n },\n }\n}\n\nexport type IntegrationHealthService = ReturnType<typeof createHealthService>\n"],
5
- "mappings": "AAGA,SAAS,gBAAgB,iBAAiB;AAanC,SAAS,oBACd,WACA,cACA,YACA;AACA,SAAO;AAAA,IACL,MAAM,eAAe,eAAuB,OAAqD;AAC/F,YAAM,aAAa,eAAe,aAAa;AAC/C,YAAM,eAAe,YAAY,gBAAgB,YAAY,WAAW,UAAU,WAAW,QAAQ,GAAG,cAAc;AAEtH,UAAI,CAAC,cAAc,SAAS;AAC1B,eAAO,EAAE,QAAQ,aAAa,SAAS,6BAA6B;AAAA,MACtE;AAEA,UAAI;AACJ,UAAI;AACF,cAAM,UAAU,UAAU,QAA4B,aAAa,OAAO;AAC1E,cAAM,qBAAqB,UAAU,QAAuG,+BAA+B;AAC3K,cAAM,cAAc,MAAM,mBAAmB,QAAQ,eAAe,KAAK;AACzE,iBAAS,MAAM,QAAQ,MAAM,aAAa,KAAK;AAAA,MACjD,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,iBAAS,EAAE,QAAQ,aAAa,QAAQ;AAAA,MAC1C;AAEA,YAAM,aAAa,OAAO,eAAe;AAAA,QACvC,kBAAkB,OAAO;AAAA,QACzB,qBAAqB,oBAAI,KAAK;AAAA,MAChC,GAAG,KAAK;AAER,YAAM,SAAS,WAAW,OAAO,eAAe,KAAK;AACrD,UAAI,OAAO,WAAW,WAAW;AAC/B,cAAM,OAAO,KAAK,uBAAuB,EAAE,QAAQ,OAAO,QAAQ,GAAG,OAAO,QAAQ,CAAC;AAAA,MACvF,OAAO;AACL,cAAM,OAAO,KAAK,iBAAiB,OAAO,MAAM,IAAI,EAAE,QAAQ,OAAO,QAAQ,SAAS,OAAO,SAAS,GAAG,OAAO,QAAQ,CAAC;AAAA,MAC3H;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { IntegrationStateService } from './state-service'\nimport type { IntegrationLogService } from './log-service'\nimport { getIntegration, getBundle, type IntegrationScope } from '@open-mercato/shared/modules/integrations/types'\n\ntype HealthCheckResult = {\n status: 'healthy' | 'degraded' | 'unhealthy'\n message?: string\n details?: Record<string, unknown>\n}\n\ntype HealthCheckService = {\n check: (credentials: Record<string, unknown> | null, scope: IntegrationScope) => Promise<HealthCheckResult>\n}\n\nexport function createHealthService(\n container: AwilixContainer,\n stateService: IntegrationStateService,\n logService: IntegrationLogService,\n) {\n return {\n async runHealthCheck(integrationId: string, scope: IntegrationScope): Promise<HealthCheckResult> {\n const definition = getIntegration(integrationId)\n const healthConfig = definition?.healthCheck ?? (definition?.bundleId ? getBundle(definition.bundleId)?.healthCheck : undefined)\n\n if (!healthConfig?.service) {\n return { status: 'unhealthy', message: 'No health check configured' }\n }\n\n let result: HealthCheckResult\n try {\n const checker = container.resolve<HealthCheckService>(healthConfig.service)\n const credentialsService = container.resolve<{ resolve: (id: string, scope: IntegrationScope) => Promise<Record<string, unknown> | null> }>('integrationCredentialsService')\n const credentials = await credentialsService.resolve(integrationId, scope)\n result = await checker.check(credentials, scope)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Health check failed'\n result = { status: 'unhealthy', message }\n }\n\n await stateService.upsert(integrationId, {\n lastHealthStatus: result.status,\n lastHealthCheckedAt: new Date(),\n }, scope)\n\n const logger = logService.scoped(integrationId, scope)\n if (result.status === 'healthy') {\n await logger.info(`Health check passed`, { status: result.status, ...result.details })\n } else {\n await logger.warn(`Health check: ${result.status}`, { status: result.status, message: result.message, ...result.details })\n }\n\n return result\n },\n }\n}\n\nexport type IntegrationHealthService = ReturnType<typeof createHealthService>\n"],
5
+ "mappings": "AAGA,SAAS,gBAAgB,iBAAwC;AAY1D,SAAS,oBACd,WACA,cACA,YACA;AACA,SAAO;AAAA,IACL,MAAM,eAAe,eAAuB,OAAqD;AAC/F,YAAM,aAAa,eAAe,aAAa;AAC/C,YAAM,eAAe,YAAY,gBAAgB,YAAY,WAAW,UAAU,WAAW,QAAQ,GAAG,cAAc;AAEtH,UAAI,CAAC,cAAc,SAAS;AAC1B,eAAO,EAAE,QAAQ,aAAa,SAAS,6BAA6B;AAAA,MACtE;AAEA,UAAI;AACJ,UAAI;AACF,cAAM,UAAU,UAAU,QAA4B,aAAa,OAAO;AAC1E,cAAM,qBAAqB,UAAU,QAAuG,+BAA+B;AAC3K,cAAM,cAAc,MAAM,mBAAmB,QAAQ,eAAe,KAAK;AACzE,iBAAS,MAAM,QAAQ,MAAM,aAAa,KAAK;AAAA,MACjD,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,iBAAS,EAAE,QAAQ,aAAa,QAAQ;AAAA,MAC1C;AAEA,YAAM,aAAa,OAAO,eAAe;AAAA,QACvC,kBAAkB,OAAO;AAAA,QACzB,qBAAqB,oBAAI,KAAK;AAAA,MAChC,GAAG,KAAK;AAER,YAAM,SAAS,WAAW,OAAO,eAAe,KAAK;AACrD,UAAI,OAAO,WAAW,WAAW;AAC/B,cAAM,OAAO,KAAK,uBAAuB,EAAE,QAAQ,OAAO,QAAQ,GAAG,OAAO,QAAQ,CAAC;AAAA,MACvF,OAAO;AACL,cAAM,OAAO,KAAK,iBAAiB,OAAO,MAAM,IAAI,EAAE,QAAQ,OAAO,QAAQ,SAAS,OAAO,SAAS,GAAG,OAAO,QAAQ,CAAC;AAAA,MAC3H;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/lib/log-service.ts"],
4
- "sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { ListIntegrationLogsQuery } from '../data/validators'\nimport { IntegrationLog } from '../data/entities'\nimport type { IntegrationScope } from './types'\n\ntype LogInput = {\n integrationId: string\n runId?: string | null\n scopeEntityType?: string | null\n scopeEntityId?: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n code?: string | null\n payload?: Record<string, unknown> | null\n}\n\nexport function createIntegrationLogService(em: EntityManager) {\n return {\n async write(input: LogInput, scope: IntegrationScope): Promise<IntegrationLog> {\n const row = em.create(IntegrationLog, {\n integrationId: input.integrationId,\n runId: input.runId,\n scopeEntityType: input.scopeEntityType,\n scopeEntityId: input.scopeEntityId,\n level: input.level,\n message: input.message,\n code: input.code,\n payload: input.payload,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persistAndFlush(row)\n return row\n },\n\n scoped(integrationId: string, scope: IntegrationScope) {\n return {\n info: (message: string, payload?: Record<string, unknown>) => this.write({ integrationId, level: 'info', message, payload }, scope),\n warn: (message: string, payload?: Record<string, unknown>) => this.write({ integrationId, level: 'warn', message, payload }, scope),\n error: (message: string, payload?: Record<string, unknown>) => this.write({ integrationId, level: 'error', message, payload }, scope),\n }\n },\n\n async query(query: ListIntegrationLogsQuery, scope: IntegrationScope): Promise<{ items: IntegrationLog[]; total: number }> {\n const where: FilterQuery<IntegrationLog> = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n\n if (query.integrationId) where.integrationId = query.integrationId\n if (query.level) where.level = query.level\n if (query.runId) where.runId = query.runId\n if (query.entityType) where.scopeEntityType = query.entityType\n if (query.entityId) where.scopeEntityId = query.entityId\n\n const items = await findWithDecryption(\n em,\n IntegrationLog,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n },\n scope,\n )\n const total = await em.count(IntegrationLog, where)\n return { items, total }\n },\n\n async pruneOlderThan(days: number, scope: IntegrationScope): Promise<number> {\n const threshold = new Date(Date.now() - days * 24 * 60 * 60 * 1000)\n const deletedCount = await em.nativeDelete(IntegrationLog, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n createdAt: { $lt: threshold },\n })\n return deletedCount\n },\n }\n}\n\nexport type IntegrationLogService = ReturnType<typeof createIntegrationLogService>\n"],
5
- "mappings": "AACA,SAAS,0BAA0B;AAEnC,SAAS,sBAAsB;AAcxB,SAAS,4BAA4B,IAAmB;AAC7D,SAAO;AAAA,IACL,MAAM,MAAM,OAAiB,OAAkD;AAC7E,YAAM,MAAM,GAAG,OAAO,gBAAgB;AAAA,QACpC,eAAe,MAAM;AAAA,QACrB,OAAO,MAAM;AAAA,QACb,iBAAiB,MAAM;AAAA,QACvB,eAAe,MAAM;AAAA,QACrB,OAAO,MAAM;AAAA,QACb,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB,CAAC;AACD,YAAM,GAAG,gBAAgB,GAAG;AAC5B,aAAO;AAAA,IACT;AAAA,IAEA,OAAO,eAAuB,OAAyB;AACrD,aAAO;AAAA,QACL,MAAM,CAAC,SAAiB,YAAsC,KAAK,MAAM,EAAE,eAAe,OAAO,QAAQ,SAAS,QAAQ,GAAG,KAAK;AAAA,QAClI,MAAM,CAAC,SAAiB,YAAsC,KAAK,MAAM,EAAE,eAAe,OAAO,QAAQ,SAAS,QAAQ,GAAG,KAAK;AAAA,QAClI,OAAO,CAAC,SAAiB,YAAsC,KAAK,MAAM,EAAE,eAAe,OAAO,SAAS,SAAS,QAAQ,GAAG,KAAK;AAAA,MACtI;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,OAAiC,OAA8E;AACzH,YAAM,QAAqC;AAAA,QACzC,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB;AAEA,UAAI,MAAM,cAAe,OAAM,gBAAgB,MAAM;AACrD,UAAI,MAAM,MAAO,OAAM,QAAQ,MAAM;AACrC,UAAI,MAAM,MAAO,OAAM,QAAQ,MAAM;AACrC,UAAI,MAAM,WAAY,OAAM,kBAAkB,MAAM;AACpD,UAAI,MAAM,SAAU,OAAM,gBAAgB,MAAM;AAEhD,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACE,SAAS,EAAE,WAAW,OAAO;AAAA,UAC7B,OAAO,MAAM;AAAA,UACb,SAAS,MAAM,OAAO,KAAK,MAAM;AAAA,QACnC;AAAA,QACA;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,GAAG,MAAM,gBAAgB,KAAK;AAClD,aAAO,EAAE,OAAO,MAAM;AAAA,IACxB;AAAA,IAEA,MAAM,eAAe,MAAc,OAA0C;AAC3E,YAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,KAAK,GAAI;AAClE,YAAM,eAAe,MAAM,GAAG,aAAa,gBAAgB;AAAA,QACzD,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW,EAAE,KAAK,UAAU;AAAA,MAC9B,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'\nimport type { ListIntegrationLogsQuery } from '../data/validators'\nimport { IntegrationLog } from '../data/entities'\n\ntype LogInput = {\n integrationId: string\n runId?: string | null\n scopeEntityType?: string | null\n scopeEntityId?: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n code?: string | null\n payload?: Record<string, unknown> | null\n}\n\nexport function createIntegrationLogService(em: EntityManager) {\n return {\n async write(input: LogInput, scope: IntegrationScope): Promise<IntegrationLog> {\n const row = em.create(IntegrationLog, {\n integrationId: input.integrationId,\n runId: input.runId,\n scopeEntityType: input.scopeEntityType,\n scopeEntityId: input.scopeEntityId,\n level: input.level,\n message: input.message,\n code: input.code,\n payload: input.payload,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persistAndFlush(row)\n return row\n },\n\n scoped(integrationId: string, scope: IntegrationScope) {\n return {\n info: (message: string, payload?: Record<string, unknown>) => this.write({ integrationId, level: 'info', message, payload }, scope),\n warn: (message: string, payload?: Record<string, unknown>) => this.write({ integrationId, level: 'warn', message, payload }, scope),\n error: (message: string, payload?: Record<string, unknown>) => this.write({ integrationId, level: 'error', message, payload }, scope),\n }\n },\n\n async query(query: ListIntegrationLogsQuery, scope: IntegrationScope): Promise<{ items: IntegrationLog[]; total: number }> {\n const where: FilterQuery<IntegrationLog> = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }\n\n if (query.integrationId) where.integrationId = query.integrationId\n if (query.level) where.level = query.level\n if (query.runId) where.runId = query.runId\n if (query.entityType) where.scopeEntityType = query.entityType\n if (query.entityId) where.scopeEntityId = query.entityId\n\n const items = await findWithDecryption(\n em,\n IntegrationLog,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n },\n scope,\n )\n const total = await em.count(IntegrationLog, where)\n return { items, total }\n },\n\n async pruneOlderThan(days: number, scope: IntegrationScope): Promise<number> {\n const threshold = new Date(Date.now() - days * 24 * 60 * 60 * 1000)\n const deletedCount = await em.nativeDelete(IntegrationLog, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n createdAt: { $lt: threshold },\n })\n return deletedCount\n },\n }\n}\n\nexport type IntegrationLogService = ReturnType<typeof createIntegrationLogService>\n"],
5
+ "mappings": "AACA,SAAS,0BAA0B;AAGnC,SAAS,sBAAsB;AAaxB,SAAS,4BAA4B,IAAmB;AAC7D,SAAO;AAAA,IACL,MAAM,MAAM,OAAiB,OAAkD;AAC7E,YAAM,MAAM,GAAG,OAAO,gBAAgB;AAAA,QACpC,eAAe,MAAM;AAAA,QACrB,OAAO,MAAM;AAAA,QACb,iBAAiB,MAAM;AAAA,QACvB,eAAe,MAAM;AAAA,QACrB,OAAO,MAAM;AAAA,QACb,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB,CAAC;AACD,YAAM,GAAG,gBAAgB,GAAG;AAC5B,aAAO;AAAA,IACT;AAAA,IAEA,OAAO,eAAuB,OAAyB;AACrD,aAAO;AAAA,QACL,MAAM,CAAC,SAAiB,YAAsC,KAAK,MAAM,EAAE,eAAe,OAAO,QAAQ,SAAS,QAAQ,GAAG,KAAK;AAAA,QAClI,MAAM,CAAC,SAAiB,YAAsC,KAAK,MAAM,EAAE,eAAe,OAAO,QAAQ,SAAS,QAAQ,GAAG,KAAK;AAAA,QAClI,OAAO,CAAC,SAAiB,YAAsC,KAAK,MAAM,EAAE,eAAe,OAAO,SAAS,SAAS,QAAQ,GAAG,KAAK;AAAA,MACtI;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,OAAiC,OAA8E;AACzH,YAAM,QAAqC;AAAA,QACzC,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB;AAEA,UAAI,MAAM,cAAe,OAAM,gBAAgB,MAAM;AACrD,UAAI,MAAM,MAAO,OAAM,QAAQ,MAAM;AACrC,UAAI,MAAM,MAAO,OAAM,QAAQ,MAAM;AACrC,UAAI,MAAM,WAAY,OAAM,kBAAkB,MAAM;AACpD,UAAI,MAAM,SAAU,OAAM,gBAAgB,MAAM;AAEhD,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACE,SAAS,EAAE,WAAW,OAAO;AAAA,UAC7B,OAAO,MAAM;AAAA,UACb,SAAS,MAAM,OAAO,KAAK,MAAM;AAAA,QACnC;AAAA,QACA;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,GAAG,MAAM,gBAAgB,KAAK;AAClD,aAAO,EAAE,OAAO,MAAM;AAAA,IACxB;AAAA,IAEA,MAAM,eAAe,MAAc,OAA0C;AAC3E,YAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,KAAK,GAAI;AAClE,YAAM,eAAe,MAAM,GAAG,aAAa,gBAAgB;AAAA,QACzD,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW,EAAE,KAAK,UAAU;AAAA,MAC9B,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/lib/state-service.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { IntegrationState } from '../data/entities'\nimport type { IntegrationScope } from './types'\n\nexport function createIntegrationStateService(em: EntityManager) {\n return {\n async get(integrationId: string, scope: IntegrationScope): Promise<IntegrationState | null> {\n return findOneWithDecryption(\n em,\n IntegrationState,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n },\n\n async upsert(\n integrationId: string,\n input: Partial<Pick<IntegrationState, 'isEnabled' | 'apiVersion' | 'reauthRequired' | 'lastHealthStatus' | 'lastHealthCheckedAt'>>,\n scope: IntegrationScope,\n ): Promise<IntegrationState> {\n const current = await this.get(integrationId, scope)\n if (current) {\n if (input.isEnabled !== undefined) current.isEnabled = input.isEnabled\n if (input.apiVersion !== undefined) current.apiVersion = input.apiVersion\n if (input.reauthRequired !== undefined) current.reauthRequired = input.reauthRequired\n if (input.lastHealthStatus !== undefined) current.lastHealthStatus = input.lastHealthStatus\n if (input.lastHealthCheckedAt !== undefined) current.lastHealthCheckedAt = input.lastHealthCheckedAt\n await em.flush()\n return current\n }\n\n const created = em.create(IntegrationState, {\n integrationId,\n isEnabled: input.isEnabled ?? true,\n apiVersion: input.apiVersion,\n reauthRequired: input.reauthRequired ?? false,\n lastHealthStatus: input.lastHealthStatus,\n lastHealthCheckedAt: input.lastHealthCheckedAt,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persistAndFlush(created)\n return created\n },\n\n async resolveApiVersion(integrationId: string, scope: IntegrationScope): Promise<string | undefined> {\n const state = await this.get(integrationId, scope)\n return state?.apiVersion ?? undefined\n },\n\n async setReauthRequired(integrationId: string, required: boolean, scope: IntegrationScope): Promise<IntegrationState> {\n return this.upsert(integrationId, { reauthRequired: required }, scope)\n },\n }\n}\n\nexport type IntegrationStateService = ReturnType<typeof createIntegrationStateService>\n"],
5
- "mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,wBAAwB;AAG1B,SAAS,8BAA8B,IAAmB;AAC/D,SAAO;AAAA,IACL,MAAM,IAAI,eAAuB,OAA2D;AAC1F,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,UACE;AAAA,UACA,gBAAgB,MAAM;AAAA,UACtB,UAAU,MAAM;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,OACJ,eACA,OACA,OAC2B;AAC3B,YAAM,UAAU,MAAM,KAAK,IAAI,eAAe,KAAK;AACnD,UAAI,SAAS;AACX,YAAI,MAAM,cAAc,OAAW,SAAQ,YAAY,MAAM;AAC7D,YAAI,MAAM,eAAe,OAAW,SAAQ,aAAa,MAAM;AAC/D,YAAI,MAAM,mBAAmB,OAAW,SAAQ,iBAAiB,MAAM;AACvE,YAAI,MAAM,qBAAqB,OAAW,SAAQ,mBAAmB,MAAM;AAC3E,YAAI,MAAM,wBAAwB,OAAW,SAAQ,sBAAsB,MAAM;AACjF,cAAM,GAAG,MAAM;AACf,eAAO;AAAA,MACT;AAEA,YAAM,UAAU,GAAG,OAAO,kBAAkB;AAAA,QAC1C;AAAA,QACA,WAAW,MAAM,aAAa;AAAA,QAC9B,YAAY,MAAM;AAAA,QAClB,gBAAgB,MAAM,kBAAkB;AAAA,QACxC,kBAAkB,MAAM;AAAA,QACxB,qBAAqB,MAAM;AAAA,QAC3B,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB,CAAC;AACD,YAAM,GAAG,gBAAgB,OAAO;AAChC,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,kBAAkB,eAAuB,OAAsD;AACnG,YAAM,QAAQ,MAAM,KAAK,IAAI,eAAe,KAAK;AACjD,aAAO,OAAO,cAAc;AAAA,IAC9B;AAAA,IAEA,MAAM,kBAAkB,eAAuB,UAAmB,OAAoD;AACpH,aAAO,KAAK,OAAO,eAAe,EAAE,gBAAgB,SAAS,GAAG,KAAK;AAAA,IACvE;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'\nimport { IntegrationState } from '../data/entities'\n\nexport function createIntegrationStateService(em: EntityManager) {\n return {\n async get(integrationId: string, scope: IntegrationScope): Promise<IntegrationState | null> {\n return findOneWithDecryption(\n em,\n IntegrationState,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n },\n\n async upsert(\n integrationId: string,\n input: Partial<Pick<IntegrationState, 'isEnabled' | 'apiVersion' | 'reauthRequired' | 'lastHealthStatus' | 'lastHealthCheckedAt'>>,\n scope: IntegrationScope,\n ): Promise<IntegrationState> {\n const current = await this.get(integrationId, scope)\n if (current) {\n if (input.isEnabled !== undefined) current.isEnabled = input.isEnabled\n if (input.apiVersion !== undefined) current.apiVersion = input.apiVersion\n if (input.reauthRequired !== undefined) current.reauthRequired = input.reauthRequired\n if (input.lastHealthStatus !== undefined) current.lastHealthStatus = input.lastHealthStatus\n if (input.lastHealthCheckedAt !== undefined) current.lastHealthCheckedAt = input.lastHealthCheckedAt\n await em.flush()\n return current\n }\n\n const created = em.create(IntegrationState, {\n integrationId,\n isEnabled: input.isEnabled ?? true,\n apiVersion: input.apiVersion,\n reauthRequired: input.reauthRequired ?? false,\n lastHealthStatus: input.lastHealthStatus,\n lastHealthCheckedAt: input.lastHealthCheckedAt,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persistAndFlush(created)\n return created\n },\n\n async resolveApiVersion(integrationId: string, scope: IntegrationScope): Promise<string | undefined> {\n const state = await this.get(integrationId, scope)\n return state?.apiVersion ?? undefined\n },\n\n async setReauthRequired(integrationId: string, required: boolean, scope: IntegrationScope): Promise<IntegrationState> {\n return this.upsert(integrationId, { reauthRequired: required }, scope)\n },\n }\n}\n\nexport type IntegrationStateService = ReturnType<typeof createIntegrationStateService>\n"],
5
+ "mappings": "AACA,SAAS,6BAA6B;AAEtC,SAAS,wBAAwB;AAE1B,SAAS,8BAA8B,IAAmB;AAC/D,SAAO;AAAA,IACL,MAAM,IAAI,eAAuB,OAA2D;AAC1F,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,UACE;AAAA,UACA,gBAAgB,MAAM;AAAA,UACtB,UAAU,MAAM;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,OACJ,eACA,OACA,OAC2B;AAC3B,YAAM,UAAU,MAAM,KAAK,IAAI,eAAe,KAAK;AACnD,UAAI,SAAS;AACX,YAAI,MAAM,cAAc,OAAW,SAAQ,YAAY,MAAM;AAC7D,YAAI,MAAM,eAAe,OAAW,SAAQ,aAAa,MAAM;AAC/D,YAAI,MAAM,mBAAmB,OAAW,SAAQ,iBAAiB,MAAM;AACvE,YAAI,MAAM,qBAAqB,OAAW,SAAQ,mBAAmB,MAAM;AAC3E,YAAI,MAAM,wBAAwB,OAAW,SAAQ,sBAAsB,MAAM;AACjF,cAAM,GAAG,MAAM;AACf,eAAO;AAAA,MACT;AAEA,YAAM,UAAU,GAAG,OAAO,kBAAkB;AAAA,QAC1C;AAAA,QACA,WAAW,MAAM,aAAa;AAAA,QAC9B,YAAY,MAAM;AAAA,QAClB,gBAAgB,MAAM,kBAAkB;AAAA,QACxC,kBAAkB,MAAM;AAAA,QACxB,qBAAqB,MAAM;AAAA,QAC3B,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB,CAAC;AACD,YAAM,GAAG,gBAAgB,OAAO;AAChC,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,kBAAkB,eAAuB,OAAsD;AACnG,YAAM,QAAQ,MAAM,KAAK,IAAI,eAAe,KAAK;AACjD,aAAO,OAAO,cAAc;AAAA,IAC9B;AAAA,IAEA,MAAM,kBAAkB,eAAuB,UAAmB,OAAoD;AACpH,aAAO,KAAK,OAAO,eAAe,EAAE,gBAAgB,SAAS,GAAG,KAAK;AAAA,IACvE;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -7,9 +7,9 @@ import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/op
7
7
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
8
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
9
9
  import {
10
- runCrudMutationGuardAfterSuccess,
11
- validateCrudMutationGuard
12
- } from "@open-mercato/shared/lib/crud/mutation-guard";
10
+ bridgeLegacyGuard,
11
+ runMutationGuards
12
+ } from "@open-mercato/shared/lib/crud/mutation-guard-registry";
13
13
  import { withScopedPayload } from "../../utils.js";
14
14
  const convertSchema = z.object({
15
15
  quoteId: z.string().uuid(),
@@ -19,9 +19,28 @@ const convertSchema = z.object({
19
19
  const metadata = {
20
20
  POST: { requireAuth: true, requireFeatures: ["sales.quotes.manage", "sales.orders.manage"] }
21
21
  };
22
- function buildMutationGuardErrorResponse(validation) {
23
- if (validation.ok) return null;
24
- return NextResponse.json(validation.body, { status: validation.status });
22
+ function resolveUserFeatures(auth) {
23
+ const features = auth?.features;
24
+ if (!Array.isArray(features)) return [];
25
+ return features.filter((value) => typeof value === "string");
26
+ }
27
+ async function runGuards(ctx, input) {
28
+ const legacyGuard = bridgeLegacyGuard(ctx.container);
29
+ if (!legacyGuard) {
30
+ return { ok: true, afterSuccessCallbacks: [] };
31
+ }
32
+ return runMutationGuards([legacyGuard], input, {
33
+ userFeatures: resolveUserFeatures(ctx.auth)
34
+ });
35
+ }
36
+ async function runGuardAfterSuccessCallbacks(callbacks, input) {
37
+ for (const callback of callbacks) {
38
+ if (!callback.guard.afterSuccess) continue;
39
+ await callback.guard.afterSuccess({
40
+ ...input,
41
+ metadata: callback.metadata ?? null
42
+ });
43
+ }
25
44
  }
26
45
  async function resolveRequestContext(req) {
27
46
  const container = await createRequestContainer();
@@ -54,7 +73,7 @@ async function POST(req) {
54
73
  const payload = await req.json().catch(() => ({}));
55
74
  const scoped = withScopedPayload(payload ?? {}, ctx, translate);
56
75
  const input = convertSchema.parse(scoped);
57
- const mutationGuardValidation = await validateCrudMutationGuard(ctx.container, {
76
+ const guardResult = await runGuards(ctx, {
58
77
  tenantId: ctx.auth?.tenantId ?? "",
59
78
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
60
79
  userId: ctx.auth?.sub ?? "",
@@ -64,9 +83,8 @@ async function POST(req) {
64
83
  requestMethod: req.method,
65
84
  requestHeaders: req.headers
66
85
  });
67
- if (mutationGuardValidation) {
68
- const lockErrorResponse = buildMutationGuardErrorResponse(mutationGuardValidation);
69
- if (lockErrorResponse) return lockErrorResponse;
86
+ if (!guardResult.ok) {
87
+ return NextResponse.json(guardResult.errorBody ?? { error: "Operation blocked by guard" }, { status: guardResult.errorStatus ?? 422 });
70
88
  }
71
89
  const commandBus = ctx.container.resolve("commandBus");
72
90
  const { result, logEntry } = await commandBus.execute("sales.quotes.convert_to_order", { input, ctx });
@@ -86,8 +104,8 @@ async function POST(req) {
86
104
  })
87
105
  );
88
106
  }
89
- if (mutationGuardValidation?.ok && mutationGuardValidation.shouldRunAfterSuccess) {
90
- await runCrudMutationGuardAfterSuccess(ctx.container, {
107
+ if (guardResult.afterSuccessCallbacks.length) {
108
+ await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
91
109
  tenantId: ctx.auth?.tenantId ?? "",
92
110
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
93
111
  userId: ctx.auth?.sub ?? "",
@@ -95,8 +113,7 @@ async function POST(req) {
95
113
  resourceId: input.quoteId,
96
114
  operation: "update",
97
115
  requestMethod: req.method,
98
- requestHeaders: req.headers,
99
- metadata: mutationGuardValidation.metadata ?? null
116
+ requestHeaders: req.headers
100
117
  });
101
118
  }
102
119
  return jsonResponse;