open-prompt / prisma /schema.prisma
GitHub Action
Automated sync to Hugging Face
bcce530
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])
}