@tokenoftrust/storefront-runner 1.3.4-rc.4 → 1.4.0
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.config.mjs +15 -0
- package/apps/storefront/dev-plugins/dev-publish.mjs +55 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +86 -7
- package/apps/storefront/drizzle.config.apps.ts +13 -0
- package/apps/storefront/env.d.ts +10 -0
- package/apps/storefront/migrations/README.md +13 -7
- package/apps/storefront/migrations-apps/0000_fast_millenium_guard.sql +129 -0
- package/apps/storefront/migrations-apps/meta/0000_snapshot.json +843 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +13 -0
- package/apps/storefront/package.json +13 -1
- package/apps/storefront/perf/README.md +64 -0
- package/apps/storefront/perf/assert-budgets.ts +159 -0
- package/apps/storefront/playwright.config.ts +23 -0
- package/apps/storefront/public/js/dashboard-apps.js +173 -0
- package/apps/storefront/public/shared/commerce-marketing.css +221 -0
- package/apps/storefront/src/components/CollectionCard.astro +1 -0
- package/apps/storefront/src/components/ProductCard.astro +1 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1959 -0
- package/apps/storefront/src/components/apps/AppWidgetFrame.astro +30 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +6 -3
- package/apps/storefront/src/components/chrome/SiteFooter.astro +10 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +10 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +3 -2
- package/apps/storefront/src/components/content/Callout.astro +75 -0
- package/apps/storefront/src/components/content/NeedsReviewCallout.astro +66 -0
- package/apps/storefront/src/components/content/ProseSections.astro +121 -0
- package/apps/storefront/src/components/content/ProseToc.astro +34 -0
- package/apps/storefront/src/components/content/RichText.astro +44 -0
- package/apps/storefront/src/components/content/TrustStrip.astro +46 -0
- package/apps/storefront/src/components/home/Hero.astro +14 -0
- package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +295 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +42 -21
- package/apps/storefront/src/components/islands/VariantSelector.tsx +62 -12
- package/apps/storefront/src/components/plp/FacetSidebar.astro +2 -2
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +1 -0
- package/apps/storefront/src/config/compliance/rulesets.ts +79 -0
- package/apps/storefront/src/config/storeName.ts +34 -0
- package/apps/storefront/src/layouts/Layout.astro +97 -23
- package/apps/storefront/src/lib/analytics/budgets.json +69 -0
- package/apps/storefront/src/lib/analytics/lighthouseReport.ts +109 -0
- package/apps/storefront/src/lib/analytics/perfBudgets.ts +452 -0
- package/apps/storefront/src/lib/analytics/rumAlert.ts +179 -0
- package/apps/storefront/src/lib/analytics/webVitals.ts +269 -0
- package/apps/storefront/src/lib/apps/adminService.ts +106 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +205 -0
- package/apps/storefront/src/lib/apps/apiAuth.ts +91 -0
- package/apps/storefront/src/lib/apps/apiRoute.ts +35 -0
- package/apps/storefront/src/lib/apps/catalogMapper.ts +39 -0
- package/apps/storefront/src/lib/apps/credentials.ts +153 -0
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +156 -0
- package/apps/storefront/src/lib/apps/healthAggregate.ts +66 -0
- package/apps/storefront/src/lib/apps/orders/attributionService.ts +206 -0
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +28 -0
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +203 -0
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +100 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +117 -0
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +91 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +181 -0
- package/apps/storefront/src/lib/apps/registryService.ts +579 -0
- package/apps/storefront/src/lib/apps/scopes.ts +79 -0
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +121 -0
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +148 -0
- package/apps/storefront/src/lib/apps/widgets/eligibility.ts +18 -0
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +52 -0
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +84 -0
- package/apps/storefront/src/lib/apps/widgets/placements.ts +57 -0
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +111 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +119 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +21 -2
- package/apps/storefront/src/lib/auth/loginGate.ts +102 -18
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +215 -0
- package/apps/storefront/src/lib/auth/route.ts +16 -1
- package/apps/storefront/src/lib/auth/session.ts +8 -0
- package/apps/storefront/src/lib/auth/stepUpChallenge.ts +107 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +208 -0
- package/apps/storefront/src/lib/blog/provider.ts +39 -0
- package/apps/storefront/src/lib/blog/types.ts +26 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +39 -1
- package/apps/storefront/src/lib/chrome/model.ts +11 -0
- package/apps/storefront/src/lib/compliance/enforcement.ts +95 -0
- package/apps/storefront/src/lib/content/callout.ts +78 -0
- package/apps/storefront/src/lib/content/index.ts +27 -0
- package/apps/storefront/src/lib/content/needsReview.ts +58 -0
- package/apps/storefront/src/lib/content/prose.ts +186 -0
- package/apps/storefront/src/lib/content/richtext.ts +76 -0
- package/apps/storefront/src/lib/content/trustStrip.ts +74 -0
- package/apps/storefront/src/lib/content-edit/client.ts +70 -14
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +213 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +8 -2
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +65 -115
- package/apps/storefront/src/lib/dev/cockpitStore.ts +65 -0
- package/apps/storefront/src/lib/dev/previewStatus.ts +112 -0
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +241 -0
- package/apps/storefront/src/lib/dev/subjectReissue.ts +67 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +10 -10
- package/apps/storefront/src/lib/env.ts +12 -0
- package/apps/storefront/src/lib/jsonld.ts +12 -17
- package/apps/storefront/src/lib/membership/eligibility.ts +37 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +302 -0
- package/apps/storefront/src/lib/privacy/emailHint.ts +13 -5
- package/apps/storefront/src/lib/publish/apex-readiness.ts +337 -0
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +269 -0
- package/apps/storefront/src/lib/publish/domainState.ts +351 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +362 -0
- package/apps/storefront/src/lib/rawChrome.ts +34 -3
- package/apps/storefront/src/lib/storyblok/content-model.ts +34 -2
- package/apps/storefront/src/lib/storyblok/provider.ts +11 -4
- package/apps/storefront/src/lib/subscription/model.ts +114 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +3 -3
- package/apps/storefront/src/lib/tot/query.ts +32 -0
- package/apps/storefront/src/lib/webhooks/cloudflareQueueDispatcher.ts +82 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +245 -0
- package/apps/storefront/src/lib/webhooks/deliveryMapper.ts +34 -0
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +668 -0
- package/apps/storefront/src/lib/webhooks/dispatcher.ts +168 -0
- package/apps/storefront/src/lib/webhooks/emit.ts +167 -0
- package/apps/storefront/src/lib/webhooks/endpointGuard.ts +135 -0
- package/apps/storefront/src/lib/webhooks/events.ts +98 -0
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +49 -0
- package/apps/storefront/src/lib/webhooks/signing.ts +29 -0
- package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +146 -0
- package/apps/storefront/src/middleware/index.ts +28 -13
- package/apps/storefront/src/pages/404.astro +21 -9
- package/apps/storefront/src/pages/[...slug].astro +36 -2
- package/apps/storefront/src/pages/admin.astro +29 -3
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +60 -0
- package/apps/storefront/src/pages/api/apps/admin/health.ts +44 -0
- package/apps/storefront/src/pages/api/apps/admin/install.ts +100 -0
- package/apps/storefront/src/pages/api/apps/admin/list.ts +26 -0
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +56 -0
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +57 -0
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +74 -0
- package/apps/storefront/src/pages/api/apps/admin/update.ts +103 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +59 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +51 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +172 -0
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +87 -0
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +142 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +39 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +49 -0
- package/apps/storefront/src/pages/api/apps/v1/health.ts +32 -0
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +59 -0
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +48 -0
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +73 -0
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +21 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +62 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +47 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +48 -1
- package/apps/storefront/src/pages/api/auth/step-up-send.ts +57 -0
- package/apps/storefront/src/pages/api/auth/step-up-verify.ts +129 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +23 -0
- package/apps/storefront/src/pages/api/compliance/preflight.ts +206 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +54 -0
- package/apps/storefront/src/pages/api/test/dev-session.ts +133 -0
- package/apps/storefront/src/pages/auth/login.astro +197 -41
- package/apps/storefront/src/pages/auth/magic.astro +75 -43
- package/apps/storefront/src/pages/blog/[slug].astro +107 -0
- package/apps/storefront/src/pages/blog/index.astro +98 -0
- package/apps/storefront/src/pages/capabilities.astro +8 -0
- package/apps/storefront/src/pages/cockpit.astro +433 -69
- package/apps/storefront/src/pages/collections/[handle].astro +8 -0
- package/apps/storefront/src/pages/collections/index.astro +10 -2
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +119 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +5 -0
- package/apps/storefront/src/pages/index.astro +57 -0
- package/apps/storefront/src/pages/llms.txt.ts +31 -10
- package/apps/storefront/src/pages/products/[handle].astro +100 -9
- package/apps/storefront/src/pages/sitemap.xml.ts +21 -4
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +198 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -0
- package/apps/storefront/src/pages/style-guide/index.astro +10 -0
- package/apps/storefront/src/styles/fonts.css +54 -0
- package/apps/storefront/src/styles/global.css +22 -2
- package/apps/storefront/src/themes/schema.ts +3 -25
- package/apps/storefront/tsconfig.json +1 -1
- package/apps/storefront/vitest.config.ts +4 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/candidate-index.ts +311 -0
- package/packages/public-runtime/src/checkout.ts +94 -2
- package/packages/public-runtime/src/compliance/evaluate.ts +265 -0
- package/packages/public-runtime/src/compliance/evidence-signals.ts +81 -0
- package/packages/public-runtime/src/compliance/index.ts +22 -0
- package/packages/public-runtime/src/compliance/pact-report.ts +94 -0
- package/packages/public-runtime/src/compliance/profile.ts +198 -0
- package/packages/public-runtime/src/compliance/ruleset.ts +117 -0
- package/packages/public-runtime/src/compliance/verification.ts +81 -0
- package/packages/public-runtime/src/csp.ts +27 -3
- package/packages/public-runtime/src/customization-reconcile.ts +35 -0
- package/packages/public-runtime/src/customization-runtime.ts +8 -0
- package/packages/public-runtime/src/customization-versioning.ts +17 -0
- package/packages/public-runtime/src/extension-contract.ts +2 -1
- package/packages/public-runtime/src/hash.ts +25 -0
- package/packages/public-runtime/src/index.ts +6 -0
- package/packages/public-runtime/src/membership.ts +353 -0
- package/packages/public-runtime/src/product.ts +24 -2
- package/packages/public-runtime/src/review-trust-proof.ts +194 -0
- package/packages/public-runtime/src/tenant-assets.ts +40 -5
- package/packages/public-runtime/src/tenant.ts +236 -0
- package/packages/public-runtime/src/widget-postmessage.ts +205 -0
- package/scripts/dev/publish.mjs +158 -0
- package/scripts/dev/transient-files.mjs +2 -1
- package/tenants/home/public/fonts/inter-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-800-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-800-normal.woff2 +0 -0
- package/tenants/home/public/pages/storefront.css +23 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +0 -169
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/apps/admin/install — install a private app for the resolved
|
|
3
|
+
* operator tenant + a caller-chosen env (PrivateApps epic, D2 Chunk C).
|
|
4
|
+
* Session-authed (see `@/lib/apps/adminSession`) — NOT the app JWT (D3's data
|
|
5
|
+
* surface). `tenantId` comes only from the resolved session, never the body.
|
|
6
|
+
*
|
|
7
|
+
* The response's `credential.secret` is the ONE time the plaintext client
|
|
8
|
+
* secret is ever surfaced — never logged, never returned again (see
|
|
9
|
+
* `registryService.ts`'s `InstallResult` doc).
|
|
10
|
+
*/
|
|
11
|
+
import type { APIContext } from "astro";
|
|
12
|
+
import { resolveAdminSession } from "@/lib/apps/adminSession.js";
|
|
13
|
+
import { getRegistryService, registryErrorResponse, json } from "@/lib/apps/adminService.js";
|
|
14
|
+
import { RegistryError, type InstallInput } from "@/lib/apps/registryService.js";
|
|
15
|
+
import { emitWebhookEvent } from "@/lib/webhooks/emit.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
interface InstallBody {
|
|
20
|
+
appId?: unknown;
|
|
21
|
+
appVersion?: unknown;
|
|
22
|
+
env?: unknown;
|
|
23
|
+
scopes?: unknown;
|
|
24
|
+
manifest?: unknown;
|
|
25
|
+
webhookEndpoint?: unknown;
|
|
26
|
+
widgetPlacements?: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Validate + narrow the request body into `InstallInput`, or a 400 message. */
|
|
30
|
+
function parseInstallInput(body: InstallBody): InstallInput | string {
|
|
31
|
+
if (typeof body.appId !== "string" || !body.appId.trim()) return "appId is required";
|
|
32
|
+
if (typeof body.appVersion !== "string" || !body.appVersion.trim()) return "appVersion is required";
|
|
33
|
+
if (typeof body.env !== "string" || !body.env.trim()) return "env is required";
|
|
34
|
+
if (!Array.isArray(body.scopes) || body.scopes.some((s) => typeof s !== "string")) {
|
|
35
|
+
return "scopes must be a string[]";
|
|
36
|
+
}
|
|
37
|
+
if (body.manifest === null || typeof body.manifest !== "object" || Array.isArray(body.manifest)) {
|
|
38
|
+
return "manifest is required and must be an object";
|
|
39
|
+
}
|
|
40
|
+
if (
|
|
41
|
+
body.webhookEndpoint !== undefined &&
|
|
42
|
+
body.webhookEndpoint !== null &&
|
|
43
|
+
typeof body.webhookEndpoint !== "string"
|
|
44
|
+
) {
|
|
45
|
+
return "webhookEndpoint, when given, must be a string or null";
|
|
46
|
+
}
|
|
47
|
+
if (body.widgetPlacements !== undefined && !Array.isArray(body.widgetPlacements)) {
|
|
48
|
+
return "widgetPlacements, when given, must be an array";
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
appId: body.appId,
|
|
52
|
+
appVersion: body.appVersion,
|
|
53
|
+
env: body.env,
|
|
54
|
+
scopes: body.scopes as string[],
|
|
55
|
+
manifest: body.manifest,
|
|
56
|
+
webhookEndpoint: (body.webhookEndpoint as string | null | undefined) ?? undefined,
|
|
57
|
+
widgetPlacements: body.widgetPlacements as unknown[] | undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
62
|
+
const auth = await resolveAdminSession(context);
|
|
63
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
64
|
+
|
|
65
|
+
let body: InstallBody;
|
|
66
|
+
try {
|
|
67
|
+
body = (await context.request.json()) as InstallBody;
|
|
68
|
+
} catch {
|
|
69
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const input = parseInstallInput(body);
|
|
73
|
+
if (typeof input === "string") return json({ error: input }, 400);
|
|
74
|
+
|
|
75
|
+
const registry = await getRegistryService();
|
|
76
|
+
if (registry instanceof Response) return registry;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const result = await registry.install(input, { tenantId: auth.tenantId, actor: auth.actor });
|
|
80
|
+
try {
|
|
81
|
+
// Best-effort: a webhook-plumbing hiccup must never turn a successful
|
|
82
|
+
// install into a failed response — see emit.ts's header.
|
|
83
|
+
await emitWebhookEvent("app.installed", auth.tenantId, {}, { installId: result.install.installId });
|
|
84
|
+
} catch (cause) {
|
|
85
|
+
console.warn(
|
|
86
|
+
"[diag:webhooks:emit]",
|
|
87
|
+
JSON.stringify({
|
|
88
|
+
topic: "app.installed",
|
|
89
|
+
tenantId: auth.tenantId,
|
|
90
|
+
installId: result.install.installId,
|
|
91
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return json(result, 201);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err instanceof RegistryError) return registryErrorResponse(err);
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/apps/admin/list — list the app installs for the resolved operator
|
|
3
|
+
* tenant (PrivateApps epic, D2 Chunk C; D8 console-session auth added). The
|
|
4
|
+
* `?tenant=` query param (read by console callers with several memberships,
|
|
5
|
+
* mirroring `../../grants/team-members.ts`) is only ever a CANDIDATE into
|
|
6
|
+
* `resolveOperatorSession` — the tenant actually used below is `auth.tenantId`
|
|
7
|
+
* from the resolved session, never the raw param, so a caller can only ever
|
|
8
|
+
* see their own tenant's installs. Read-only: any membership role admits.
|
|
9
|
+
*/
|
|
10
|
+
import type { APIContext } from "astro";
|
|
11
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
12
|
+
import { getRegistryService, json } from "@/lib/apps/adminService.js";
|
|
13
|
+
|
|
14
|
+
export const prerender = false;
|
|
15
|
+
|
|
16
|
+
export async function GET(context: APIContext): Promise<Response> {
|
|
17
|
+
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
18
|
+
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
19
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
20
|
+
|
|
21
|
+
const registry = await getRegistryService();
|
|
22
|
+
if (registry instanceof Response) return registry;
|
|
23
|
+
|
|
24
|
+
const installs = await registry.listByTenant(auth.tenantId);
|
|
25
|
+
return json({ installs });
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/apps/admin/resume — resume a suspended install back to active
|
|
3
|
+
* (PrivateApps epic, D2 Chunk C; D8 console-session auth added).
|
|
4
|
+
*
|
|
5
|
+
* `registryService.resume()` takes a bare `installId` with no tenant check of
|
|
6
|
+
* its own (Chunk A assumption #1) — so this route resolves the install via
|
|
7
|
+
* `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
|
|
8
|
+
* `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
|
|
9
|
+
* existence oracle). Mutating: `requireManageRole` gates a console
|
|
10
|
+
* `developer` role out — the host-bound/Bearer path is exempt (no `role`).
|
|
11
|
+
*/
|
|
12
|
+
import type { APIContext } from "astro";
|
|
13
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
14
|
+
import {
|
|
15
|
+
getRegistryService,
|
|
16
|
+
registryErrorResponse,
|
|
17
|
+
requireOwnedInstall,
|
|
18
|
+
requireManageRole,
|
|
19
|
+
parseInstallId,
|
|
20
|
+
json,
|
|
21
|
+
} from "@/lib/apps/adminService.js";
|
|
22
|
+
import { RegistryError } from "@/lib/apps/registryService.js";
|
|
23
|
+
|
|
24
|
+
export const prerender = false;
|
|
25
|
+
|
|
26
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
27
|
+
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
28
|
+
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
29
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
30
|
+
const manageDenied = requireManageRole(auth);
|
|
31
|
+
if (manageDenied) return manageDenied;
|
|
32
|
+
|
|
33
|
+
let body: unknown;
|
|
34
|
+
try {
|
|
35
|
+
body = await context.request.json();
|
|
36
|
+
} catch {
|
|
37
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const installId = parseInstallId(body);
|
|
41
|
+
if (!installId) return json({ error: "installId is required" }, 400);
|
|
42
|
+
|
|
43
|
+
const registry = await getRegistryService();
|
|
44
|
+
if (registry instanceof Response) return registry;
|
|
45
|
+
|
|
46
|
+
const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
|
|
47
|
+
if (owned instanceof Response) return owned;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const install = await registry.resume(installId, auth.actor);
|
|
51
|
+
return json({ install });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err instanceof RegistryError) return registryErrorResponse(err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/apps/admin/suspend — suspend an active install: immediately
|
|
3
|
+
* disables its API tokens, webhook delivery, and widget placements
|
|
4
|
+
* (PrivateApps epic, D2 Chunk C; D8 console-session auth added).
|
|
5
|
+
*
|
|
6
|
+
* `registryService.suspend()` takes a bare `installId` with no tenant check
|
|
7
|
+
* of its own (Chunk A assumption #1) — so this route resolves the install via
|
|
8
|
+
* `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
|
|
9
|
+
* `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
|
|
10
|
+
* existence oracle). Mutating: `requireManageRole` gates a console
|
|
11
|
+
* `developer` role out — the host-bound/Bearer path is exempt (no `role`).
|
|
12
|
+
*/
|
|
13
|
+
import type { APIContext } from "astro";
|
|
14
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
15
|
+
import {
|
|
16
|
+
getRegistryService,
|
|
17
|
+
registryErrorResponse,
|
|
18
|
+
requireOwnedInstall,
|
|
19
|
+
requireManageRole,
|
|
20
|
+
parseInstallId,
|
|
21
|
+
json,
|
|
22
|
+
} from "@/lib/apps/adminService.js";
|
|
23
|
+
import { RegistryError } from "@/lib/apps/registryService.js";
|
|
24
|
+
|
|
25
|
+
export const prerender = false;
|
|
26
|
+
|
|
27
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
28
|
+
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
29
|
+
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
30
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
31
|
+
const manageDenied = requireManageRole(auth);
|
|
32
|
+
if (manageDenied) return manageDenied;
|
|
33
|
+
|
|
34
|
+
let body: unknown;
|
|
35
|
+
try {
|
|
36
|
+
body = await context.request.json();
|
|
37
|
+
} catch {
|
|
38
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const installId = parseInstallId(body);
|
|
42
|
+
if (!installId) return json({ error: "installId is required" }, 400);
|
|
43
|
+
|
|
44
|
+
const registry = await getRegistryService();
|
|
45
|
+
if (registry instanceof Response) return registry;
|
|
46
|
+
|
|
47
|
+
const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
|
|
48
|
+
if (owned instanceof Response) return owned;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const install = await registry.suspend(installId, auth.actor);
|
|
52
|
+
return json({ install });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err instanceof RegistryError) return registryErrorResponse(err);
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/apps/admin/uninstall — uninstall an app: revokes credentials and
|
|
3
|
+
* clears placements, but retains the install row + audit trail (PrivateApps
|
|
4
|
+
* epic, D2 Chunk C; D8 console-session auth added).
|
|
5
|
+
*
|
|
6
|
+
* `registryService.uninstall()` takes a bare `installId` with no tenant check
|
|
7
|
+
* of its own (Chunk A assumption #1) — so this route resolves the install via
|
|
8
|
+
* `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
|
|
9
|
+
* `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
|
|
10
|
+
* existence oracle). Mutating: `requireManageRole` gates a console
|
|
11
|
+
* `developer` role out — the host-bound/Bearer path is exempt (no `role`).
|
|
12
|
+
*/
|
|
13
|
+
import type { APIContext } from "astro";
|
|
14
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
15
|
+
import {
|
|
16
|
+
getRegistryService,
|
|
17
|
+
registryErrorResponse,
|
|
18
|
+
requireOwnedInstall,
|
|
19
|
+
requireManageRole,
|
|
20
|
+
parseInstallId,
|
|
21
|
+
json,
|
|
22
|
+
} from "@/lib/apps/adminService.js";
|
|
23
|
+
import { RegistryError } from "@/lib/apps/registryService.js";
|
|
24
|
+
import { emitWebhookEvent } from "@/lib/webhooks/emit.js";
|
|
25
|
+
|
|
26
|
+
export const prerender = false;
|
|
27
|
+
|
|
28
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
29
|
+
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
30
|
+
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
31
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
32
|
+
const manageDenied = requireManageRole(auth);
|
|
33
|
+
if (manageDenied) return manageDenied;
|
|
34
|
+
|
|
35
|
+
let body: unknown;
|
|
36
|
+
try {
|
|
37
|
+
body = await context.request.json();
|
|
38
|
+
} catch {
|
|
39
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const installId = parseInstallId(body);
|
|
43
|
+
if (!installId) return json({ error: "installId is required" }, 400);
|
|
44
|
+
|
|
45
|
+
const registry = await getRegistryService();
|
|
46
|
+
if (registry instanceof Response) return registry;
|
|
47
|
+
|
|
48
|
+
const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
|
|
49
|
+
if (owned instanceof Response) return owned;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const install = await registry.uninstall(installId, auth.actor);
|
|
53
|
+
try {
|
|
54
|
+
// Best-effort, and deliberately AFTER uninstall has already flipped the
|
|
55
|
+
// install to "uninstalled" — this is the final farewell delivery; see
|
|
56
|
+
// emit.ts / deliveryEngine.ts's matching status exemption for it.
|
|
57
|
+
await emitWebhookEvent("app.uninstalled", auth.tenantId, {}, { installId });
|
|
58
|
+
} catch (cause) {
|
|
59
|
+
console.warn(
|
|
60
|
+
"[diag:webhooks:emit]",
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
topic: "app.uninstalled",
|
|
63
|
+
tenantId: auth.tenantId,
|
|
64
|
+
installId,
|
|
65
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return json({ install });
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err instanceof RegistryError) return registryErrorResponse(err);
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/apps/admin/update — patch an existing install's version, scopes,
|
|
3
|
+
* manifest snapshot, or webhook endpoint (PrivateApps epic, D2 Chunk C; D8
|
|
4
|
+
* console-session auth added).
|
|
5
|
+
*
|
|
6
|
+
* `registryService.update()` takes a bare `installId` with no tenant check of
|
|
7
|
+
* its own (Chunk A assumption #1) — so this route resolves the install via
|
|
8
|
+
* `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
|
|
9
|
+
* `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
|
|
10
|
+
* existence oracle). Mutating: `requireManageRole` gates a console
|
|
11
|
+
* `developer` role out — the host-bound/Bearer path is exempt (no `role`).
|
|
12
|
+
*/
|
|
13
|
+
import type { APIContext } from "astro";
|
|
14
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
15
|
+
import {
|
|
16
|
+
getRegistryService,
|
|
17
|
+
registryErrorResponse,
|
|
18
|
+
requireOwnedInstall,
|
|
19
|
+
requireManageRole,
|
|
20
|
+
parseInstallId,
|
|
21
|
+
json,
|
|
22
|
+
} from "@/lib/apps/adminService.js";
|
|
23
|
+
import { RegistryError, type AppInstallPatch } from "@/lib/apps/registryService.js";
|
|
24
|
+
|
|
25
|
+
export const prerender = false;
|
|
26
|
+
|
|
27
|
+
interface UpdateBody {
|
|
28
|
+
installId?: unknown;
|
|
29
|
+
appVersion?: unknown;
|
|
30
|
+
scopes?: unknown;
|
|
31
|
+
manifest?: unknown;
|
|
32
|
+
webhookEndpoint?: unknown;
|
|
33
|
+
widgetPlacements?: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Validate + narrow the given fields into `AppInstallPatch`, or a 400 message. */
|
|
37
|
+
function parsePatch(body: UpdateBody): AppInstallPatch | string {
|
|
38
|
+
const patch: AppInstallPatch = {};
|
|
39
|
+
if (body.appVersion !== undefined) {
|
|
40
|
+
if (typeof body.appVersion !== "string" || !body.appVersion.trim()) {
|
|
41
|
+
return "appVersion, when given, must be a non-empty string";
|
|
42
|
+
}
|
|
43
|
+
patch.appVersion = body.appVersion;
|
|
44
|
+
}
|
|
45
|
+
if (body.scopes !== undefined) {
|
|
46
|
+
if (!Array.isArray(body.scopes) || body.scopes.some((s) => typeof s !== "string")) {
|
|
47
|
+
return "scopes, when given, must be a string[]";
|
|
48
|
+
}
|
|
49
|
+
patch.scopes = body.scopes as string[];
|
|
50
|
+
}
|
|
51
|
+
if (body.manifest !== undefined) {
|
|
52
|
+
if (body.manifest === null || typeof body.manifest !== "object" || Array.isArray(body.manifest)) {
|
|
53
|
+
return "manifest, when given, must be an object";
|
|
54
|
+
}
|
|
55
|
+
patch.manifest = body.manifest;
|
|
56
|
+
}
|
|
57
|
+
if (body.webhookEndpoint !== undefined) {
|
|
58
|
+
if (body.webhookEndpoint !== null && typeof body.webhookEndpoint !== "string") {
|
|
59
|
+
return "webhookEndpoint, when given, must be a string or null";
|
|
60
|
+
}
|
|
61
|
+
patch.webhookEndpoint = body.webhookEndpoint as string | null;
|
|
62
|
+
}
|
|
63
|
+
if (body.widgetPlacements !== undefined) {
|
|
64
|
+
if (!Array.isArray(body.widgetPlacements)) return "widgetPlacements, when given, must be an array";
|
|
65
|
+
patch.widgetPlacements = body.widgetPlacements;
|
|
66
|
+
}
|
|
67
|
+
return patch;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
71
|
+
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
72
|
+
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
73
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
74
|
+
const manageDenied = requireManageRole(auth);
|
|
75
|
+
if (manageDenied) return manageDenied;
|
|
76
|
+
|
|
77
|
+
let body: UpdateBody;
|
|
78
|
+
try {
|
|
79
|
+
body = (await context.request.json()) as UpdateBody;
|
|
80
|
+
} catch {
|
|
81
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const installId = parseInstallId(body);
|
|
85
|
+
if (!installId) return json({ error: "installId is required" }, 400);
|
|
86
|
+
|
|
87
|
+
const patch = parsePatch(body);
|
|
88
|
+
if (typeof patch === "string") return json({ error: patch }, 400);
|
|
89
|
+
|
|
90
|
+
const registry = await getRegistryService();
|
|
91
|
+
if (registry instanceof Response) return registry;
|
|
92
|
+
|
|
93
|
+
const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
|
|
94
|
+
if (owned instanceof Response) return owned;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const install = await registry.update(installId, patch, auth.actor);
|
|
98
|
+
return json({ install });
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err instanceof RegistryError) return registryErrorResponse(err);
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/apps/admin/webhooks/deliveries/{deliveryId}/replay — the
|
|
3
|
+
* ops/support replay (PrivateApps epic, D4 Chunk E; D8 console-session auth
|
|
4
|
+
* added). Operator session only; `tenantId` comes from `auth.tenantId`
|
|
5
|
+
* (`resolveOperatorSession`), never the request. A `deliveryId` whose
|
|
6
|
+
* `tenantId` doesn't match the operator's resolved tenant 404s identically to
|
|
7
|
+
* a truly-missing one — same no-cross-tenant-oracle discipline
|
|
8
|
+
* `requireOwnedInstall` keeps for install ids. `webhook_deliveries` already
|
|
9
|
+
* carries its own `tenantId` (set at creation, mirroring the owning
|
|
10
|
+
* install's), so this checks that column directly rather than round-tripping
|
|
11
|
+
* through the registry. Mutating (re-enqueues a delivery): `requireManageRole`
|
|
12
|
+
* gates a console `developer` role out — the host-bound/Bearer path is
|
|
13
|
+
* exempt (no `role`).
|
|
14
|
+
*
|
|
15
|
+
* Not part of the app-facing `openapi.yaml` contract (same as the rest of
|
|
16
|
+
* `admin/*`) — no `Idempotency-Key` requirement; an authenticated operator
|
|
17
|
+
* retriggering a replay isn't the untrusted-client-retry case that header
|
|
18
|
+
* guards against.
|
|
19
|
+
*
|
|
20
|
+
* Same `requeueForReplay` + same-`deliveryId` re-enqueue as the app-facing
|
|
21
|
+
* replay — see that route's doc comment for the full rationale.
|
|
22
|
+
*/
|
|
23
|
+
import type { APIContext } from "astro";
|
|
24
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
25
|
+
import { json, requireManageRole } from "@/lib/apps/adminService.js";
|
|
26
|
+
import { WebhookDeliveryStore } from "@/lib/webhooks/deliveryStore.js";
|
|
27
|
+
import { getDispatcher } from "@/lib/webhooks/getDispatcher.js";
|
|
28
|
+
import { fromD1 } from "@/lib/d1/catalog.js";
|
|
29
|
+
import { readD1 } from "@/lib/env";
|
|
30
|
+
|
|
31
|
+
export const prerender = false;
|
|
32
|
+
|
|
33
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
34
|
+
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
35
|
+
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
36
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
37
|
+
const manageDenied = requireManageRole(auth);
|
|
38
|
+
if (manageDenied) return manageDenied;
|
|
39
|
+
|
|
40
|
+
const deliveryId = context.params.deliveryId;
|
|
41
|
+
if (!deliveryId) return json({ error: "Delivery not found.", code: "not_found" }, 404);
|
|
42
|
+
|
|
43
|
+
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
44
|
+
if (!d1) return json({ error: "webhook store not configured" }, 503);
|
|
45
|
+
const store = new WebhookDeliveryStore(fromD1(d1));
|
|
46
|
+
|
|
47
|
+
const delivery = await store.getDelivery(deliveryId);
|
|
48
|
+
if (!delivery || delivery.tenantId !== auth.tenantId) {
|
|
49
|
+
return json({ error: "Delivery not found.", code: "not_found" }, 404);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dispatcher = await getDispatcher();
|
|
53
|
+
if (dispatcher instanceof Response) return dispatcher;
|
|
54
|
+
|
|
55
|
+
const requeued = await store.requeueForReplay(deliveryId);
|
|
56
|
+
await dispatcher.enqueue({ deliveryId });
|
|
57
|
+
|
|
58
|
+
return json({ delivery: requeued }, 202);
|
|
59
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/apps/admin/webhooks/deliveries — the ops/support tenant-wide
|
|
3
|
+
* delivery view (PrivateApps epic, D4 Chunk E; D8 console-session auth
|
|
4
|
+
* added). `tenantId` used below comes only from the resolved operator
|
|
5
|
+
* session (`auth.tenantId`), never a raw query param — mirrors
|
|
6
|
+
* `admin/list.ts`'s tenant scoping; the same `?tenant=` param is read only as
|
|
7
|
+
* a CANDIDATE for `resolveOperatorSession` to admit against. Unlike the
|
|
8
|
+
* app-facing `/api/apps/v1/webhooks/deliveries` (one install's own
|
|
9
|
+
* deliveries), this surfaces every install's deliveries for the operator's
|
|
10
|
+
* tenant via the additive `listDeliveriesByTenant` store method. Read-only:
|
|
11
|
+
* any membership role admits.
|
|
12
|
+
*
|
|
13
|
+
* Not part of the app-facing `openapi.yaml` contract (same as the rest of
|
|
14
|
+
* `admin/*`) — returns the internal `WebhookDelivery` shape as-is (installId
|
|
15
|
+
* included; useful for support triage), no `toContractDelivery` narrowing.
|
|
16
|
+
*/
|
|
17
|
+
import type { APIContext } from "astro";
|
|
18
|
+
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
19
|
+
import { json } from "@/lib/apps/adminService.js";
|
|
20
|
+
import { WebhookDeliveryStore, type DeliveryState } from "@/lib/webhooks/deliveryStore.js";
|
|
21
|
+
import { fromD1 } from "@/lib/d1/catalog.js";
|
|
22
|
+
import { readD1 } from "@/lib/env";
|
|
23
|
+
|
|
24
|
+
export const prerender = false;
|
|
25
|
+
|
|
26
|
+
const DELIVERY_STATES: readonly DeliveryState[] = ["pending", "retrying", "delivered", "dead-lettered"];
|
|
27
|
+
|
|
28
|
+
/** Absent = no filter (`undefined`); an unrecognized value is a 400, not a silent no-op. */
|
|
29
|
+
function parseStateFilter(raw: string | null): DeliveryState | undefined | "invalid" {
|
|
30
|
+
if (raw === null) return undefined;
|
|
31
|
+
return (DELIVERY_STATES as readonly string[]).includes(raw) ? (raw as DeliveryState) : "invalid";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function GET(context: APIContext): Promise<Response> {
|
|
35
|
+
const params = new URL(context.request.url).searchParams;
|
|
36
|
+
const auth = await resolveOperatorSession(context, params.get("tenant"));
|
|
37
|
+
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
38
|
+
|
|
39
|
+
const state = parseStateFilter(params.get("state"));
|
|
40
|
+
if (state === "invalid") {
|
|
41
|
+
return json({ error: "invalid state filter", code: "invalid_state_filter" }, 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
45
|
+
if (!d1) return json({ error: "webhook store not configured" }, 503);
|
|
46
|
+
const store = new WebhookDeliveryStore(fromD1(d1));
|
|
47
|
+
|
|
48
|
+
const deliveries = await store.listDeliveriesByTenant(auth.tenantId, { state });
|
|
49
|
+
|
|
50
|
+
return json({ deliveries });
|
|
51
|
+
}
|