@tokenoftrust/storefront-runner 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/storefront/.astro/content.d.ts +159 -0
- package/apps/storefront/.astro/integrations/_astrojs_cloudflare/cloudflare.d.ts +1 -0
- package/apps/storefront/.astro/types.d.ts +3 -0
- package/apps/storefront/node_modules/.bin/acorn +43 -0
- package/apps/storefront/node_modules/.bin/astro +43 -0
- package/apps/storefront/node_modules/.bin/astro-check +43 -0
- package/apps/storefront/node_modules/.bin/browserslist +43 -0
- package/apps/storefront/node_modules/.bin/cf-wrangler +43 -0
- package/apps/storefront/node_modules/.bin/depcruise +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-baseline +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-fmt +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-wrap-stream-in-html +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruise +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruiser +43 -0
- package/apps/storefront/node_modules/.bin/drizzle-kit +43 -0
- package/apps/storefront/node_modules/.bin/esbuild +43 -0
- package/apps/storefront/node_modules/.bin/jiti +43 -0
- package/apps/storefront/node_modules/.bin/playwright +43 -0
- package/apps/storefront/node_modules/.bin/playwright-core +43 -0
- package/apps/storefront/node_modules/.bin/prettier +43 -0
- package/apps/storefront/node_modules/.bin/rollup +43 -0
- package/apps/storefront/node_modules/.bin/tsc +43 -0
- package/apps/storefront/node_modules/.bin/tsserver +43 -0
- package/apps/storefront/node_modules/.bin/tsx +43 -0
- package/apps/storefront/node_modules/.bin/vite +43 -0
- package/apps/storefront/node_modules/.bin/vitest +43 -0
- package/apps/storefront/node_modules/.bin/workerd +43 -0
- package/apps/storefront/node_modules/.bin/wrangler +43 -0
- package/apps/storefront/node_modules/.bin/wrangler2 +43 -0
- package/apps/storefront/node_modules/.bin/yaml +43 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js +58 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js +741 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js +2549 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/_metadata.json +150 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js +434 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js +1535 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js +48 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js +42 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/package.json +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js +310 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js +420 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat_jsx-runtime.js +4 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js +236 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js +12 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js +192 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-dev-runtime.js +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js +92 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js +59 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/rolldown-runtime-BPOCksWG.js +24 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js +189 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js +13 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js +458 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js +1016 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js +32 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js +113 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/_metadata.json +8 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/package.json +3 -0
- package/apps/storefront/src/layouts/Layout.astro +1 -1
- package/apps/storefront/src/lib/apps/registryAccess.ts +45 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +4 -4
- package/apps/storefront/src/lib/shared/capability.ts +68 -0
- package/apps/storefront/src/lib/shared/developerInvites.ts +1250 -0
- package/apps/storefront/src/lib/shared/invitePreflight.ts +123 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +38 -4
- package/apps/storefront/src/lib/webhooks/emit.ts +1 -1
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +1 -1
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +1 -1
- package/apps/storefront/src/pages/index.astro +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/node_modules/.bin/tsc +43 -0
- package/packages/public-runtime/node_modules/.bin/tsserver +43 -0
- package/packages/public-runtime/node_modules/.bin/vitest +43 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/lib/tenant-dirs.mjs +80 -0
- package/scripts/tenant/lint-embeds.mjs +180 -0
- package/scripts/tenant/materialize-tenant-content.mjs +533 -0
- package/scripts/tenant/validate.mjs +837 -0
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +0 -68
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +0 -111
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +0 -2
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +0 -247
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +0 -73
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +0 -79
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +0 -10982
- package/apps/storefront/scripts/import-v84-through-ws2.ts +0 -38
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +0 -2
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +0 -4
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +0 -185
- package/apps/storefront/scripts/prove-clone-isolation.ts +0 -506
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +0 -148
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +0 -35
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +0 -214
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +0 -18
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +0 -18
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +0 -18
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +0 -17
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +0 -15
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +0 -21
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +0 -42
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +0 -145
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +0 -18
- package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +0 -531
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +0 -878
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +0 -1435
- package/apps/storefront/src/components/admin/AdminCheckoutSettingsPanel.astro +0 -226
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +0 -593
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +0 -465
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +0 -105
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +0 -901
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +0 -2983
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +0 -435
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +0 -782
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +0 -611
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +0 -1223
- package/apps/storefront/src/lib/apps/adminService.ts +0 -116
- package/apps/storefront/src/lib/apps/adminSession.ts +0 -214
- package/apps/storefront/src/lib/apps/apiAuth.ts +0 -91
- package/apps/storefront/src/lib/apps/apiRoute.ts +0 -35
- package/apps/storefront/src/lib/apps/catalogMapper.ts +0 -39
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +0 -228
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +0 -447
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +0 -367
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +0 -315
- package/apps/storefront/src/lib/apps/healthAggregate.ts +0 -66
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +0 -614
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +0 -125
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +0 -563
- package/apps/storefront/src/lib/apps/integrationsTab.ts +0 -547
- package/apps/storefront/src/lib/apps/integrationsView.ts +0 -106
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +0 -599
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +0 -170
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +0 -121
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +0 -148
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +0 -16
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +0 -105
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +0 -177
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +0 -251
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +0 -241
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +0 -211
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +0 -409
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +0 -368
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +0 -188
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +0 -291
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +0 -95
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +0 -91
- package/apps/storefront/src/lib/content-edit/client.ts +0 -286
- package/apps/storefront/src/lib/content-edit/index.ts +0 -14
- package/apps/storefront/src/lib/content-edit/route.ts +0 -25
- package/apps/storefront/src/lib/content-edit/types.ts +0 -19
- package/apps/storefront/src/lib/dev/cockpitStore.ts +0 -57
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +0 -4625
- package/apps/storefront/src/lib/publish/apex-readiness.ts +0 -360
- package/apps/storefront/src/lib/publish/buildStream.ts +0 -269
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +0 -72
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +0 -267
- package/apps/storefront/src/lib/publish/domainState.ts +0 -360
- package/apps/storefront/src/lib/publish/lifecycleState.ts +0 -369
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +0 -29
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +0 -78
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +0 -62
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +0 -124
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +0 -75
- package/apps/storefront/src/lib/publish/panelPolling.ts +0 -78
- package/apps/storefront/src/lib/publish/publish-guard.ts +0 -88
- package/apps/storefront/src/lib/publish/publishFormat.ts +0 -165
- package/apps/storefront/src/lib/publish/publishHero.ts +0 -177
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +0 -66
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +0 -141
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +0 -41
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +0 -47
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +0 -42
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +0 -59
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +0 -440
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +0 -161
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +0 -325
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +0 -241
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +0 -439
- package/apps/storefront/src/lib/publish/sseRoute.ts +0 -119
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +0 -215
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +0 -142
- package/apps/storefront/src/lib/publish/versionNumber.ts +0 -52
- package/apps/storefront/src/pages/admin/ops-timeline.astro +0 -420
- package/apps/storefront/src/pages/admin/review-release.astro +0 -265
- package/apps/storefront/src/pages/admin/select.astro +0 -96
- package/apps/storefront/src/pages/admin.astro +0 -570
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +0 -86
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +0 -174
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +0 -96
- package/apps/storefront/src/pages/api/apps/admin/health.ts +0 -44
- package/apps/storefront/src/pages/api/apps/admin/install.ts +0 -125
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +0 -118
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +0 -89
- package/apps/storefront/src/pages/api/apps/admin/list.ts +0 -26
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +0 -85
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +0 -79
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +0 -80
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +0 -98
- package/apps/storefront/src/pages/api/apps/admin/update.ts +0 -126
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +0 -70
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +0 -51
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +0 -255
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +0 -87
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +0 -162
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +0 -39
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +0 -49
- package/apps/storefront/src/pages/api/apps/v1/health.ts +0 -32
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +0 -59
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +0 -48
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +0 -73
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +0 -21
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +0 -74
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +0 -47
- package/apps/storefront/src/pages/api/cache-purge.ts +0 -55
- package/apps/storefront/src/pages/api/compliance/tamper.ts +0 -88
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +0 -164
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +0 -154
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +0 -120
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +0 -108
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +0 -70
- package/apps/storefront/src/pages/api/test/dev-session.ts +0 -133
- package/apps/storefront/src/pages/cockpit.astro +0 -1548
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +0 -119
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +0 -105
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +0 -177
- package/apps/storefront/src/pages/dashboard/index.astro +0 -160
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /api/apps/v1/health` — the D4 install delivery/telemetry health read
|
|
3
|
-
* (PrivateApps epic; `docs/private-apps/contract/openapi.yaml`'s
|
|
4
|
-
* `getInstallHealth`). Requires no scope beyond a valid token — only ever the
|
|
5
|
-
* caller's OWN install health (the `sub` claim `authorizeAppRequest` already
|
|
6
|
-
* resolved), never another install's.
|
|
7
|
-
*
|
|
8
|
-
* `WebhookDeliveryStore.getInstallHealth`'s `InstallHealth` return already IS
|
|
9
|
-
* the contract's `InstallHealth` shape 1:1, incl. `p95DeliveryMs` (a
|
|
10
|
-
* never-attempted install reads as healthy/zeroed) — no mapper needed, unlike
|
|
11
|
-
* `Delivery` below.
|
|
12
|
-
*/
|
|
13
|
-
import type { APIContext } from "astro";
|
|
14
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
15
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
16
|
-
import { WebhookDeliveryStore } from "@/lib/webhooks/deliveryStore.js";
|
|
17
|
-
import { fromD1 } from "@/lib/d1/catalog.js";
|
|
18
|
-
import { readD1 } from "@/lib/env";
|
|
19
|
-
|
|
20
|
-
export const prerender = false;
|
|
21
|
-
|
|
22
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
23
|
-
const auth = await authorizeAppRequest(context, null);
|
|
24
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
25
|
-
|
|
26
|
-
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
27
|
-
if (!d1) return withRequestId(json({ error: "webhook store not configured", code: "unconfigured" }, 503), context);
|
|
28
|
-
const store = new WebhookDeliveryStore(fromD1(d1));
|
|
29
|
-
|
|
30
|
-
const health = await store.getInstallHealth(auth.install.installId);
|
|
31
|
-
return withRequestId(json(health), context);
|
|
32
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /api/apps/v1/inventory` — the D3 inventory-availability read
|
|
3
|
-
* (PrivateApps epic; `docs/private-apps/contract/openapi.yaml`'s
|
|
4
|
-
* `listInventory`). Requires scope `inventory:read`.
|
|
5
|
-
*
|
|
6
|
-
* No new table: this projects `{sku, available, quantity?, policy?}` off the
|
|
7
|
-
* variants embedded in the existing catalog read model
|
|
8
|
-
* (`../../../lib/d1/catalog.js`'s `listAllActiveProducts`) — active products
|
|
9
|
-
* only. A variant with no `sku` is skipped; the contract's `InventoryList`
|
|
10
|
-
* item requires one, and there's nothing to key it by otherwise.
|
|
11
|
-
*/
|
|
12
|
-
import type { APIContext } from "astro";
|
|
13
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
14
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
15
|
-
import { listAllActiveProducts, fromD1 } from "@/lib/d1/catalog.js";
|
|
16
|
-
import { readD1 } from "@/lib/env";
|
|
17
|
-
|
|
18
|
-
export const prerender = false;
|
|
19
|
-
|
|
20
|
-
interface InventoryItem {
|
|
21
|
-
sku: string;
|
|
22
|
-
available: boolean;
|
|
23
|
-
quantity?: number;
|
|
24
|
-
policy?: "deny" | "continue";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function toInventoryItem(policy: string | undefined, quantity: number | undefined, sku: string, available: boolean): InventoryItem {
|
|
28
|
-
return {
|
|
29
|
-
sku,
|
|
30
|
-
available,
|
|
31
|
-
quantity,
|
|
32
|
-
policy: policy === "deny" || policy === "continue" ? policy : undefined,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
37
|
-
const auth = await authorizeAppRequest(context, "inventory:read");
|
|
38
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
39
|
-
|
|
40
|
-
const d1 = await readD1("CATALOG_DB");
|
|
41
|
-
if (!d1) return withRequestId(json({ error: "catalog not configured", code: "unconfigured" }, 503), context);
|
|
42
|
-
const db = fromD1(d1);
|
|
43
|
-
|
|
44
|
-
const skuFilter = new URL(context.request.url).searchParams.get("sku");
|
|
45
|
-
const products = await listAllActiveProducts(db);
|
|
46
|
-
|
|
47
|
-
const items: InventoryItem[] = [];
|
|
48
|
-
for (const product of products) {
|
|
49
|
-
for (const variant of product.variants) {
|
|
50
|
-
if (!variant.sku) continue;
|
|
51
|
-
if (skuFilter && variant.sku !== skuFilter) continue;
|
|
52
|
-
items.push(
|
|
53
|
-
toInventoryItem(variant.inventory.policy, variant.inventory.quantity, variant.sku, variant.inventory.available),
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return withRequestId(json({ items }), context);
|
|
59
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /api/apps/v1/orders/{id}` — the D6 minimal single-order read
|
|
3
|
-
* (PrivateApps epic; `docs/private-apps/contract/openapi.yaml`'s `getOrder`).
|
|
4
|
-
* Requires scope `orders:read:minimal`.
|
|
5
|
-
*
|
|
6
|
-
* `{id}` is tot-foxycart's `externalId` — the same id `listOrders` echoes
|
|
7
|
-
* back as `Order.id`. There's no cross-tenant oracle here: the Foxy partner
|
|
8
|
-
* API key is itself tenant-scoped, so a call can only ever resolve THIS
|
|
9
|
-
* tenant's own orders — an unknown id and a different tenant's id both come
|
|
10
|
-
* back `null` from `FoxyOrderClient.getOrder` and are reported identically
|
|
11
|
-
* as 404.
|
|
12
|
-
*/
|
|
13
|
-
import type { APIContext } from "astro";
|
|
14
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
15
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
16
|
-
import { toContractOrder } from "@/lib/apps/orders/orderMapper.js";
|
|
17
|
-
import { getFoxyOrderClient } from "@/lib/apps/orders/foxyOrderClient.js";
|
|
18
|
-
|
|
19
|
-
export const prerender = false;
|
|
20
|
-
|
|
21
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
22
|
-
const auth = await authorizeAppRequest(context, "orders:read:minimal");
|
|
23
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
24
|
-
|
|
25
|
-
const id = context.params.id;
|
|
26
|
-
if (!id) return withRequestId(json({ error: "missing order id", code: "bad_request" }, 400), context);
|
|
27
|
-
|
|
28
|
-
let client;
|
|
29
|
-
try {
|
|
30
|
-
client = await getFoxyOrderClient();
|
|
31
|
-
} catch {
|
|
32
|
-
return withRequestId(json({ error: "orders not configured", code: "unconfigured" }, 503), context);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let foxyOrder;
|
|
36
|
-
try {
|
|
37
|
-
foxyOrder = await client.getOrder(auth.install.tenantId, id);
|
|
38
|
-
} catch {
|
|
39
|
-
return withRequestId(json({ error: "failed to read order", code: "upstream_error" }, 502), context);
|
|
40
|
-
}
|
|
41
|
-
if (!foxyOrder) return withRequestId(json({ error: "order not found", code: "not_found" }, 404), context);
|
|
42
|
-
|
|
43
|
-
const order = await toContractOrder(foxyOrder, {
|
|
44
|
-
tenantId: auth.install.tenantId,
|
|
45
|
-
installId: auth.install.installId,
|
|
46
|
-
});
|
|
47
|
-
return withRequestId(json(order), context);
|
|
48
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /api/apps/v1/orders` — the D6 minimal order list (PrivateApps epic;
|
|
3
|
-
* `docs/private-apps/contract/openapi.yaml`'s `listOrders`). Requires scope
|
|
4
|
-
* `orders:read:minimal`.
|
|
5
|
-
*
|
|
6
|
-
* Reads live from tot-foxycart via `FoxyOrderClient` (never a raw fetch) and
|
|
7
|
-
* maps every row through `orderMapper.ts`'s `toContractOrder` — nothing
|
|
8
|
-
* beyond the contract's minimized `Order` shape (customer as opaque hash, no
|
|
9
|
-
* checkout/tax/signed-cart internals) ever leaves this route. Fail-closed:
|
|
10
|
-
* an unconfigured Foxy client is a 503, any upstream read failure is a clean
|
|
11
|
-
* 502 — neither leaks Foxy's own error detail.
|
|
12
|
-
*/
|
|
13
|
-
import type { APIContext } from "astro";
|
|
14
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
15
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
16
|
-
import { toContractOrder, type ContractOrderStatus } from "@/lib/apps/orders/orderMapper.js";
|
|
17
|
-
import { getFoxyOrderClient } from "@/lib/apps/orders/foxyOrderClient.js";
|
|
18
|
-
|
|
19
|
-
export const prerender = false;
|
|
20
|
-
|
|
21
|
-
const DEFAULT_PAGE_SIZE = 24;
|
|
22
|
-
const MAX_PAGE_SIZE = 100;
|
|
23
|
-
const ORDER_STATUSES: readonly ContractOrderStatus[] = ["created", "fulfilled", "cancelled"];
|
|
24
|
-
|
|
25
|
-
function parsePage(raw: string | null, fallback: number): number {
|
|
26
|
-
const n = raw != null ? Number(raw) : NaN;
|
|
27
|
-
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function parseStatus(raw: string | null): ContractOrderStatus | undefined {
|
|
31
|
-
return raw != null && (ORDER_STATUSES as readonly string[]).includes(raw) ? (raw as ContractOrderStatus) : undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
35
|
-
const auth = await authorizeAppRequest(context, "orders:read:minimal");
|
|
36
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
37
|
-
|
|
38
|
-
const url = new URL(context.request.url);
|
|
39
|
-
const page = parsePage(url.searchParams.get("page"), 1);
|
|
40
|
-
const pageSize = Math.min(
|
|
41
|
-
MAX_PAGE_SIZE,
|
|
42
|
-
Math.max(1, parsePage(url.searchParams.get("pageSize"), DEFAULT_PAGE_SIZE)),
|
|
43
|
-
);
|
|
44
|
-
const statusFilter = parseStatus(url.searchParams.get("status"));
|
|
45
|
-
|
|
46
|
-
let client;
|
|
47
|
-
try {
|
|
48
|
-
client = await getFoxyOrderClient();
|
|
49
|
-
} catch {
|
|
50
|
-
return withRequestId(json({ error: "orders not configured", code: "unconfigured" }, 503), context);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let foxyPage;
|
|
54
|
-
try {
|
|
55
|
-
foxyPage = await client.listOrders(auth.install.tenantId, {
|
|
56
|
-
limit: pageSize,
|
|
57
|
-
offset: (page - 1) * pageSize,
|
|
58
|
-
});
|
|
59
|
-
} catch {
|
|
60
|
-
return withRequestId(json({ error: "failed to read orders", code: "upstream_error" }, 502), context);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const mapCtx = { tenantId: auth.install.tenantId, installId: auth.install.installId };
|
|
64
|
-
let orders = await Promise.all(foxyPage.orders.map((order) => toContractOrder(order, mapCtx)));
|
|
65
|
-
// tot-foxycart's own `status` query param speaks Foxy's status vocabulary, not
|
|
66
|
-
// the contract's created|fulfilled|cancelled enum — there's no confirmed
|
|
67
|
-
// mapping to push this filter upstream, so it's applied post-map here. This
|
|
68
|
-
// can leave a page short of `pageSize`, and `total` still reflects Foxy's
|
|
69
|
-
// UNFILTERED count — a known gap, flagged rather than built around.
|
|
70
|
-
if (statusFilter) orders = orders.filter((order) => order.status === statusFilter);
|
|
71
|
-
|
|
72
|
-
return withRequestId(json({ orders, page, pageSize, total: foxyPage.total }), context);
|
|
73
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /api/apps/v1/reports` — the D3 report-snapshot list (PrivateApps
|
|
3
|
-
* epic; `docs/private-apps/contract/openapi.yaml`'s `listReports`). Requires
|
|
4
|
-
* scope `reports:read`.
|
|
5
|
-
*
|
|
6
|
-
* Real auth + scope enforcement, empty data: this milestone ships the
|
|
7
|
-
* endpoint's contract shape (a valid `ReportList`) with no report snapshots
|
|
8
|
-
* behind it yet — there is no report-generation pipeline to read from.
|
|
9
|
-
*/
|
|
10
|
-
import type { APIContext } from "astro";
|
|
11
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
12
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
13
|
-
|
|
14
|
-
export const prerender = false;
|
|
15
|
-
|
|
16
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
17
|
-
const auth = await authorizeAppRequest(context, "reports:read");
|
|
18
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
19
|
-
|
|
20
|
-
return withRequestId(json({ reports: [] }), context);
|
|
21
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `POST /api/apps/v1/webhooks/deliveries/{deliveryId}/replay` — the D4
|
|
3
|
-
* install-owned replay (PrivateApps epic; `docs/private-apps/contract/
|
|
4
|
-
* openapi.yaml`'s `replayWebhookDelivery`). Authorized by install ownership,
|
|
5
|
-
* not a scope (`x-requiredScope: null`, `x-authorization: install-owns-
|
|
6
|
-
* delivery`): an app may replay only a delivery it owns.
|
|
7
|
-
*
|
|
8
|
-
* Preserves the SAME `deliveryId` (the CloudEvents wire id) throughout — a
|
|
9
|
-
* receiver that already acked a `delivered` delivery still dedupes a
|
|
10
|
-
* replayed resend of it — via `deliveryStore.ts`'s `requeueForReplay`, which
|
|
11
|
-
* resets the row (incl. out of the `delivered`/`dead-lettered` terminal
|
|
12
|
-
* states) to `pending` with a fresh attempt budget so the dispatcher's
|
|
13
|
-
* `attemptDelivery` makes a real HTTP attempt instead of hitting its
|
|
14
|
-
* terminal-state no-op branch. See `requeueForReplay`'s own doc comment for
|
|
15
|
-
* the full rationale.
|
|
16
|
-
*
|
|
17
|
-
* No `Idempotency-Key` ledger exists yet (no table backs it), so this only
|
|
18
|
-
* validates the header's presence, per the contract's `required: true` — it
|
|
19
|
-
* does not yet detect/409 a reused key with a mismatched body (there is no
|
|
20
|
-
* request body on this operation to hash in the first place).
|
|
21
|
-
*/
|
|
22
|
-
import type { APIContext } from "astro";
|
|
23
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
24
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
25
|
-
import { WebhookDeliveryStore } from "@/lib/webhooks/deliveryStore.js";
|
|
26
|
-
import { toContractDelivery } from "@/lib/webhooks/deliveryMapper.js";
|
|
27
|
-
import { getDispatcher } from "@/lib/webhooks/getDispatcher.js";
|
|
28
|
-
import { fromD1 } from "@/lib/d1/catalog.js";
|
|
29
|
-
import { readD1 } from "@/lib/env";
|
|
30
|
-
import { recordActivity } from "@/lib/activity/recordActivity";
|
|
31
|
-
|
|
32
|
-
export const prerender = false;
|
|
33
|
-
|
|
34
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
35
|
-
const auth = await authorizeAppRequest(context, null);
|
|
36
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
37
|
-
|
|
38
|
-
if (!context.request.headers.get("idempotency-key")) {
|
|
39
|
-
return withRequestId(json({ error: "Idempotency-Key header is required" }, 400), context);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const deliveryId = context.params.deliveryId;
|
|
43
|
-
if (!deliveryId) {
|
|
44
|
-
return withRequestId(json({ error: "deliveryId is required", code: "not_found" }, 404), context);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
48
|
-
if (!d1) return withRequestId(json({ error: "webhook store not configured", code: "unconfigured" }, 503), context);
|
|
49
|
-
const store = new WebhookDeliveryStore(fromD1(d1));
|
|
50
|
-
|
|
51
|
-
const delivery = await store.getDelivery(deliveryId);
|
|
52
|
-
if (!delivery || delivery.installId !== auth.install.installId) {
|
|
53
|
-
return withRequestId(json({ error: "Delivery not found.", code: "not_found" }, 404), context);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const dispatcher = await getDispatcher();
|
|
57
|
-
if (dispatcher instanceof Response) return withRequestId(dispatcher, context);
|
|
58
|
-
|
|
59
|
-
const requeued = await store.requeueForReplay(deliveryId);
|
|
60
|
-
await dispatcher.enqueue({ deliveryId });
|
|
61
|
-
|
|
62
|
-
// D11: the app-facing (install-owned, app-JWT authed) replay — the `agent` cohort. Same
|
|
63
|
-
// catalog key as the admin console call site; `surface: "v1"` disambiguates. installId
|
|
64
|
-
// is an opaque per-install handle; deliveryId is the opaque CloudEvents wire id.
|
|
65
|
-
recordActivity({
|
|
66
|
-
action: "webhook.delivery.replayed",
|
|
67
|
-
actor: { kind: "agent", id: auth.install.installId },
|
|
68
|
-
source: "server",
|
|
69
|
-
outcome: { status: "succeeded" },
|
|
70
|
-
scope: { tenantId: auth.install.tenantId },
|
|
71
|
-
payload: { args: { deliveryId, surface: "v1" } },
|
|
72
|
-
});
|
|
73
|
-
return withRequestId(json(toContractDelivery(requeued), 202), context);
|
|
74
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `GET /api/apps/v1/webhooks/deliveries` — the D4 install-owned delivery
|
|
3
|
-
* list (PrivateApps epic; `docs/private-apps/contract/openapi.yaml`'s
|
|
4
|
-
* `listWebhookDeliveries`). Authorized by install ownership, not a scope
|
|
5
|
-
* (`x-requiredScope: null`, `x-authorization: install-owns-delivery`): any
|
|
6
|
-
* installed app may list ITS OWN delivery records, never another install's —
|
|
7
|
-
* `listDeliveriesByInstall` is scoped to `auth.install.installId` from the
|
|
8
|
-
* JWT, never a caller-supplied id.
|
|
9
|
-
*
|
|
10
|
-
* Delivery rows never carry a payload body (see `deliveryStore.ts`'s header),
|
|
11
|
-
* so there is nothing sensitive to redact in `toContractDelivery`'s output.
|
|
12
|
-
*/
|
|
13
|
-
import type { APIContext } from "astro";
|
|
14
|
-
import { authorizeAppRequest } from "@/lib/apps/apiAuth.js";
|
|
15
|
-
import { json, withRequestId } from "@/lib/apps/apiRoute.js";
|
|
16
|
-
import { WebhookDeliveryStore, type DeliveryState } from "@/lib/webhooks/deliveryStore.js";
|
|
17
|
-
import { toContractDelivery } from "@/lib/webhooks/deliveryMapper.js";
|
|
18
|
-
import { fromD1 } from "@/lib/d1/catalog.js";
|
|
19
|
-
import { readD1 } from "@/lib/env";
|
|
20
|
-
|
|
21
|
-
export const prerender = false;
|
|
22
|
-
|
|
23
|
-
const DELIVERY_STATES: readonly DeliveryState[] = ["pending", "retrying", "delivered", "dead-lettered"];
|
|
24
|
-
|
|
25
|
-
/** Absent = no filter (`undefined`); an unrecognized value is a 400, not a silent no-op. */
|
|
26
|
-
function parseStateFilter(raw: string | null): DeliveryState | undefined | "invalid" {
|
|
27
|
-
if (raw === null) return undefined;
|
|
28
|
-
return (DELIVERY_STATES as readonly string[]).includes(raw) ? (raw as DeliveryState) : "invalid";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
32
|
-
const auth = await authorizeAppRequest(context, null);
|
|
33
|
-
if (!auth.ok) return withRequestId(auth.response, context);
|
|
34
|
-
|
|
35
|
-
const state = parseStateFilter(new URL(context.request.url).searchParams.get("state"));
|
|
36
|
-
if (state === "invalid") {
|
|
37
|
-
return withRequestId(json({ error: "invalid state filter", code: "invalid_state_filter" }, 400), context);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
41
|
-
if (!d1) return withRequestId(json({ error: "webhook store not configured", code: "unconfigured" }, 503), context);
|
|
42
|
-
const store = new WebhookDeliveryStore(fromD1(d1));
|
|
43
|
-
|
|
44
|
-
const deliveries = await store.listDeliveriesByInstall(auth.install.installId, { state });
|
|
45
|
-
|
|
46
|
-
return withRequestId(json({ deliveries: deliveries.map(toContractDelivery) }), context);
|
|
47
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/cache-purge — bump a tenant's edge page-cache version.
|
|
3
|
-
*
|
|
4
|
-
* The ETL load step calls this per tenant after a (re)migration so freshly
|
|
5
|
-
* migrated content is visible immediately, rather than waiting out the page
|
|
6
|
-
* TTL. This closes the loop that HttpToTClient.purgeTenant() (catalog API cache)
|
|
7
|
-
* only half-covered: that purges the data cache; this purges the rendered HTML.
|
|
8
|
-
*
|
|
9
|
-
* Auth: `Authorization: Bearer <CACHE_PURGE_SECRET>`. JSON bodies are exempt from
|
|
10
|
-
* Astro's CSRF origin check, so the server-to-server ETL caller works.
|
|
11
|
-
*/
|
|
12
|
-
import type { APIContext } from "astro";
|
|
13
|
-
import { readEnv, readKv } from "@/lib/env";
|
|
14
|
-
import { bumpCacheVersion } from "@/lib/edgeCache";
|
|
15
|
-
import { recordActivity } from "@/lib/activity/recordActivity";
|
|
16
|
-
|
|
17
|
-
export const prerender = false;
|
|
18
|
-
|
|
19
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
20
|
-
const secret = await readEnv("CACHE_PURGE_SECRET");
|
|
21
|
-
const auth = context.request.headers.get("authorization");
|
|
22
|
-
if (!secret || auth !== `Bearer ${secret}`) {
|
|
23
|
-
return new Response("Unauthorized", { status: 401 });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const kv = await readKv("TENANT_CACHE");
|
|
27
|
-
if (!kv) {
|
|
28
|
-
return new Response("No cache store bound", { status: 503 });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let tenantId: string | undefined;
|
|
32
|
-
try {
|
|
33
|
-
tenantId = ((await context.request.json()) as { tenantId?: string })?.tenantId;
|
|
34
|
-
} catch {
|
|
35
|
-
/* fall through to 400 */
|
|
36
|
-
}
|
|
37
|
-
if (!tenantId) {
|
|
38
|
-
return new Response("tenantId required", { status: 400 });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const version = await bumpCacheVersion(kv, tenantId);
|
|
42
|
-
// D11: the caller is the Bearer-secret ETL load step (a machine, no session) — emit as
|
|
43
|
-
// the `agent` cohort with an opaque handle. Best-effort; never affects the purge.
|
|
44
|
-
recordActivity({
|
|
45
|
-
action: "cache.purged",
|
|
46
|
-
actor: { kind: "agent", id: "storefront" },
|
|
47
|
-
source: "server",
|
|
48
|
-
outcome: { status: "succeeded" },
|
|
49
|
-
scope: { tenantId },
|
|
50
|
-
payload: { args: { version } },
|
|
51
|
-
});
|
|
52
|
-
return new Response(JSON.stringify({ tenantId, version }), {
|
|
53
|
-
headers: { "content-type": "application/json" },
|
|
54
|
-
});
|
|
55
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/compliance/tamper — the server receiver for protected-surface tamper
|
|
3
|
-
* beacons (D-R7, `integration-protected-tamper-recovery`).
|
|
4
|
-
*
|
|
5
|
-
* The browser sentinel restores safe state IN the document and posts a
|
|
6
|
-
* `TamperEvidenceBeacon` here. This route validates it fail-closed, records the
|
|
7
|
-
* durable evidence + operator alert, and — when the beacon is attributable to a
|
|
8
|
-
* single active install — SUSPENDS that install. It is a DETECTIVE control: the
|
|
9
|
-
* suspension is after-the-fact recovery, never prevention.
|
|
10
|
-
*
|
|
11
|
-
* TENANT SCOPE is server-owned: the install lookup and suspend are ALWAYS keyed on
|
|
12
|
-
* `locals.tenant` (middleware-resolved from the request host), never on the
|
|
13
|
-
* body-supplied `tenant` — a page cannot ask to suspend another tenant's app. The
|
|
14
|
-
* beacon body carries no secret (the contract rejects one), and the response is a
|
|
15
|
-
* write-path ack the emitter ignores, so a failed post never breaks the page.
|
|
16
|
-
*/
|
|
17
|
-
import type { APIContext } from "astro";
|
|
18
|
-
import { getRegistryService, json } from "@/lib/apps/adminService.js";
|
|
19
|
-
import type { AppInstall } from "@/lib/apps/registryService.js";
|
|
20
|
-
import { RegistryError } from "@/lib/apps/registryService.js";
|
|
21
|
-
import { planTamperRecovery, type AttributableInstall } from "@tot/public-runtime";
|
|
22
|
-
|
|
23
|
-
export const prerender = false;
|
|
24
|
-
|
|
25
|
-
const SYSTEM_ACTOR = "system:tamper-sentinel";
|
|
26
|
-
|
|
27
|
-
function toAttributable(install: AppInstall): AttributableInstall {
|
|
28
|
-
return {
|
|
29
|
-
installId: install.installId,
|
|
30
|
-
appId: install.appId,
|
|
31
|
-
appVersion: install.appVersion,
|
|
32
|
-
status: install.status,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
37
|
-
const tenantId = context.locals.tenant?.tenant_id;
|
|
38
|
-
// No resolved tenant ⇒ nothing third-party could be scoped to; accept-and-drop so
|
|
39
|
-
// the emitter never errors or retries.
|
|
40
|
-
if (!tenantId) return json({ ok: true, accepted: false }, 202);
|
|
41
|
-
|
|
42
|
-
let body: unknown;
|
|
43
|
-
try {
|
|
44
|
-
body = await context.request.json();
|
|
45
|
-
} catch {
|
|
46
|
-
return json({ ok: false, error: "invalid JSON body" }, 400);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Resolve the tenant's installs (empty when the registry binding isn't configured
|
|
50
|
-
// in this runtime — the beacon is still validated + recorded, just not actioned).
|
|
51
|
-
const registry = await getRegistryService();
|
|
52
|
-
const installs: AttributableInstall[] =
|
|
53
|
-
registry instanceof Response
|
|
54
|
-
? []
|
|
55
|
-
: (await registry.listByTenant(tenantId)).map(toAttributable);
|
|
56
|
-
|
|
57
|
-
const plan = planTamperRecovery(body, installs, {
|
|
58
|
-
recordId: crypto.randomUUID(),
|
|
59
|
-
receivedAt: new Date().toISOString(),
|
|
60
|
-
});
|
|
61
|
-
if (!plan.ok) return json({ ok: false, error: "invalid tamper beacon" }, 400);
|
|
62
|
-
|
|
63
|
-
const { record, suspend } = plan.plan;
|
|
64
|
-
// Durable, secret-free evidence line for operations. The beacon itself is the record.
|
|
65
|
-
// eslint-disable-next-line no-console
|
|
66
|
-
console.warn(
|
|
67
|
-
`[tamper] tenant=${tenantId} record=${record.recordId} surface=${record.beacon.surface} kind=${record.beacon.kind} recovered=${record.beacon.recovered} app=${record.beacon.attribution?.appId ?? "unattributable"}`,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
let suspended = false;
|
|
71
|
-
if (suspend && !(registry instanceof Response)) {
|
|
72
|
-
try {
|
|
73
|
-
await registry.suspend(suspend.installId, SYSTEM_ACTOR);
|
|
74
|
-
suspended = true;
|
|
75
|
-
} catch (err) {
|
|
76
|
-
// An already-suspended/uninstalled install (invalid_state) is a benign
|
|
77
|
-
// no-op under rapid re-mutation; anything else is swallowed so the write
|
|
78
|
-
// path never errors the emitter. The audit row from a successful suspend is
|
|
79
|
-
// the durable trail.
|
|
80
|
-
if (!(err instanceof RegistryError)) {
|
|
81
|
-
// eslint-disable-next-line no-console
|
|
82
|
-
console.error(`[tamper] suspend failed for ${suspend.installId}`, err);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return json({ ok: true, accepted: true, recovered: record.beacon.recovered, suspended }, 202);
|
|
88
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/content-edit/ai-edit — the `/dev` page's "Build with Claude" live loop
|
|
3
|
-
* (WS5 G4 + decision draft-overlay-same-origin). A prompt becomes a reviewable
|
|
4
|
-
* per-file diff via tot-mcp's `content_edit` tool, applied to the signed-in
|
|
5
|
-
* developer's OWN tenant content — and materialized into their private DRAFT
|
|
6
|
-
* overlay so the /dev live preview shows the change instantly (visible AND
|
|
7
|
-
* interactive), while live/test channels and every other viewer are untouched.
|
|
8
|
-
*
|
|
9
|
-
* AUTH: `/dev`'s own signed-in-viewer session (`readViewerSession`) is the
|
|
10
|
-
* developer-level gate; the MCP call authenticates as the storefront's own
|
|
11
|
-
* TENANT-OPERATOR credential (`getContentEditClient` — the same
|
|
12
|
-
* credential_validate path `/api/changes/accept` uses). The tenant edited is the
|
|
13
|
-
* SESSION'S OWN first membership — never a caller-supplied tenant.
|
|
14
|
-
*
|
|
15
|
-
* DRAFT MATERIALIZATION: content_edit returns unified DIFFS, not contents, so we
|
|
16
|
-
* re-apply each diff to the exact content we sent (applyUnifiedDiff, strict +
|
|
17
|
-
* fail-closed) and write the results to `dev-draft:` KV keys with a TTL, minting
|
|
18
|
-
* the signed tot_dev_draft cookie the middleware's overlay verifies. When KV or
|
|
19
|
-
* the signing secret is absent (e.g. bare `astro dev`), the edit still runs and
|
|
20
|
-
* returns diffs — `applied:false` tells the page the preview won't change.
|
|
21
|
-
*/
|
|
22
|
-
import type { APIContext } from "astro";
|
|
23
|
-
import { readViewerSession } from "@/lib/auth/route";
|
|
24
|
-
import { getContentEditClient } from "@/lib/content-edit";
|
|
25
|
-
import { applyUnifiedDiff, DiffApplyError } from "@/lib/content-edit/applyDiff";
|
|
26
|
-
import { json } from "@/lib/grants/route.js";
|
|
27
|
-
import { readEnv, readKv } from "@/lib/env";
|
|
28
|
-
// Arbitrary-tenant lookup lives behind the config-layer provider so this handler
|
|
29
|
-
// doesn't import the concrete resolver/static-map directly (tenancy-resolver-fence).
|
|
30
|
-
import { resolvePreviewTenant } from "@/config/previewTenantLookup";
|
|
31
|
-
import { createContentProvider } from "@/lib/storyblok/provider";
|
|
32
|
-
import { readPublishedArtifact, themeArtifactPath } from "@tot/public-runtime";
|
|
33
|
-
import {
|
|
34
|
-
DEV_DRAFT_COOKIE,
|
|
35
|
-
DEV_DRAFT_TTL_SECONDS,
|
|
36
|
-
devDraftKey,
|
|
37
|
-
signDevDraftToken,
|
|
38
|
-
verifyDevDraftToken,
|
|
39
|
-
} from "@/lib/dev/devDraft";
|
|
40
|
-
import type { ContentEditFile } from "@/lib/content-edit/types";
|
|
41
|
-
|
|
42
|
-
export const prerender = false;
|
|
43
|
-
|
|
44
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
45
|
-
const session = await readViewerSession({ cookies: context.cookies });
|
|
46
|
-
if (!session) return json({ error: "sign in required" }, 401);
|
|
47
|
-
if (!session.capability || session.capability === "none") {
|
|
48
|
-
return json({ error: "no developer capability on this account" }, 403);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let body: { prompt?: unknown };
|
|
52
|
-
try {
|
|
53
|
-
body = (await context.request.json()) as typeof body;
|
|
54
|
-
} catch {
|
|
55
|
-
return json({ error: "invalid JSON body" }, 400);
|
|
56
|
-
}
|
|
57
|
-
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
58
|
-
if (!prompt) return json({ error: "prompt is required" }, 400);
|
|
59
|
-
|
|
60
|
-
// The tenant is the session's OWN store (first membership, else the session's
|
|
61
|
-
// primary resource) — the same resolution /dev renders with. No caller-supplied
|
|
62
|
-
// tenant is ever honored here.
|
|
63
|
-
const wanted = (session.tenants?.[0]?.resource ?? session.resource ?? "").toLowerCase();
|
|
64
|
-
const tenant = wanted ? await resolvePreviewTenant(wanted) : null;
|
|
65
|
-
if (!tenant) return json({ error: "no store linked to this account" }, 403);
|
|
66
|
-
|
|
67
|
-
const kv = await readKv("TENANT_CACHE");
|
|
68
|
-
const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
|
|
69
|
-
const secret = (await readEnv("CUSTOMIZATION_PREVIEW_SECRET")) ?? null;
|
|
70
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
71
|
-
|
|
72
|
-
// Reuse the caller's existing draft (so successive edits stack) or start a new one.
|
|
73
|
-
const priorCookie = context.cookies.get(DEV_DRAFT_COOKIE)?.value ?? null;
|
|
74
|
-
const priorClaims =
|
|
75
|
-
priorCookie && secret ? await verifyDevDraftToken(secret, priorCookie, nowSeconds) : null;
|
|
76
|
-
const draftId =
|
|
77
|
-
priorClaims?.tenantId === tenant.tenant_id ? priorClaims.draftId : crypto.randomUUID();
|
|
78
|
-
|
|
79
|
-
// Current content = the developer's draft when present, else what actually serves
|
|
80
|
-
// (published overlay → build-time), so edits stack and always start from reality.
|
|
81
|
-
const provider = createContentProvider(tenant, kv ? { get: kvGet, env: "live" } : undefined);
|
|
82
|
-
const draftHome = await kvGet(devDraftKey(tenant.tenant_id, draftId, "content/home.html"));
|
|
83
|
-
const homeHtml = draftHome ?? (await provider.getHomeHtml());
|
|
84
|
-
const draftTheme = await kvGet(devDraftKey(tenant.tenant_id, draftId, "theme.json"));
|
|
85
|
-
const themeJson =
|
|
86
|
-
draftTheme ??
|
|
87
|
-
(await readPublishedArtifact(kvGet, tenant.tenant_id, "live", themeArtifactPath(tenant.tenant_id))) ??
|
|
88
|
-
JSON.stringify(tenant.theme_tokens ?? {}, null, 2);
|
|
89
|
-
|
|
90
|
-
// content_edit's allowlist: home document + theme. A block-based tenant with no raw
|
|
91
|
-
// home.html still gets theme edits (themeStyle drives its pages).
|
|
92
|
-
const files: ContentEditFile[] = [];
|
|
93
|
-
if (homeHtml) files.push({ path: "content/home.html", content: homeHtml });
|
|
94
|
-
files.push({ path: "theme.json", content: themeJson });
|
|
95
|
-
|
|
96
|
-
const client = await getContentEditClient();
|
|
97
|
-
if (client instanceof Response) return client;
|
|
98
|
-
|
|
99
|
-
let result;
|
|
100
|
-
try {
|
|
101
|
-
result = await client.editContent(prompt, files);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
104
|
-
return json({ ok: false, error: message }, 502);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Materialize the draft: re-apply each returned diff to the exact content we sent.
|
|
108
|
-
// Strict + fail-closed per file — a bad diff writes nothing for that file. Theme
|
|
109
|
-
// must still parse as JSON before it may land (a broken draft theme would degrade
|
|
110
|
-
// the whole preview).
|
|
111
|
-
const sent = new Map(files.map((f) => [f.path, f.content]));
|
|
112
|
-
const appliedPaths: string[] = [];
|
|
113
|
-
const canApply = !!kv && !!secret;
|
|
114
|
-
if (canApply) {
|
|
115
|
-
for (const d of result.diffs ?? []) {
|
|
116
|
-
const base = sent.get(d.path);
|
|
117
|
-
if (base == null) continue; // never write a path we didn't offer
|
|
118
|
-
let next: string;
|
|
119
|
-
try {
|
|
120
|
-
next = applyUnifiedDiff(base, d.diff);
|
|
121
|
-
} catch (err) {
|
|
122
|
-
if (err instanceof DiffApplyError) continue;
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
if (next === base) continue;
|
|
126
|
-
if (d.path === "theme.json") {
|
|
127
|
-
try {
|
|
128
|
-
JSON.parse(next);
|
|
129
|
-
} catch {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
await kv!.put(devDraftKey(tenant.tenant_id, draftId, d.path), next, {
|
|
134
|
-
expirationTtl: DEV_DRAFT_TTL_SECONDS,
|
|
135
|
-
});
|
|
136
|
-
appliedPaths.push(d.path);
|
|
137
|
-
}
|
|
138
|
-
if (appliedPaths.length > 0) {
|
|
139
|
-
// (Re)issue the signed draft cookie so the middleware overlay serves this draft.
|
|
140
|
-
const token = await signDevDraftToken(
|
|
141
|
-
secret!,
|
|
142
|
-
tenant.tenant_id,
|
|
143
|
-
draftId,
|
|
144
|
-
DEV_DRAFT_TTL_SECONDS,
|
|
145
|
-
nowSeconds,
|
|
146
|
-
);
|
|
147
|
-
context.cookies.set(DEV_DRAFT_COOKIE, token, {
|
|
148
|
-
httpOnly: true,
|
|
149
|
-
secure: true,
|
|
150
|
-
sameSite: "lax",
|
|
151
|
-
path: "/",
|
|
152
|
-
maxAge: DEV_DRAFT_TTL_SECONDS,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return json({
|
|
158
|
-
ok: true,
|
|
159
|
-
...result,
|
|
160
|
-
// True when the draft landed and the live preview will show it on reload.
|
|
161
|
-
applied: appliedPaths.length > 0,
|
|
162
|
-
appliedPaths,
|
|
163
|
-
});
|
|
164
|
-
}
|