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:
+1
-1
@@ -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_URL="http://localhost:11434"
|
||||||
OLLAMA_MODEL="llama3.1:8b"
|
OLLAMA_MODEL="llama3.1:8b"
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "postgresql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export async function POST(request: Request) {
|
|||||||
.filter((product): product is Product => Boolean(product))
|
.filter((product): product is Product => Boolean(product))
|
||||||
.map((product) => setupVerification[product.id])
|
.map((product) => setupVerification[product.id])
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
const prefs = store.preferences();
|
const prefs = await store.preferences();
|
||||||
const prompt = [
|
const prompt = [
|
||||||
"You are a professional mountain bike suspension and tire setup tuner.",
|
"You are a professional mountain bike suspension and tire setup tuner.",
|
||||||
"Combine manufacturer setup ranges, community setup patterns, and the rider's past preferences.",
|
"Combine manufacturer setup ranges, community setup patterns, and the rider's past preferences.",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const schema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
return NextResponse.json(store.listBuilds());
|
return NextResponse.json(await store.listBuilds());
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
@@ -35,7 +35,7 @@ export async function POST(request: Request) {
|
|||||||
rearTireId: body.rearTireId,
|
rearTireId: body.rearTireId,
|
||||||
};
|
};
|
||||||
const setup = { ...buildSetupDefaults(selection, body.rider), ...body.setup };
|
const setup = { ...buildSetupDefaults(selection, body.rider), ...body.setup };
|
||||||
const build = store.createBuild({
|
const build = await store.createBuild({
|
||||||
name: body.name,
|
name: body.name,
|
||||||
riderWeightKg: body.rider.weight,
|
riderWeightKg: body.rider.weight,
|
||||||
discipline: body.rider.discipline,
|
discipline: body.rider.discipline,
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ const schema = z.object({
|
|||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const body = schema.parse(await request.json());
|
const body = schema.parse(await request.json());
|
||||||
const review = store.createReview(body);
|
const review = await store.createReview(body);
|
||||||
return NextResponse.json(review, { status: 201 });
|
return NextResponse.json(review, { status: 201 });
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-53
@@ -1,58 +1,36 @@
|
|||||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
import { prisma } from "./prisma";
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
type Database = {
|
function parseSetup(build: any) {
|
||||||
builds: any[];
|
return {
|
||||||
reviews: any[];
|
...build,
|
||||||
};
|
setup: build.setup ? JSON.parse(build.setup) : {},
|
||||||
|
};
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const store = {
|
export const store = {
|
||||||
listBuilds() {
|
async listBuilds() {
|
||||||
return readDb().builds.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
|
const builds = await prisma.bikeBuild.findMany({
|
||||||
},
|
orderBy: { createdAt: "desc" },
|
||||||
createBuild(input: any) {
|
});
|
||||||
const data = readDb();
|
return builds.map(parseSetup);
|
||||||
const build = {
|
},
|
||||||
id: crypto.randomUUID(),
|
async createBuild(input: any) {
|
||||||
...input,
|
const build = await prisma.bikeBuild.create({
|
||||||
createdAt: new Date().toISOString(),
|
data: {
|
||||||
};
|
...input,
|
||||||
data.builds.unshift(build);
|
setup: JSON.stringify(input.setup ?? {}),
|
||||||
writeDb(data);
|
},
|
||||||
return build;
|
});
|
||||||
},
|
return parseSetup(build);
|
||||||
createReview(input: any) {
|
},
|
||||||
const data = readDb();
|
async createReview(input: any) {
|
||||||
const review = {
|
return prisma.rideReview.create({ data: input });
|
||||||
id: crypto.randomUUID(),
|
},
|
||||||
...input,
|
async preferences() {
|
||||||
createdAt: new Date().toISOString(),
|
const reviews = await prisma.rideReview.findMany();
|
||||||
};
|
return {
|
||||||
data.reviews.unshift(review);
|
harshTendency: reviews.filter((review) => review.suspFeel >= 7).length,
|
||||||
writeDb(data);
|
softTendency: reviews.filter((review) => review.suspFeel <= 3).length,
|
||||||
return review;
|
};
|
||||||
},
|
},
|
||||||
preferences() {
|
|
||||||
const reviews = readDb().reviews;
|
|
||||||
return {
|
|
||||||
harshTendency: reviews.filter((review) => review.suspFeel >= 7).length,
|
|
||||||
softTendency: reviews.filter((review) => review.suspFeel <= 3).length,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user