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); });