import { PrismaClient, PromptType, StructuredFormat, RequiredMediaType } from "@prisma/client"; import bcrypt from "bcryptjs"; const prisma = new PrismaClient(); const PROMPTS_JSON_URL = "https://prompts.chat/prompts.json?full_content=true"; /** Raw shape returned by the remote API — content may be absent. */ interface RemotePromptRaw { id: string; title: string; slug: string; description: string | null; content?: string; type: string; structuredFormat: string | null; mediaUrl: string | null; viewCount: number; voteCount: number; commentCount: number; isFeatured: boolean; featuredAt: string | null; requiresMediaUpload: boolean; requiredMediaType: string | null; requiredMediaCount: number | null; createdAt: string; updatedAt: string; category: { id: string; name: string; slug: string; icon: string | null; } | null; author: { username: string; name: string | null; avatar: string | null; identifier: string; verified: boolean; }; contributors: Array<{ username: string; name: string | null; avatar: string | null; identifier: string; verified: boolean; }>; tags: Array<{ id: string; name: string; slug: string; color: string; }>; } /** Validated prompt with required content field. */ type RemotePrompt = RemotePromptRaw & { content: string }; /** Type guard that narrows a raw API prompt to a validated RemotePrompt. */ function hasContent(prompt: RemotePromptRaw): prompt is RemotePrompt { return typeof prompt.content === "string" && prompt.content.length > 0; } interface RemotePromptsResponse { count: number; prompts: RemotePromptRaw[]; } /** Fetches all prompts from the remote prompts.chat API with full content. */ async function fetchPrompts(): Promise { console.log(`šŸ“” Fetching prompts from ${PROMPTS_JSON_URL}...`); const response = await fetch(PROMPTS_JSON_URL); if (!response.ok) { throw new Error(`Failed to fetch prompts: ${response.status} ${response.statusText}`); } const data = await response.json(); console.log(`āœ… Fetched ${data.count} prompts`); return data; } /** Seeds the database with users, categories, tags, and prompts from prompts.chat. */ async function main() { console.log("🌱 Seeding database from prompts.chat..."); // Create admin user for assigning prompts const password = await bcrypt.hash("password123", 12); const admin = await prisma.user.upsert({ where: { email: "admin@prompts.chat" }, update: {}, create: { email: "admin@prompts.chat", username: "admin", name: "Admin User", password: password, role: "ADMIN", locale: "en", }, }); console.log("āœ… Created admin user"); // Fetch prompts from remote JSON const { prompts: remotePrompts } = await fetchPrompts(); // Extract unique categories from remote prompts const categoryMap = new Map(); for (const prompt of remotePrompts) { if (prompt.category) { categoryMap.set(prompt.category.slug, { name: prompt.category.name, slug: prompt.category.slug, icon: prompt.category.icon, }); } } // Create categories console.log(`šŸ“ Creating ${categoryMap.size} categories...`); const categoryIdMap = new Map(); let categoryOrder = 1; for (const [slug, cat] of categoryMap) { const category = await prisma.category.upsert({ where: { slug }, update: { name: cat.name, icon: cat.icon }, create: { name: cat.name, slug: cat.slug, icon: cat.icon, order: categoryOrder++, }, }); categoryIdMap.set(slug, category.id); } console.log(`āœ… Created ${categoryMap.size} categories`); // Extract unique tags from remote prompts const tagMap = new Map(); for (const prompt of remotePrompts) { for (const tag of prompt.tags) { tagMap.set(tag.slug, { name: tag.name, slug: tag.slug, color: tag.color, }); } } // Create tags console.log(`šŸ·ļø Creating ${tagMap.size} tags...`); const tagIdMap = new Map(); for (const [slug, tag] of tagMap) { const createdTag = await prisma.tag.upsert({ where: { slug }, update: { name: tag.name, color: tag.color }, create: { name: tag.name, slug: tag.slug, color: tag.color, }, }); tagIdMap.set(slug, createdTag.id); } console.log(`āœ… Created ${tagMap.size} tags`); // Extract unique authors from remote prompts and create users const authorMap = new Map(); for (const prompt of remotePrompts) { if (!authorMap.has(prompt.author.username)) { authorMap.set(prompt.author.username, { username: prompt.author.username, name: prompt.author.name, avatar: prompt.author.avatar, verified: prompt.author.verified, }); } } // Create users for authors console.log(`šŸ‘¤ Creating ${authorMap.size} users...`); const userIdMap = new Map(); for (const [username, author] of authorMap) { // Skip creating if it's the admin if (username === "admin") { userIdMap.set(username, admin.id); continue; } const user = await prisma.user.upsert({ where: { username }, update: { name: author.name, avatar: author.avatar }, create: { email: `${username}@prompts.chat`, username: author.username, name: author.name, avatar: author.avatar, password: password, role: "USER", locale: "en", }, }); userIdMap.set(username, user.id); } console.log(`āœ… Created ${authorMap.size} users`); // Create prompts console.log(`šŸ“ Creating ${remotePrompts.length} prompts...`); let promptsCreated = 0; let promptsSkipped = 0; for (const remotePrompt of remotePrompts) { const authorId = userIdMap.get(remotePrompt.author.username); if (!authorId) { console.warn(`āš ļø Skipping prompt "${remotePrompt.title}" - author not found`); promptsSkipped++; continue; } const categoryId = remotePrompt.category ? categoryIdMap.get(remotePrompt.category.slug) : null; // Map prompt type const typeMap: Record = { TEXT: "TEXT", IMAGE: "IMAGE", VIDEO: "VIDEO", AUDIO: "AUDIO", STRUCTURED: "STRUCTURED", SKILL: "SKILL", }; const promptType = typeMap[remotePrompt.type] || "TEXT"; // Map structured format const formatMap: Record = { JSON: "JSON", YAML: "YAML", }; const structuredFormat = remotePrompt.structuredFormat ? formatMap[remotePrompt.structuredFormat] : null; // Skip prompts without content if (!hasContent(remotePrompt)) { console.warn(`⚠ Skipping prompt "${remotePrompt.title}" - no content`); promptsSkipped++; continue; } // Check if prompt already exists const existingPrompt = await prisma.prompt.findFirst({ where: { slug: remotePrompt.slug }, }); if (existingPrompt) { promptsSkipped++; continue; } try { const prompt = await prisma.prompt.create({ data: { title: remotePrompt.title, slug: remotePrompt.slug, description: remotePrompt.description, content: remotePrompt.content, type: promptType, structuredFormat: structuredFormat, mediaUrl: remotePrompt.mediaUrl, viewCount: remotePrompt.viewCount, isFeatured: remotePrompt.isFeatured, featuredAt: remotePrompt.featuredAt ? new Date(remotePrompt.featuredAt) : null, requiresMediaUpload: remotePrompt.requiresMediaUpload, requiredMediaType: remotePrompt.requiredMediaType as RequiredMediaType | null, requiredMediaCount: remotePrompt.requiredMediaCount, authorId: authorId, categoryId: categoryId, tags: { create: remotePrompt.tags .filter(tag => tagIdMap.has(tag.slug)) .map(tag => ({ tagId: tagIdMap.get(tag.slug)! })), }, }, }); // Create initial version await prisma.promptVersion.create({ data: { promptId: prompt.id, version: 1, content: remotePrompt.content, changeNote: "Initial version", createdBy: authorId, }, }); promptsCreated++; } catch (error) { console.warn(`āš ļø Failed to create prompt "${remotePrompt.title}":`, error); promptsSkipped++; } } console.log(`āœ… Created ${promptsCreated} prompts (${promptsSkipped} skipped)`); // Fail-fast if no prompts were created — likely an upstream API regression const total = promptsCreated + promptsSkipped; if (total > 0 && promptsCreated === 0) { throw new Error( `Seeding failed: 0 prompts created out of ${total} (${promptsSkipped} skipped). ` + `This likely indicates an upstream API regression — check that ${PROMPTS_JSON_URL} returns content.` ); } console.log("\nšŸŽ‰ Seeding complete!"); console.log("\nšŸ“‹ Test credentials (password: password123):"); console.log(" Admin: admin@prompts.chat"); } main() .catch((e) => { console.error("āŒ Seeding failed:", e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });