Make SQLite database self-create on serverless hosts
Vercel's serverless functions only allow writes to /tmp, and it starts empty on every cold start. Point the database there in production and create the User table on first request if it doesn't exist yet, so the app can be deployed for a live preview without manual database setup. Note: this is a demo-friendly stand-in — data does not persist across cold starts on Vercel. A real persistent database is a later phase. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VL5YUEf2B9LG4xdVUQwcSg
This commit is contained in:
7 files changed
+63
-6
No files matched your search
@@ -0,0 +1,9 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||||
|
||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { z } from "zod";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { prisma, ensureDb } from "@/lib/prisma";
|
||||
|
||||
const updateSchema = z.object({
|
||||
bio: z.string().trim().max(500).optional(),
|
||||
@@ -10,6 +10,8 @@ const updateSchema = z.object({
|
||||
});
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
await ensureDb();
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Not signed in" }, { status: 401 });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { prisma, ensureDb } from "@/lib/prisma";
|
||||
|
||||
const signupSchema = z.object({
|
||||
name: z.string().trim().min(1, "Name is required").max(80),
|
||||
@@ -11,6 +11,8 @@ const signupSchema = z.object({
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
await ensureDb();
|
||||
|
||||
const body = await request.json().catch(() => null);
|
||||
if (!body) {
|
||||
return NextResponse.json({ error: "Invalid request body" }, { status: 400 });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { prisma, ensureDb } from "@/lib/prisma";
|
||||
import ProfileEditor from "./profile-editor";
|
||||
import SignOutButton from "./sign-out-button";
|
||||
|
||||
@@ -11,6 +11,8 @@ export default async function ProfilePage() {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
await ensureDb();
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
select: { id: true, name: true, email: true, age: true, bio: true },
|
||||
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
import type { AuthOptions } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { prisma, ensureDb } from "@/lib/prisma";
|
||||
|
||||
export const authOptions: AuthOptions = {
|
||||
session: { strategy: "jwt" },
|
||||
@@ -18,6 +18,8 @@ export const authOptions: AuthOptions = {
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) return null;
|
||||
|
||||
await ensureDb();
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email.toLowerCase() },
|
||||
});
|
||||
|
||||
+41
-2
@@ -1,9 +1,48 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma?: PrismaClient;
|
||||
dbReady?: Promise<void>;
|
||||
};
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
||||
// On Vercel's serverless functions, only /tmp is writable, and it starts
|
||||
// empty on every cold start. Point SQLite there instead of the repo-relative
|
||||
// path used for local development. This is a demo-friendly stand-in until a
|
||||
// real hosted database is wired up (data does not persist across cold starts).
|
||||
const databaseUrl = process.env.VERCEL ? "file:/tmp/dev.db" : process.env.DATABASE_URL;
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ??
|
||||
new PrismaClient({
|
||||
datasources: { db: { url: databaseUrl } },
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForPrisma.prisma = prisma;
|
||||
}
|
||||
|
||||
const CREATE_USER_TABLE = `
|
||||
CREATE TABLE IF NOT EXISTS "User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"email" TEXT NOT NULL,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"age" INTEGER NOT NULL,
|
||||
"bio" TEXT NOT NULL DEFAULT '',
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
`;
|
||||
const CREATE_USER_EMAIL_INDEX = `CREATE UNIQUE INDEX IF NOT EXISTS "User_email_key" ON "User"("email");`;
|
||||
|
||||
// Idempotent, so it's safe to call before every request. Needed because on
|
||||
// Vercel the /tmp SQLite file is missing entirely on a fresh cold start.
|
||||
export async function ensureDb(): Promise<void> {
|
||||
if (!globalForPrisma.dbReady) {
|
||||
globalForPrisma.dbReady = prisma
|
||||
.$executeRawUnsafe(CREATE_USER_TABLE)
|
||||
.then(() => prisma.$executeRawUnsafe(CREATE_USER_EMAIL_INDEX))
|
||||
.then(() => undefined);
|
||||
}
|
||||
await globalForPrisma.dbReady;
|
||||
}
|
||||
Reference in new issue
Block a user