Files
agentic-coding-starter-kit/create-agentic-app/template/src/app/api/diagnostics/route.ts
Leon van Zyl a3a151c67a feat: comprehensive boilerplate improvements
Security & Stability:
- Add Next.js 16 proxy.ts for BetterAuth cookie-based auth protection
- Add rate limiting for API routes (src/lib/rate-limit.ts)
- Add Zod validation for chat API request bodies
- Add session auth check to chat and diagnostics endpoints
- Add security headers in next.config.ts (CSP, X-Frame-Options, etc.)
- Add file upload validation and sanitization in storage.ts

Core UX Components:
- Add error boundaries (error.tsx, not-found.tsx, chat/error.tsx)
- Add loading states (skeleton.tsx, spinner.tsx, loading.tsx files)
- Add toast notifications with Sonner
- Add form components (input.tsx, textarea.tsx, label.tsx)
- Add database indexes for performance (schema.ts)
- Enhance chat UX: timestamps, copy-to-clipboard, thinking indicator,
  error display, localStorage message persistence

Polish & Accessibility:
- Add Open Graph and Twitter card metadata
- Add JSON-LD structured data for SEO
- Add sitemap.ts, robots.ts, manifest.ts
- Add skip-to-content link and ARIA labels in site-header
- Enable profile page quick action buttons with dialogs
- Update Next.js 15 references to Next.js 16

Developer Experience:
- Add GitHub Actions CI workflow (lint, typecheck, build)
- Add Prettier configuration (.prettierrc, .prettierignore)
- Add .nvmrc pinning Node 20
- Add ESLint rules: import/order, react-hooks/exhaustive-deps
- Add stricter TypeScript settings (exactOptionalPropertyTypes,
  noImplicitOverride)
- Add interactive setup script (scripts/setup.ts)
- Add session utility functions (src/lib/session.ts)

All changes mirrored to create-agentic-app/template/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 14:46:15 +02:00

170 lines
4.9 KiB
TypeScript

import { headers } from "next/headers";
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
type StatusLevel = "ok" | "warn" | "error";
interface DiagnosticsResponse {
timestamp: string;
env: {
POSTGRES_URL: boolean;
BETTER_AUTH_SECRET: boolean;
GOOGLE_CLIENT_ID: boolean;
GOOGLE_CLIENT_SECRET: boolean;
OPENROUTER_API_KEY: boolean;
NEXT_PUBLIC_APP_URL: boolean;
};
database: {
connected: boolean;
schemaApplied: boolean;
error?: string;
};
auth: {
configured: boolean;
routeResponding: boolean | null;
};
ai: {
configured: boolean;
};
storage: {
configured: boolean;
type: "local" | "remote";
};
overallStatus: StatusLevel;
}
export async function GET(req: Request) {
// Require authentication for diagnostics endpoint
const session = await auth.api.getSession({ headers: await headers() });
if (!session) {
return NextResponse.json(
{ error: "Unauthorized. Please sign in to access diagnostics." },
{ status: 401 }
);
}
const env = {
POSTGRES_URL: Boolean(process.env.POSTGRES_URL),
BETTER_AUTH_SECRET: Boolean(process.env.BETTER_AUTH_SECRET),
GOOGLE_CLIENT_ID: Boolean(process.env.GOOGLE_CLIENT_ID),
GOOGLE_CLIENT_SECRET: Boolean(process.env.GOOGLE_CLIENT_SECRET),
OPENROUTER_API_KEY: Boolean(process.env.OPENROUTER_API_KEY),
NEXT_PUBLIC_APP_URL: Boolean(process.env.NEXT_PUBLIC_APP_URL),
} as const;
// Database checks with timeout
let dbConnected = false;
let schemaApplied = false;
let dbError: string | undefined;
if (env.POSTGRES_URL) {
try {
// Add timeout to prevent hanging on unreachable database
const dbCheckPromise = (async () => {
const [{ db }, { sql }, schema] = await Promise.all([
import("@/lib/db"),
import("drizzle-orm"),
import("@/lib/schema"),
]);
// Ping DB - this will actually attempt to connect
const result = await db.execute(sql`SELECT 1 as ping`);
if (!result) {
throw new Error("Database query returned no result");
}
dbConnected = true;
try {
// Touch a known table to verify migrations
await db.select().from(schema.user).limit(1);
schemaApplied = true;
} catch {
schemaApplied = false;
// If we can't query the user table, it's likely migrations haven't run
if (!dbError) {
dbError = "Schema not applied. Run: npm run db:migrate";
}
}
})();
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Database connection timeout (5s)")), 5000)
);
await Promise.race([dbCheckPromise, timeoutPromise]);
} catch {
dbConnected = false;
schemaApplied = false;
// Provide user-friendly error messages
dbError = "Database not connected. Please start your PostgreSQL database and verify your POSTGRES_URL in .env";
}
} else {
dbConnected = false;
schemaApplied = false;
dbError = "POSTGRES_URL is not set";
}
// Auth route check: we consider the route responding if it returns any HTTP response
// for /api/auth/session (status codes in the 2xx-4xx range are acceptable for readiness)
const origin = (() => {
try {
return new URL(req.url).origin;
} catch {
return process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
}
})();
let authRouteResponding: boolean | null = null;
try {
const res = await fetch(`${origin}/api/auth/session`, {
method: "GET",
headers: { Accept: "application/json" },
cache: "no-store",
});
authRouteResponding = res.status >= 200 && res.status < 500;
} catch {
authRouteResponding = false;
}
const authConfigured =
env.BETTER_AUTH_SECRET && env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET;
const aiConfigured = env.OPENROUTER_API_KEY; // We avoid live-calling the AI provider here
// Storage configuration check
const storageConfigured = Boolean(process.env.BLOB_READ_WRITE_TOKEN);
const storageType: "local" | "remote" = storageConfigured ? "remote" : "local";
const overallStatus: StatusLevel = (() => {
if (!env.POSTGRES_URL || !dbConnected || !schemaApplied) return "error";
if (!authConfigured) return "error";
// AI is optional; warn if not configured
if (!aiConfigured) return "warn";
return "ok";
})();
const body: DiagnosticsResponse = {
timestamp: new Date().toISOString(),
env,
database: {
connected: dbConnected,
schemaApplied,
...(dbError !== undefined && { error: dbError }),
},
auth: {
configured: authConfigured,
routeResponding: authRouteResponding,
},
ai: {
configured: aiConfigured,
},
storage: {
configured: storageConfigured,
type: storageType,
},
overallStatus,
};
return NextResponse.json(body, {
status: 200,
});
}