mystuff2/prisma/seed.ts
Greg aa0d14b679 Add favorites/library app with Coolify deployment setup
Personal favorites tracker: Next.js 16 App Router, Prisma/PostgreSQL,
NextAuth v5 credentials auth with admin/friend roles, category-driven
custom fields, lending library, optional S3 cover images.

Deployment: multi-stage Dockerfile (standalone output) whose entrypoint
runs prisma migrate deploy and the idempotent seed on every boot; env
validation made lazy so next build works in the secret-free image.
See DEPLOY.md for Coolify setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:20:41 +02:00

163 lines
4.1 KiB
TypeScript

import { PrismaClient } from "@prisma/client";
import bcrypt from "bcryptjs";
const prisma = new PrismaClient();
type FieldDef = {
key: string;
label: string;
type: "text" | "textarea" | "number" | "date" | "url" | "select";
options?: string[];
};
const CATEGORIES: Array<{
key: string;
label: string;
icon: string;
sortOrder: number;
fieldSchema: FieldDef[];
}> = [
{
key: "books",
label: "Books",
icon: "BookOpen",
sortOrder: 1,
fieldSchema: [
{ key: "author", label: "Author", type: "text" },
{ key: "isbn", label: "ISBN", type: "text" },
{ key: "publishedYear", label: "Published year", type: "number" },
{ key: "pageCount", label: "Pages", type: "number" },
],
},
{
key: "movies",
label: "Movies",
icon: "Clapperboard",
sortOrder: 2,
fieldSchema: [
{ key: "director", label: "Director", type: "text" },
{ key: "runtimeMinutes", label: "Runtime (min)", type: "number" },
{ key: "releaseYear", label: "Release year", type: "number" },
],
},
{
key: "tv-series",
label: "TV Series",
icon: "Tv",
sortOrder: 3,
fieldSchema: [
{ key: "creator", label: "Creator", type: "text" },
{ key: "seasons", label: "Seasons", type: "number" },
{ key: "firstAirYear", label: "First aired", type: "number" },
],
},
{
key: "recipes",
label: "Recipes",
icon: "ChefHat",
sortOrder: 4,
fieldSchema: [
{ key: "sourceUrl", label: "Source URL", type: "url" },
{ key: "prepTimeMinutes", label: "Prep time (min)", type: "number" },
{ key: "servings", label: "Servings", type: "number" },
],
},
{
key: "youtube",
label: "YouTube",
icon: "Youtube",
sortOrder: 5,
fieldSchema: [
{ key: "videoUrl", label: "Video URL", type: "url" },
{ key: "channel", label: "Channel", type: "text" },
],
},
{
key: "music",
label: "Music",
icon: "Music",
sortOrder: 6,
fieldSchema: [
{ key: "artist", label: "Artist", type: "text" },
{ key: "album", label: "Album", type: "text" },
{ key: "releaseYear", label: "Release year", type: "number" },
],
},
{
key: "concerts",
label: "Concerts",
icon: "Mic2",
sortOrder: 7,
fieldSchema: [
{ key: "artist", label: "Artist", type: "text" },
{ key: "venue", label: "Venue", type: "text" },
{ key: "eventDate", label: "Date", type: "date" },
],
},
{
key: "restaurants",
label: "Restaurants",
icon: "UtensilsCrossed",
sortOrder: 8,
fieldSchema: [
{ key: "location", label: "Location", type: "text" },
{ key: "cuisine", label: "Cuisine", type: "text" },
{ key: "website", label: "Website", type: "url" },
],
},
{
key: "articles",
label: "Articles & Sites",
icon: "Newspaper",
sortOrder: 9,
fieldSchema: [
{ key: "url", label: "URL", type: "url" },
{ key: "siteName", label: "Site", type: "text" },
],
},
];
async function main() {
for (const cat of CATEGORIES) {
await prisma.category.upsert({
where: { key: cat.key },
update: {}, // never overwrite admin's edits (label, isShared, fieldSchema)
create: cat,
});
}
console.log(`Seeded ${CATEGORIES.length} categories (existing rows untouched).`);
const adminExists = await prisma.user.findFirst({ where: { role: "ADMIN" } });
if (adminExists) {
console.log("Admin user already exists — skipping bootstrap.");
return;
}
const email = process.env.ADMIN_EMAIL;
const password = process.env.ADMIN_PASSWORD;
if (!email || !password) {
console.warn(
"No admin user exists and ADMIN_EMAIL/ADMIN_PASSWORD are not set — skipping admin bootstrap."
);
return;
}
await prisma.user.create({
data: {
email,
passwordHash: await bcrypt.hash(password, 12),
name: "Admin",
role: "ADMIN",
},
});
console.log(`Created admin user ${email}.`);
}
main()
.then(() => prisma.$disconnect())
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});