@littlebearapps/platform-admin-sdk 1.4.2 → 2.0.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/dist/templates.d.ts +1 -1
- package/dist/templates.js +232 -2
- package/package.json +1 -1
- package/templates/full/config/audit-targets.yaml +72 -0
- package/templates/full/dashboard/src/components/notifications/NotificationBell.tsx +30 -0
- package/templates/full/dashboard/src/components/notifications/NotificationList.tsx +116 -0
- package/templates/full/dashboard/src/components/notifications/index.ts +2 -0
- package/templates/full/dashboard/src/components/patterns/ActivePatterns.tsx +62 -0
- package/templates/full/dashboard/src/components/patterns/PatternStats.tsx +60 -0
- package/templates/full/dashboard/src/components/patterns/PatternTabs.tsx +116 -0
- package/templates/full/dashboard/src/components/patterns/SuggestionsQueue.tsx +115 -0
- package/templates/full/dashboard/src/components/patterns/SystemPatterns.tsx +52 -0
- package/templates/full/dashboard/src/components/patterns/index.ts +5 -0
- package/templates/full/dashboard/src/components/reports/GapDetectionReport.tsx +69 -0
- package/templates/full/dashboard/src/components/reports/SdkAuditReport.tsx +72 -0
- package/templates/full/dashboard/src/components/reports/index.ts +2 -0
- package/templates/full/dashboard/src/components/search/SearchModal.tsx +108 -0
- package/templates/full/dashboard/src/pages/api/notifications/[id]/read.ts +37 -0
- package/templates/full/dashboard/src/pages/api/notifications/index.ts +47 -0
- package/templates/full/dashboard/src/pages/api/notifications/read-all.ts +28 -0
- package/templates/full/dashboard/src/pages/api/notifications/unread-count.ts +31 -0
- package/templates/full/dashboard/src/pages/api/patterns/approve.ts +55 -0
- package/templates/full/dashboard/src/pages/api/patterns/cache-refresh.ts +38 -0
- package/templates/full/dashboard/src/pages/api/patterns/discover.ts +36 -0
- package/templates/full/dashboard/src/pages/api/patterns/index.ts +36 -0
- package/templates/full/dashboard/src/pages/api/patterns/ready-for-review.ts +39 -0
- package/templates/full/dashboard/src/pages/api/patterns/reject.ts +54 -0
- package/templates/full/dashboard/src/pages/api/patterns/stats.ts +39 -0
- package/templates/full/dashboard/src/pages/api/patterns/suggestions.ts +43 -0
- package/templates/full/dashboard/src/pages/api/reports/audit.ts +45 -0
- package/templates/full/dashboard/src/pages/api/reports/usage.ts +52 -0
- package/templates/full/dashboard/src/pages/api/search/index.ts +74 -0
- package/templates/full/dashboard/src/pages/api/search/reindex.ts +28 -0
- package/templates/full/dashboard/src/pages/api/search/stats.ts +27 -0
- package/templates/full/dashboard/src/pages/api/settings/index.ts +37 -0
- package/templates/full/dashboard/src/pages/api/settings/update.ts +41 -0
- package/templates/full/dashboard/src/pages/api/topology/index.ts +56 -0
- package/templates/full/dashboard/src/pages/notifications.astro +11 -0
- package/templates/full/migrations/008_auditor.sql +99 -0
- package/templates/full/migrations/010_pricing_versions.sql +110 -0
- package/templates/full/migrations/011_multi_account.sql +51 -0
- package/templates/full/scripts/ops/set-kv-pricing.ts +182 -0
- package/templates/full/scripts/ops/universal-backfill.ts +147 -0
- package/templates/full/workers/lib/ai-judge-schema.ts +181 -0
- package/templates/full/workers/lib/auditor/comprehensive-report.ts +407 -0
- package/templates/full/workers/lib/auditor/feature-coverage.ts +348 -0
- package/templates/full/workers/lib/auditor/index.ts +9 -0
- package/templates/full/workers/lib/auditor/types.ts +167 -0
- package/templates/full/workers/platform-auditor.ts +1071 -0
- package/templates/full/wrangler.auditor.jsonc.hbs +75 -0
- package/templates/shared/.github/workflows/contract-check.yml.hbs +42 -0
- package/templates/shared/.github/workflows/dashboard-deploy.yml.hbs +39 -0
- package/templates/shared/.github/workflows/platform-check.yml.hbs +28 -0
- package/templates/shared/.github/workflows/security.yml +33 -0
- package/templates/shared/config/observability.yaml.hbs +276 -0
- package/templates/shared/contracts/schemas/envelope.v1.schema.json +64 -0
- package/templates/shared/contracts/schemas/error_report.v1.schema.json +65 -0
- package/templates/shared/contracts/types/telemetry-envelope.types.ts +139 -0
- package/templates/shared/dashboard/astro.config.mjs +21 -0
- package/templates/shared/dashboard/package.json.hbs +29 -0
- package/templates/shared/dashboard/src/components/Header.astro +29 -0
- package/templates/shared/dashboard/src/components/Nav.astro.hbs +59 -0
- package/templates/shared/dashboard/src/components/infrastructure/AlertHistory.tsx +57 -0
- package/templates/shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx +73 -0
- package/templates/shared/dashboard/src/components/infrastructure/ServiceRegistry.tsx +55 -0
- package/templates/shared/dashboard/src/components/infrastructure/UptimeStatus.tsx +56 -0
- package/templates/shared/dashboard/src/components/infrastructure/index.ts +4 -0
- package/templates/shared/dashboard/src/components/overview/ActivityFeed.tsx +134 -0
- package/templates/shared/dashboard/src/components/overview/CostQuadrant.tsx +131 -0
- package/templates/shared/dashboard/src/components/overview/ErrorsQuadrant.tsx +113 -0
- package/templates/shared/dashboard/src/components/overview/HealthQuadrant.tsx +87 -0
- package/templates/shared/dashboard/src/components/overview/MissionControl.tsx +139 -0
- package/templates/shared/dashboard/src/components/resources/AllowanceStatus.tsx +44 -0
- package/templates/shared/dashboard/src/components/resources/CostCentreOverview.tsx +42 -0
- package/templates/shared/dashboard/src/components/resources/ResourceTabs.tsx +69 -0
- package/templates/shared/dashboard/src/components/resources/index.ts +3 -0
- package/templates/shared/dashboard/src/components/settings/SettingsCard.tsx +21 -0
- package/templates/shared/dashboard/src/components/settings/index.ts +1 -0
- package/templates/shared/dashboard/src/components/ui/AlertBanner.tsx +39 -0
- package/templates/shared/dashboard/src/components/ui/Breadcrumbs.tsx +27 -0
- package/templates/shared/dashboard/src/components/ui/EmptyState.tsx +26 -0
- package/templates/shared/dashboard/src/components/ui/ErrorBoundary.tsx +42 -0
- package/templates/shared/dashboard/src/components/ui/LoadingSkeleton.tsx +18 -0
- package/templates/shared/dashboard/src/components/ui/PageShell.tsx +26 -0
- package/templates/shared/dashboard/src/components/ui/Sparkline.tsx +127 -0
- package/templates/shared/dashboard/src/components/ui/StatusDot.tsx +21 -0
- package/templates/shared/dashboard/src/components/ui/Toast.tsx +44 -0
- package/templates/shared/dashboard/src/components/ui/index.ts +9 -0
- package/templates/shared/dashboard/src/components/usage/AnomaliesWidget.tsx +68 -0
- package/templates/shared/dashboard/src/components/usage/HourlyUsageChart.tsx +55 -0
- package/templates/shared/dashboard/src/components/usage/PlanAllowanceDashboard.tsx +67 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCostBreakdown.tsx +55 -0
- package/templates/shared/dashboard/src/components/usage/index.ts +4 -0
- package/templates/shared/dashboard/src/env.d.ts.hbs +34 -0
- package/templates/shared/dashboard/src/layouts/DashboardLayout.astro +37 -0
- package/templates/shared/dashboard/src/lib/cloudflare/costs.ts +21 -0
- package/templates/shared/dashboard/src/lib/fetch.ts +29 -0
- package/templates/shared/dashboard/src/lib/types.ts +72 -0
- package/templates/shared/dashboard/src/middleware/auth.ts +100 -0
- package/templates/shared/dashboard/src/middleware/index.ts +1 -0
- package/templates/shared/dashboard/src/pages/api/costs/overview.ts +65 -0
- package/templates/shared/dashboard/src/pages/api/costs/providers.ts +47 -0
- package/templates/shared/dashboard/src/pages/api/infrastructure/services.ts +55 -0
- package/templates/shared/dashboard/src/pages/api/infrastructure/stats.ts +99 -0
- package/templates/shared/dashboard/src/pages/api/overview/summary.ts +311 -0
- package/templates/shared/dashboard/src/pages/api/usage/allowances.ts +56 -0
- package/templates/shared/dashboard/src/pages/api/usage/anomalies.ts +45 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing.ts +53 -0
- package/templates/shared/dashboard/src/pages/api/usage/circuit-breakers.ts +44 -0
- package/templates/shared/dashboard/src/pages/api/usage/granular.ts +50 -0
- package/templates/shared/dashboard/src/pages/api/usage/hourly.ts +45 -0
- package/templates/shared/dashboard/src/pages/api/usage/projects.ts +51 -0
- package/templates/shared/dashboard/src/pages/api/usage/status.ts +42 -0
- package/templates/shared/dashboard/src/pages/api/user/identity.ts +11 -0
- package/templates/shared/dashboard/src/pages/dashboard.astro +11 -0
- package/templates/shared/dashboard/src/pages/index.astro +3 -0
- package/templates/shared/dashboard/src/pages/resources.astro +11 -0
- package/templates/shared/dashboard/src/pages/settings/index.astro +28 -0
- package/templates/shared/dashboard/src/pages/settings/notifications.astro +34 -0
- package/templates/shared/dashboard/src/pages/settings/thresholds.astro +39 -0
- package/templates/shared/dashboard/src/pages/settings/usage.astro +28 -0
- package/templates/shared/dashboard/src/styles/global.css +29 -0
- package/templates/shared/dashboard/tailwind.config.mjs +9 -0
- package/templates/shared/dashboard/tsconfig.json +9 -0
- package/templates/shared/dashboard/wrangler.json.hbs +47 -0
- package/templates/shared/docs/architecture.md +89 -0
- package/templates/shared/docs/post-deploy-runbook.md +126 -0
- package/templates/shared/docs/troubleshooting.md +91 -0
- package/templates/shared/package.json.hbs +17 -1
- package/templates/shared/scripts/ops/backfill-cloudflare-daily.ts +145 -0
- package/templates/shared/scripts/ops/backfill-cloudflare-hourly.ts +473 -0
- package/templates/shared/scripts/ops/backfill-monthly-rollups.ts +125 -0
- package/templates/shared/scripts/ops/discover-graphql-datasets.ts +482 -0
- package/templates/shared/scripts/ops/reset-budget-state.ts +279 -0
- package/templates/shared/scripts/ops/validate-controls.js +141 -0
- package/templates/shared/scripts/ops/validate-pipeline.ts +237 -0
- package/templates/shared/scripts/ops/verify-account-completeness.ts +236 -0
- package/templates/shared/scripts/validate-schemas.js +61 -0
- package/templates/shared/tests/contract/validate-schemas.test.ts +130 -0
- package/templates/shared/tests/fixtures/telemetry-envelope-invalid.json +9 -0
- package/templates/shared/tests/fixtures/telemetry-envelope-valid.json +27 -0
- package/templates/shared/tests/helpers/mock-d1.ts +61 -0
- package/templates/shared/tests/helpers/mock-kv.ts +37 -0
- package/templates/shared/tests/unit/workers/batch-persistence.test.ts +133 -0
- package/templates/shared/tests/unit/workers/budget-enforcement.test.ts +214 -0
- package/templates/shared/vitest.config.ts +18 -0
- package/templates/shared/workers/lib/usage/collectors/anthropic.ts +114 -0
- package/templates/shared/workers/lib/usage/collectors/apify.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/custom-http.ts +151 -0
- package/templates/shared/workers/lib/usage/collectors/deepseek.ts +92 -0
- package/templates/shared/workers/lib/usage/collectors/gemini.ts +263 -0
- package/templates/shared/workers/lib/usage/collectors/github.ts +362 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +31 -15
- package/templates/shared/workers/lib/usage/collectors/minimax.ts +106 -0
- package/templates/shared/workers/lib/usage/collectors/openai.ts +171 -0
- package/templates/shared/workers/lib/usage/collectors/resend.ts +79 -0
- package/templates/shared/workers/lib/usage/collectors/stripe.ts +192 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +46 -0
- package/templates/shared/workers/platform-usage.ts +98 -8
- package/templates/standard/dashboard/src/components/errors/ErrorStats.tsx +53 -0
- package/templates/standard/dashboard/src/components/errors/ErrorsTable.tsx +133 -0
- package/templates/standard/dashboard/src/components/errors/index.ts +2 -0
- package/templates/standard/dashboard/src/components/health/CircuitBreakerEvents.tsx +69 -0
- package/templates/standard/dashboard/src/components/health/CircuitBreakerPanel.tsx +97 -0
- package/templates/standard/dashboard/src/components/health/DlqStatusCard.tsx +52 -0
- package/templates/standard/dashboard/src/components/health/HealthTabs.tsx +86 -0
- package/templates/standard/dashboard/src/components/health/index.ts +4 -0
- package/templates/standard/dashboard/src/lib/errors.ts +28 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts +49 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/resolve.ts +36 -0
- package/templates/standard/dashboard/src/pages/api/errors/[fingerprint].ts +55 -0
- package/templates/standard/dashboard/src/pages/api/errors/index.ts +58 -0
- package/templates/standard/dashboard/src/pages/api/errors/stats.ts +55 -0
- package/templates/standard/dashboard/src/pages/api/health/audit-history.ts +37 -0
- package/templates/standard/dashboard/src/pages/api/health/dlq.ts +43 -0
- package/templates/standard/dashboard/src/pages/circuit-breakers.astro +13 -0
- package/templates/standard/dashboard/src/pages/errors.astro +13 -0
- package/templates/standard/dashboard/src/pages/health.astro +11 -0
- package/templates/standard/migrations/009_topology_mapper.sql +65 -0
- package/templates/standard/tests/unit/error-collector/capture.test.ts +106 -0
- package/templates/standard/tests/unit/error-collector/fingerprint.test.ts +155 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +37 -3
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +32 -1
- package/templates/standard/workers/lib/mapper/attribution-check.ts +339 -0
- package/templates/standard/workers/lib/mapper/index.ts +7 -0
- package/templates/standard/workers/platform-mapper.ts +482 -0
- package/templates/standard/workers/platform-sdk-test-client.ts +125 -0
- package/templates/standard/wrangler.mapper.jsonc.hbs +85 -0
- package/templates/standard/wrangler.sdk-test-client.jsonc.hbs +62 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ notifications: [], total: 0 }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const page = parseInt(url.searchParams.get('page') ?? '1');
|
|
13
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit') ?? '20'), 100);
|
|
14
|
+
const offset = (page - 1) * limit;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const notifications = await db
|
|
18
|
+
.prepare(
|
|
19
|
+
`SELECT id, title, body, category, priority, source, created_at, read_at, action_url, expires_at
|
|
20
|
+
FROM notifications
|
|
21
|
+
WHERE expires_at IS NULL OR expires_at > unixepoch()
|
|
22
|
+
ORDER BY created_at DESC
|
|
23
|
+
LIMIT ? OFFSET ?`
|
|
24
|
+
)
|
|
25
|
+
.bind(limit, offset)
|
|
26
|
+
.all();
|
|
27
|
+
|
|
28
|
+
const total = await db
|
|
29
|
+
.prepare(`SELECT COUNT(*) as count FROM notifications WHERE expires_at IS NULL OR expires_at > unixepoch() LIMIT 1`)
|
|
30
|
+
.first<{ count: number }>();
|
|
31
|
+
|
|
32
|
+
return new Response(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
notifications: notifications.results ?? [],
|
|
35
|
+
total: total?.count ?? 0,
|
|
36
|
+
page,
|
|
37
|
+
limit,
|
|
38
|
+
}),
|
|
39
|
+
{ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=15' } }
|
|
40
|
+
);
|
|
41
|
+
} catch {
|
|
42
|
+
return new Response(JSON.stringify({ notifications: [], total: 0 }), {
|
|
43
|
+
status: 500,
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ error: 'Database not available' }), {
|
|
8
|
+
status: 503,
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const result = await db
|
|
15
|
+
.prepare(`UPDATE notifications SET read_at = unixepoch() WHERE read_at IS NULL`)
|
|
16
|
+
.run();
|
|
17
|
+
|
|
18
|
+
return new Response(
|
|
19
|
+
JSON.stringify({ ok: true, updated: result.meta?.changes ?? 0 }),
|
|
20
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
21
|
+
);
|
|
22
|
+
} catch {
|
|
23
|
+
return new Response(JSON.stringify({ error: 'Failed to mark all as read' }), {
|
|
24
|
+
status: 500,
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ count: 0 }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const result = await db
|
|
14
|
+
.prepare(
|
|
15
|
+
`SELECT COUNT(*) as count FROM notifications
|
|
16
|
+
WHERE read_at IS NULL
|
|
17
|
+
AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
18
|
+
LIMIT 1`
|
|
19
|
+
)
|
|
20
|
+
.first<{ count: number }>();
|
|
21
|
+
|
|
22
|
+
return new Response(JSON.stringify({ count: result?.count ?? 0 }), {
|
|
23
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=15' },
|
|
24
|
+
});
|
|
25
|
+
} catch {
|
|
26
|
+
return new Response(JSON.stringify({ count: 0 }), {
|
|
27
|
+
status: 500,
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ locals, request }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
const patternApi = (locals.runtime?.env as { PATTERN_DISCOVERY_API?: Fetcher } | undefined)?.PATTERN_DISCOVERY_API;
|
|
6
|
+
|
|
7
|
+
if (!db && !patternApi) {
|
|
8
|
+
return new Response(JSON.stringify({ error: 'No database or pattern API available' }), {
|
|
9
|
+
status: 500,
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const body = (await request.json()) as { id: number; notes?: string };
|
|
16
|
+
|
|
17
|
+
if (patternApi) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
20
|
+
const res = await patternApi.fetch(
|
|
21
|
+
`https://internal/suggestions/${body.id}?action=approve`,
|
|
22
|
+
{
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ notes: body.notes }),
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
if (!res.ok) throw new Error(`Pattern API returned ${res.status}`);
|
|
31
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Fallback: update D1 directly
|
|
37
|
+
await db!
|
|
38
|
+
.prepare(
|
|
39
|
+
`UPDATE transient_pattern_suggestions
|
|
40
|
+
SET status = 'approved', reviewed_at = datetime('now'), reviewer_notes = ?
|
|
41
|
+
WHERE id = ?`
|
|
42
|
+
)
|
|
43
|
+
.bind(body.notes ?? null, body.id)
|
|
44
|
+
.run();
|
|
45
|
+
|
|
46
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return new Response(
|
|
51
|
+
JSON.stringify({ error: error instanceof Error ? error.message : 'Failed to approve' }),
|
|
52
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
const kv = (locals.runtime?.env as { PLATFORM_CACHE?: KVNamespace } | undefined)?.PLATFORM_CACHE;
|
|
6
|
+
|
|
7
|
+
if (!db || !kv) {
|
|
8
|
+
return new Response(JSON.stringify({ error: 'Bindings not available' }), {
|
|
9
|
+
status: 503,
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const approved = await db
|
|
16
|
+
.prepare(
|
|
17
|
+
`SELECT id, pattern_type, pattern_value, error_type, priority
|
|
18
|
+
FROM transient_pattern_suggestions
|
|
19
|
+
WHERE status = 'approved'
|
|
20
|
+
ORDER BY match_count DESC
|
|
21
|
+
LIMIT 500`
|
|
22
|
+
)
|
|
23
|
+
.all();
|
|
24
|
+
|
|
25
|
+
const patterns = approved.results ?? [];
|
|
26
|
+
await kv.put('PATTERNS:DYNAMIC:APPROVED', JSON.stringify(patterns), { expirationTtl: 86400 });
|
|
27
|
+
|
|
28
|
+
return new Response(
|
|
29
|
+
JSON.stringify({ refreshed: true, count: patterns.length }),
|
|
30
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
31
|
+
);
|
|
32
|
+
} catch {
|
|
33
|
+
return new Response(JSON.stringify({ error: 'Cache refresh failed' }), {
|
|
34
|
+
status: 500,
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ error: 'Database not available' }), {
|
|
8
|
+
status: 503,
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const unclassified = await db
|
|
15
|
+
.prepare(
|
|
16
|
+
`SELECT fingerprint, script_name, error_message, priority, occurrence_count
|
|
17
|
+
FROM error_occurrences
|
|
18
|
+
WHERE fingerprint NOT IN (
|
|
19
|
+
SELECT pattern_value FROM transient_pattern_suggestions WHERE status = 'approved'
|
|
20
|
+
)
|
|
21
|
+
ORDER BY occurrence_count DESC
|
|
22
|
+
LIMIT 50`
|
|
23
|
+
)
|
|
24
|
+
.all();
|
|
25
|
+
|
|
26
|
+
return new Response(
|
|
27
|
+
JSON.stringify({ discovered: unclassified.results?.length ?? 0, errors: unclassified.results ?? [] }),
|
|
28
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
29
|
+
);
|
|
30
|
+
} catch {
|
|
31
|
+
return new Response(JSON.stringify({ error: 'Discovery failed' }), {
|
|
32
|
+
status: 500,
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ suggestions: [] }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const status = url.searchParams.get('status') ?? 'pending';
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const suggestions = await db
|
|
16
|
+
.prepare(
|
|
17
|
+
`SELECT id, pattern_type, pattern_value, error_type, priority, status,
|
|
18
|
+
match_count, source, created_at, reviewed_at, reviewer_notes
|
|
19
|
+
FROM transient_pattern_suggestions
|
|
20
|
+
WHERE status = ?
|
|
21
|
+
ORDER BY created_at DESC
|
|
22
|
+
LIMIT 50`
|
|
23
|
+
)
|
|
24
|
+
.bind(status)
|
|
25
|
+
.all();
|
|
26
|
+
|
|
27
|
+
return new Response(JSON.stringify({ suggestions: suggestions.results ?? [] }), {
|
|
28
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=30' },
|
|
29
|
+
});
|
|
30
|
+
} catch {
|
|
31
|
+
return new Response(JSON.stringify({ suggestions: [] }), {
|
|
32
|
+
status: 500,
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ patterns: [], count: 0 }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const patterns = await db
|
|
14
|
+
.prepare(
|
|
15
|
+
`SELECT s.id, s.pattern_type, s.pattern_value, s.error_type, s.priority,
|
|
16
|
+
s.match_count, s.source, s.created_at,
|
|
17
|
+
COUNT(e.id) as evidence_count
|
|
18
|
+
FROM transient_pattern_suggestions s
|
|
19
|
+
LEFT JOIN pattern_match_evidence e ON e.suggestion_id = s.id
|
|
20
|
+
WHERE s.status = 'shadow'
|
|
21
|
+
AND s.match_count >= 3
|
|
22
|
+
AND s.created_at < datetime('now', '-3 days')
|
|
23
|
+
GROUP BY s.id
|
|
24
|
+
ORDER BY s.match_count DESC
|
|
25
|
+
LIMIT 20`
|
|
26
|
+
)
|
|
27
|
+
.all();
|
|
28
|
+
|
|
29
|
+
return new Response(
|
|
30
|
+
JSON.stringify({ patterns: patterns.results ?? [], count: patterns.results?.length ?? 0 }),
|
|
31
|
+
{ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=60' } }
|
|
32
|
+
);
|
|
33
|
+
} catch {
|
|
34
|
+
return new Response(JSON.stringify({ patterns: [], count: 0 }), {
|
|
35
|
+
status: 500,
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ locals, request }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
const patternApi = (locals.runtime?.env as { PATTERN_DISCOVERY_API?: Fetcher } | undefined)?.PATTERN_DISCOVERY_API;
|
|
6
|
+
|
|
7
|
+
if (!db && !patternApi) {
|
|
8
|
+
return new Response(JSON.stringify({ error: 'No database or pattern API available' }), {
|
|
9
|
+
status: 500,
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const body = (await request.json()) as { id: number; notes?: string };
|
|
16
|
+
|
|
17
|
+
if (patternApi) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
20
|
+
const res = await patternApi.fetch(
|
|
21
|
+
`https://internal/suggestions/${body.id}?action=reject`,
|
|
22
|
+
{
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ notes: body.notes }),
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
if (!res.ok) throw new Error(`Pattern API returned ${res.status}`);
|
|
31
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await db!
|
|
37
|
+
.prepare(
|
|
38
|
+
`UPDATE transient_pattern_suggestions
|
|
39
|
+
SET status = 'rejected', reviewed_at = datetime('now'), reviewer_notes = ?
|
|
40
|
+
WHERE id = ?`
|
|
41
|
+
)
|
|
42
|
+
.bind(body.notes ?? null, body.id)
|
|
43
|
+
.run();
|
|
44
|
+
|
|
45
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return new Response(
|
|
50
|
+
JSON.stringify({ error: error instanceof Error ? error.message : 'Failed to reject' }),
|
|
51
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ byStatus: {}, total: 0 }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const counts = await db
|
|
14
|
+
.prepare(
|
|
15
|
+
`SELECT status, COUNT(*) as count
|
|
16
|
+
FROM transient_pattern_suggestions
|
|
17
|
+
GROUP BY status
|
|
18
|
+
LIMIT 10`
|
|
19
|
+
)
|
|
20
|
+
.all<{ status: string; count: number }>();
|
|
21
|
+
|
|
22
|
+
const byStatus: Record<string, number> = {};
|
|
23
|
+
let total = 0;
|
|
24
|
+
for (const row of counts.results ?? []) {
|
|
25
|
+
byStatus[row.status] = row.count;
|
|
26
|
+
total += row.count;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return new Response(
|
|
30
|
+
JSON.stringify({ byStatus, total }),
|
|
31
|
+
{ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=60' } }
|
|
32
|
+
);
|
|
33
|
+
} catch {
|
|
34
|
+
return new Response(JSON.stringify({ byStatus: {}, total: 0 }), {
|
|
35
|
+
status: 500,
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ suggestions: [], total: 0 }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const status = url.searchParams.get('status') ?? 'pending';
|
|
13
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit') ?? '50'), 100);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const suggestions = await db
|
|
17
|
+
.prepare(
|
|
18
|
+
`SELECT id, pattern_type, pattern_value, error_type, priority, status,
|
|
19
|
+
match_count, source, created_at, reviewed_at, reviewer_notes, is_protected
|
|
20
|
+
FROM transient_pattern_suggestions
|
|
21
|
+
WHERE status = ?
|
|
22
|
+
ORDER BY match_count DESC, created_at DESC
|
|
23
|
+
LIMIT ?`
|
|
24
|
+
)
|
|
25
|
+
.bind(status, limit)
|
|
26
|
+
.all();
|
|
27
|
+
|
|
28
|
+
const total = await db
|
|
29
|
+
.prepare(`SELECT COUNT(*) as count FROM transient_pattern_suggestions WHERE status = ? LIMIT 1`)
|
|
30
|
+
.bind(status)
|
|
31
|
+
.first<{ count: number }>();
|
|
32
|
+
|
|
33
|
+
return new Response(
|
|
34
|
+
JSON.stringify({ suggestions: suggestions.results ?? [], total: total?.count ?? 0 }),
|
|
35
|
+
{ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=30' } }
|
|
36
|
+
);
|
|
37
|
+
} catch {
|
|
38
|
+
return new Response(JSON.stringify({ suggestions: [], total: 0 }), {
|
|
39
|
+
status: 500,
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ reports: [] }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const project = url.searchParams.get('project');
|
|
13
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit') ?? '10'), 50);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const query = project
|
|
17
|
+
? `SELECT id, project, scan_type, ai_judge_score, sdk_score, observability_score,
|
|
18
|
+
cost_protection_score, security_score, scan_date, focused_dimensions
|
|
19
|
+
FROM audit_results
|
|
20
|
+
WHERE project = ?
|
|
21
|
+
ORDER BY scan_date DESC
|
|
22
|
+
LIMIT ?`
|
|
23
|
+
: `SELECT id, project, scan_type, ai_judge_score, sdk_score, observability_score,
|
|
24
|
+
cost_protection_score, security_score, scan_date, focused_dimensions
|
|
25
|
+
FROM audit_results
|
|
26
|
+
ORDER BY scan_date DESC
|
|
27
|
+
LIMIT ?`;
|
|
28
|
+
|
|
29
|
+
const stmt = project
|
|
30
|
+
? db.prepare(query).bind(project, limit)
|
|
31
|
+
: db.prepare(query).bind(limit);
|
|
32
|
+
|
|
33
|
+
const reports = await stmt.all();
|
|
34
|
+
|
|
35
|
+
return new Response(
|
|
36
|
+
JSON.stringify({ reports: reports.results ?? [] }),
|
|
37
|
+
{ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=120' } }
|
|
38
|
+
);
|
|
39
|
+
} catch {
|
|
40
|
+
return new Response(JSON.stringify({ reports: [] }), {
|
|
41
|
+
status: 500,
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ daily: [], monthly: [] }), {
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const days = Math.min(parseInt(url.searchParams.get('days') ?? '30'), 90);
|
|
13
|
+
const startDate = new Date();
|
|
14
|
+
startDate.setDate(startDate.getDate() - days);
|
|
15
|
+
const startStr = startDate.toISOString().slice(0, 10);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const [daily, monthly] = await Promise.all([
|
|
19
|
+
db
|
|
20
|
+
.prepare(
|
|
21
|
+
`SELECT snapshot_date, d1_reads, d1_writes, kv_reads, kv_writes,
|
|
22
|
+
r2_reads, r2_writes, worker_requests, total_cost_usd
|
|
23
|
+
FROM daily_usage_rollups
|
|
24
|
+
WHERE project = 'all' AND snapshot_date >= ?
|
|
25
|
+
ORDER BY snapshot_date ASC
|
|
26
|
+
LIMIT 90`
|
|
27
|
+
)
|
|
28
|
+
.bind(startStr)
|
|
29
|
+
.all(),
|
|
30
|
+
db
|
|
31
|
+
.prepare(
|
|
32
|
+
`SELECT snapshot_month, d1_reads, d1_writes, kv_reads, kv_writes,
|
|
33
|
+
r2_reads, r2_writes, worker_requests, total_cost_usd
|
|
34
|
+
FROM monthly_usage_rollups
|
|
35
|
+
WHERE project = 'all'
|
|
36
|
+
ORDER BY snapshot_month DESC
|
|
37
|
+
LIMIT 12`
|
|
38
|
+
)
|
|
39
|
+
.all(),
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
return new Response(
|
|
43
|
+
JSON.stringify({ daily: daily.results ?? [], monthly: monthly.results ?? [] }),
|
|
44
|
+
{ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=300' } }
|
|
45
|
+
);
|
|
46
|
+
} catch {
|
|
47
|
+
return new Response(JSON.stringify({ daily: [], monthly: [] }), {
|
|
48
|
+
status: 500,
|
|
49
|
+
headers: { 'Content-Type': 'application/json' },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
4
|
+
const searchApi = (locals.runtime?.env as { SEARCH_API?: Fetcher } | undefined)?.SEARCH_API;
|
|
5
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
6
|
+
const query = url.searchParams.get('q')?.trim();
|
|
7
|
+
|
|
8
|
+
if (!query || query.length < 2) {
|
|
9
|
+
return new Response(JSON.stringify({ results: [], query: '' }), {
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Prefer search API service binding
|
|
15
|
+
if (searchApi) {
|
|
16
|
+
try {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
19
|
+
const res = await searchApi.fetch(`https://internal/search?q=${encodeURIComponent(query)}`, {
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
});
|
|
22
|
+
clearTimeout(timeout);
|
|
23
|
+
if (res.ok) {
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return new Response(JSON.stringify(data), {
|
|
26
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=30' },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Fall through to D1 fallback
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// D1 fallback: basic LIKE search
|
|
35
|
+
if (!db) {
|
|
36
|
+
return new Response(JSON.stringify({ results: [], query }), {
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const pattern = `%${query}%`;
|
|
43
|
+
const errors = await db
|
|
44
|
+
.prepare(
|
|
45
|
+
`SELECT 'error' as type, fingerprint as id, normalized_message as title, script_name as subtitle
|
|
46
|
+
FROM error_occurrences
|
|
47
|
+
WHERE normalized_message LIKE ? OR script_name LIKE ?
|
|
48
|
+
LIMIT 5`
|
|
49
|
+
)
|
|
50
|
+
.bind(pattern, pattern)
|
|
51
|
+
.all();
|
|
52
|
+
|
|
53
|
+
const notifications = await db
|
|
54
|
+
.prepare(
|
|
55
|
+
`SELECT 'notification' as type, id, title, category as subtitle
|
|
56
|
+
FROM notifications
|
|
57
|
+
WHERE title LIKE ?
|
|
58
|
+
LIMIT 5`
|
|
59
|
+
)
|
|
60
|
+
.bind(pattern)
|
|
61
|
+
.all();
|
|
62
|
+
|
|
63
|
+
const results = [...(errors.results ?? []), ...(notifications.results ?? [])];
|
|
64
|
+
|
|
65
|
+
return new Response(JSON.stringify({ results, query }), {
|
|
66
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=30' },
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
return new Response(JSON.stringify({ results: [], query }), {
|
|
70
|
+
status: 500,
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
export const POST: APIRoute = async ({ locals }) => {
|
|
4
|
+
const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
|
|
5
|
+
|
|
6
|
+
if (!db) {
|
|
7
|
+
return new Response(JSON.stringify({ error: 'Database not available' }), {
|
|
8
|
+
status: 503,
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await db
|
|
15
|
+
.prepare(`INSERT INTO search_index(search_index) VALUES('rebuild')`)
|
|
16
|
+
.run();
|
|
17
|
+
|
|
18
|
+
return new Response(
|
|
19
|
+
JSON.stringify({ ok: true, message: 'FTS5 reindex started' }),
|
|
20
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
21
|
+
);
|
|
22
|
+
} catch {
|
|
23
|
+
return new Response(JSON.stringify({ error: 'Reindex failed' }), {
|
|
24
|
+
status: 500,
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|