generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } model User { id String @id @default(cuid()) email String @unique name String? username String? @unique image String? bio String? rank String? @default("bronze") // bronze, silver, gold, verified totalRuns Int @default(0) totalStars Int @default(0) totalRemixes Int @default(0) socialLinks Json? // Notification preferences notifyEmail Boolean @default(true) notifyStars Boolean @default(true) notifyRemixes Boolean @default(true) // Stripe / Billing stripeCustomerId String? @unique stripePlan String @default("free") // free, pro stripeSubscriptionId String? @unique stripeSubscriptionStatus String? // active, canceled, past_due, etc. createdAt DateTime @default(now()) updatedAt DateTime @updatedAt prompts Prompt[] stars Star[] runs Run[] collections Collection[] comments Comment[] commentLikes CommentLike[] imagePrompts ImagePrompt[] imageVotes ImagePromptVote[] characters Character[] characterVotes CharacterVote[] workflows Workflow[] @relation("WorkflowCreator") forumPosts ForumPost[] forumReplies ForumReply[] notifications Notification[] @relation("NotificationReceiver") followers Follow[] @relation("Following") following Follow[] @relation("Follower") subscription Subscription? purchases PromptPurchase[] } model Prompt { id String @id @default(cuid()) slug String @unique title String description String? template String @db.Text schema Json @default("{\"variables\":[]}") category String? tags String[] visibility String @default("public") // public, unlisted, private modelDefault String @default("gpt-4o-mini") modelAllowed String[] @default(["gpt-4o-mini", "gpt-4o", "claude-3-5-sonnet"]) maxTokens Int @default(2048) totalRuns Int @default(0) starsCount Int @default(0) remixesCount Int @default(0) // Tier 3 fields framework String? // RACE, CARE, APE, etc. badges String[] @default([]) // Auto-calculated quality badges // Version tracking currentVersion Int @default(1) // Premium marketplace isPremium Boolean @default(false) // Is this a paid prompt? price Int? // Price in cents (e.g., 299 = $2.99) creatorId String? creator User? @relation(fields: [creatorId], references: [id]) parentId String? parent Prompt? @relation("Remixes", fields: [parentId], references: [id]) remixes Prompt[] @relation("Remixes") stars Star[] runs Run[] savedRuns SavedPromptRun[] collections CollectionPrompt[] comments Comment[] versions PromptVersion[] purchases PromptPurchase[] @relation("PremiumPurchases") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([slug]) @@index([creatorId]) @@index([category]) @@index([visibility]) } model Star { userId String promptId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@id([userId, promptId]) } model Run { id String @id @default(cuid()) promptId String userId String? model String tokens Int? cached Boolean @default(false) ipHash String? prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) @@index([promptId]) @@index([userId]) } model Collection { id String @id @default(cuid()) name String description String? userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) prompts CollectionPrompt[] isPublic Boolean @default(false) viewCount Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([isPublic]) } model CollectionPrompt { collectionId String promptId String collection Collection @relation(fields: [collectionId], references: [id], onDelete: Cascade) prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade) addedAt DateTime @default(now()) @@id([collectionId, promptId]) @@index([collectionId]) @@index([promptId]) } model Workflow { id String @id @default(cuid()) slug String @unique name String description String? steps Json // Array of workflow steps variables Json? // Input variables for the workflow visibility String @default("private") // public, unlisted, private totalRuns Int @default(0) starsCount Int @default(0) creatorId String? creator User? @relation("WorkflowCreator", fields: [creatorId], references: [id]) runs WorkflowRun[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([slug]) @@index([creatorId]) @@index([visibility]) } model WorkflowRun { id String @id @default(cuid()) workflowId String workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) userId String? // Who ran it name String? // Optional name for the run (user can name it) variables Json? // The variable values used for this run outputs Json // Array of step outputs model String // Model used for the run createdAt DateTime @default(now()) @@index([workflowId]) @@index([userId]) } // Saved prompt execution results model SavedPromptRun { id String @id @default(cuid()) promptId String prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade) userId String? name String? variables Json? // Variable values used output String @db.Text // The AI output model String createdAt DateTime @default(now()) @@index([promptId]) @@index([userId]) } // Saved AI tool execution results model SavedToolRun { id String @id @default(cuid()) toolSlug String // Tool identifier (no relation, tools are static) userId String? name String? inputs Json? // Input values used output String @db.Text // The AI output model String createdAt DateTime @default(now()) @@index([toolSlug]) @@index([userId]) } // Saved model comparison results (Thunderdome / Performance) model SavedComparison { id String @id @default(cuid()) type String // "thunderdome" or "performance" userId String? name String? prompt String @db.Text // The prompt used results Json // Array of model results with outputs winner String? // Winning model (for thunderdome) createdAt DateTime @default(now()) @@index([userId]) @@index([type]) } // ============================================ // NEW FEATURES - December 2025 // ============================================ // Comments on prompts (threaded discussions) model Comment { id String @id @default(cuid()) content String @db.Text promptId String prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) parentId String? // For threaded replies parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade) replies Comment[] @relation("CommentReplies") // Per-user like tracking (replaces plain Int to prevent spam) commentLikes CommentLike[] likesCount Int @default(0) // Denormalized count for quick display createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([promptId]) @@index([userId]) @@index([parentId]) } // Per-user comment likes — prevents duplicate/spam likes model CommentLike { commentId String comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@id([commentId, userId]) @@index([commentId]) @@index([userId]) } // Image Generation Prompts (Midjourney, DALL-E, Stable Diffusion, FLUX) model ImagePrompt { id String @id @default(cuid()) slug String @unique title String description String? // The actual prompt text prompt String @db.Text negativePrompt String? @db.Text // For SD/FLUX // Target model model String // midjourney, dalle, stable-diffusion, flux, leonardo modelVersion String? // v6.1, SDXL, FLUX.1-dev, etc. // Image settings aspectRatio String? // 16:9, 1:1, 9:16, etc. style String? // photorealistic, anime, cartoon, etc. // Generated image preview (URL) previewImage String? gallery String[] @default([]) // Multiple generated images // Metadata category String? // portrait, landscape, abstract, etc. tags String[] visibility String @default("public") // Stats totalUses Int @default(0) votesCount Int @default(0) savesCount Int @default(0) creatorId String? creator User? @relation(fields: [creatorId], references: [id]) votes ImagePromptVote[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([slug]) @@index([model]) @@index([creatorId]) @@index([category]) } // Votes for image prompts model ImagePromptVote { id String @id @default(cuid()) imagePromptId String imagePrompt ImagePrompt @relation(fields: [imagePromptId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) value Int @default(1) // 1 for upvote, -1 for downvote createdAt DateTime @default(now()) @@unique([imagePromptId, userId]) @@index([imagePromptId]) @@index([userId]) } // Character/Persona System model Character { id String @id @default(cuid()) slug String @unique name String description String? @db.Text // Persona definition personality String @db.Text // Character's personality traits background String? @db.Text // Character's backstory systemPrompt String @db.Text // The actual system prompt // Visual representation avatar String? // URL to avatar image style String? // anime, realistic, cartoon, etc. // Metadata category String? // assistant, roleplay, educational, etc. tags String[] visibility String @default("public") // Conversation settings responseStyle String? // formal, casual, playful, etc. temperature Float @default(0.7) // Stats totalChats Int @default(0) votesCount Int @default(0) savesCount Int @default(0) creatorId String? creator User? @relation(fields: [creatorId], references: [id]) votes CharacterVote[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([slug]) @@index([creatorId]) @@index([category]) } // Votes for characters model CharacterVote { id String @id @default(cuid()) characterId String character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) value Int @default(1) createdAt DateTime @default(now()) @@unique([characterId, userId]) @@index([characterId]) @@index([userId]) } // Engagement metrics tracking model EngagementMetric { id String @id @default(cuid()) // What was engaged with targetType String // prompt, imagePrompt, character, workflow targetId String // Type of engagement action String // view, use, save, share, copy userId String? // Null for anonymous ipHash String? // For anonymous tracking createdAt DateTime @default(now()) @@index([targetType, targetId]) @@index([action]) @@index([createdAt]) } // Extension Analytics - tracks prompt usage from extension model ExtensionAnalytics { id String @id @default(cuid()) // User tracking userId String? // Null for anonymous users deviceId String // Unique device identifier from extension // What action was taken action String // inject, capture, broadcast, shortcut_use, context_menu // Platform info platform String // chatgpt, claude, gemini, etc. platforms String[] @default([]) // For broadcast (multiple platforms) // Prompt info (optional - for inject/capture) promptId String? // If from OpenPrompt library promptTitle String? promptText String? @db.Text // For captured prompts // Source info source String @default("extension") // extension, website // Metadata metadata Json? // Additional data like shortcut used, etc. createdAt DateTime @default(now()) @@index([userId]) @@index([deviceId]) @@index([action]) @@index([platform]) @@index([createdAt]) } // Captured prompts from AI chats (saved by users via extension) model CapturedPrompt { id String @id @default(cuid()) userId String? deviceId String title String? text String @db.Text platform String // Where it was captured from url String? // Original URL // If saved to library savedToLibrary Boolean @default(false) promptId String? // If converted to a Prompt tags String[] @default([]) createdAt DateTime @default(now()) @@index([userId]) @@index([deviceId]) @@index([platform]) @@index([createdAt]) } // ============================================ // COMMUNITY FEATURES - December 2025 // ============================================ // Forum Posts (Community Q&A) model ForumPost { id String @id @default(cuid()) title String content String @db.Text category String // general, help, showcase, feature-request, tips userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) replies ForumReply[] views Int @default(0) likes Int @default(0) isPinned Boolean @default(false) isClosed Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([category]) @@index([createdAt]) } // Forum Replies model ForumReply { id String @id @default(cuid()) content String @db.Text postId String post ForumPost @relation(fields: [postId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) parentId String? // For nested replies likes Int @default(0) isAccepted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([postId]) @@index([userId]) @@index([parentId]) } // User Conversations (Messaging) model Conversation { id String @id @default(cuid()) participant1 String participant2 String messages Message[] lastMessage String? @db.Text lastActivity DateTime @default(now()) createdAt DateTime @default(now()) @@unique([participant1, participant2]) @@index([participant1]) @@index([participant2]) @@index([lastActivity]) } // Direct Messages model Message { id String @id @default(cuid()) content String @db.Text senderId String conversationId String conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) isRead Boolean @default(false) createdAt DateTime @default(now()) @@index([conversationId]) @@index([senderId]) @@index([createdAt]) } // Character Memory (Persistent context) model CharacterMemory { id String @id @default(cuid()) characterId String userId String contextSummary String? @db.Text recentMessages Json @default("[]") userFacts Json @default("[]") messageCount Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([characterId, userId]) @@index([characterId]) @@index([userId]) } // ============================================ // NEW FEATURES - Follow & Notifications // ============================================ // Follow system for creators model Follow { id String @id @default(cuid()) followerId String follower User @relation("Follower", fields: [followerId], references: [id], onDelete: Cascade) followingId String following User @relation("Following", fields: [followingId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([followerId, followingId]) @@index([followerId]) @@index([followingId]) } // Notification system model Notification { id String @id @default(cuid()) userId String // Who receives the notification user User @relation("NotificationReceiver", fields: [userId], references: [id], onDelete: Cascade) type String // star, remix, comment, follow, mention title String message String? // What triggered the notification actorId String? // Who performed the action targetType String? // prompt, workflow, comment, etc. targetId String? targetSlug String? // For linking isRead Boolean @default(false) createdAt DateTime @default(now()) @@index([userId, isRead]) @@index([userId, createdAt]) } // Prompt version history model PromptVersion { id String @id @default(cuid()) promptId String prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade) version Int @default(1) title String template String @db.Text schema Json @default("{\"variables\":[]}") changeNote String? // What changed createdAt DateTime @default(now()) @@index([promptId]) @@index([promptId, version]) } // ============================================ // BILLING — Stripe Integration // ============================================ // Active subscription record (one per user) model Subscription { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) // Stripe identifiers stripeCustomerId String @unique stripeSubscriptionId String @unique stripePriceId String // Plan info plan String @default("pro") // pro, enterprise status String // active, canceled, past_due, trialing, unpaid // Billing cycle currentPeriodStart DateTime currentPeriodEnd DateTime cancelAtPeriodEnd Boolean @default(false) canceledAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([stripeSubscriptionId]) } // Records when a user purchases a premium prompt model PromptPurchase { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) promptId String prompt Prompt @relation("PremiumPurchases", fields: [promptId], references: [id], onDelete: Cascade) // Payment info amount Int // in cents currency String @default("usd") stripePaymentIntentId String @unique // Creator payout (80% to creator) creatorPayout Int // 80% of amount in cents creatorPaid Boolean @default(false) createdAt DateTime @default(now()) @@unique([userId, promptId]) // One purchase per user per prompt @@index([userId]) @@index([promptId]) }