Switch database from SQLite to PostgreSQL

Replace in-memory JSON file storage with Prisma ORM using PostgreSQL.
Update environment variables and Prisma schema accordingly.
This commit is contained in:
2026-05-19 14:19:01 +02:00
parent 06ab791755
commit 45cf61525e
10 changed files with 79 additions and 60 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
DATABASE_URL="file:./dev.db"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/bikeapp?schema=public"
OLLAMA_URL="http://localhost:11434"
OLLAMA_MODEL="llama3.1:8b"
+3
View File
@@ -0,0 +1,3 @@
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/bikeapp?schema=public"
OLLAMA_URL="http://localhost:11434"
OLLAMA_MODEL="llama3.1:8b"
+31 -1
View File
@@ -1 +1,31 @@
{"scripts":{"dev":"next dev","build":"next build","start":"next start","lint":"eslint src","db:seed":"tsx prisma/seed.ts"},"dependencies":{"@prisma/client":"^5.22.0","next":"^16.2.6","postcss":"^8.5.10","prisma":"^5.22.0","react":"^19.2.1","react-dom":"^19.2.1","sharp":"^0.33.5","zod":"^3.23.8"},"devDependencies":{"@types/node":"^20.17.6","@types/react":"^19.2.7","@types/react-dom":"^19.2.3","eslint":"^9.39.4","eslint-config-next":"^16.2.6","tsx":"^4.19.2","typescript":"^5.6.3"}}
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint src",
"db:seed": "tsx prisma/seed.ts",
"db:migrate": "prisma migrate dev",
"db:studio": "prisma studio",
"postinstall": "prisma generate"
},
"dependencies": {
"@prisma/client": "^5.22.0",
"next": "^16.2.6",
"postcss": "^8.5.10",
"prisma": "^5.22.0",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"sharp": "^0.33.5",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.17.6",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"eslint": "^9.39.4",
"eslint-config-next": "^16.2.6",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ generator client {
}
datasource db {
provider = "sqlite"
provider = "postgresql"
url = env("DATABASE_URL")
}
+1 -1
View File
@@ -26,7 +26,7 @@ export async function POST(request: Request) {
.filter((product): product is Product => Boolean(product))
.map((product) => setupVerification[product.id])
.filter(Boolean);
const prefs = store.preferences();
const prefs = await store.preferences();
const prompt = [
"You are a professional mountain bike suspension and tire setup tuner.",
"Combine manufacturer setup ranges, community setup patterns, and the rider's past preferences.",
+2 -2
View File
@@ -21,7 +21,7 @@ const schema = z.object({
});
export async function GET() {
return NextResponse.json(store.listBuilds());
return NextResponse.json(await store.listBuilds());
}
export async function POST(request: Request) {
@@ -35,7 +35,7 @@ export async function POST(request: Request) {
rearTireId: body.rearTireId,
};
const setup = { ...buildSetupDefaults(selection, body.rider), ...body.setup };
const build = store.createBuild({
const build = await store.createBuild({
name: body.name,
riderWeightKg: body.rider.weight,
discipline: body.rider.discipline,
+1 -1
View File
@@ -19,6 +19,6 @@ const schema = z.object({
export async function POST(request: Request) {
const body = schema.parse(await request.json());
const review = store.createReview(body);
const review = await store.createReview(body);
return NextResponse.json(review, { status: 201 });
}
+31 -53
View File
@@ -1,58 +1,36 @@
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { prisma } from "./prisma";
type Database = {
builds: any[];
reviews: any[];
};
const dbPath = path.join(process.cwd(), "data", "bike-app.json");
function readDb(): Database {
mkdirSync(path.dirname(dbPath), { recursive: true });
try {
return JSON.parse(readFileSync(dbPath, "utf8")) as Database;
} catch {
return { builds: [], reviews: [] };
}
}
function writeDb(data: Database) {
mkdirSync(path.dirname(dbPath), { recursive: true });
writeFileSync(dbPath, JSON.stringify(data, null, 2));
function parseSetup(build: any) {
return {
...build,
setup: build.setup ? JSON.parse(build.setup) : {},
};
}
export const store = {
listBuilds() {
return readDb().builds.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
},
createBuild(input: any) {
const data = readDb();
const build = {
id: crypto.randomUUID(),
...input,
createdAt: new Date().toISOString(),
};
data.builds.unshift(build);
writeDb(data);
return build;
},
createReview(input: any) {
const data = readDb();
const review = {
id: crypto.randomUUID(),
...input,
createdAt: new Date().toISOString(),
};
data.reviews.unshift(review);
writeDb(data);
return review;
},
preferences() {
const reviews = readDb().reviews;
return {
harshTendency: reviews.filter((review) => review.suspFeel >= 7).length,
softTendency: reviews.filter((review) => review.suspFeel <= 3).length,
};
},
async listBuilds() {
const builds = await prisma.bikeBuild.findMany({
orderBy: { createdAt: "desc" },
});
return builds.map(parseSetup);
},
async createBuild(input: any) {
const build = await prisma.bikeBuild.create({
data: {
...input,
setup: JSON.stringify(input.setup ?? {}),
},
});
return parseSetup(build);
},
async createReview(input: any) {
return prisma.rideReview.create({ data: input });
},
async preferences() {
const reviews = await prisma.rideReview.findMany();
return {
harshTendency: reviews.filter((review) => review.suspFeel >= 7).length,
softTendency: reviews.filter((review) => review.suspFeel <= 3).length,
};
},
};
+7
View File
@@ -0,0 +1,7 @@
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
File diff suppressed because one or more lines are too long