generator client { provider = "prisma-client-js" binaryTargets = ["native", "linux-musl-openssl-3.0.x"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique username String? @unique passwordHash String? // null for OAuth-only users name String? avatar String? provider String @default("local") // "local", "google", "github", etc. providerId String? // OAuth provider's user ID role Role @default(USER) emailVerified Boolean @default(false) emailVerifiedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt recipes Recipe[] cookbooks Cookbook[] sharedRecipes RecipeShare[] refreshTokens RefreshToken[] verificationTokens VerificationToken[] @@index([email]) @@index([provider, providerId]) } model VerificationToken { id String @id @default(cuid()) userId String token String @unique type TokenType expiresAt DateTime createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([token]) } enum TokenType { EMAIL_VERIFICATION PASSWORD_RESET } enum Role { USER ADMIN } enum Visibility { PRIVATE // Only owner can see SHARED // Owner + specific users PUBLIC // Everyone can see } model RefreshToken { id String @id @default(cuid()) userId String token String @unique expiresAt DateTime createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([token]) } model Recipe { id String @id @default(cuid()) title String description String? prepTime Int? // minutes cookTime Int? // minutes totalTime Int? // minutes servings Int? imageUrl String? sourceUrl String? // For imported recipes author String? cuisine String? categories String[] @default([]) // Changed from single category to array rating Float? userId String? // Recipe owner visibility Visibility @default(PRIVATE) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id], onDelete: SetNull) sections RecipeSection[] ingredients Ingredient[] instructions Instruction[] images RecipeImage[] tags RecipeTag[] cookbooks CookbookRecipe[] sharedWith RecipeShare[] @@index([title]) @@index([cuisine]) @@index([userId]) @@index([visibility]) } model RecipeSection { id String @id @default(cuid()) recipeId String name String // e.g., "Starter", "Dough", "Assembly" order Int timing String? // e.g., "Day 1 - 8PM", "12 hours before mixing" recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) ingredients Ingredient[] instructions Instruction[] @@index([recipeId]) } model Ingredient { id String @id @default(cuid()) recipeId String? // Optional - can be derived from section sectionId String? // Optional - if null, belongs to recipe directly name String amount String? unit String? notes String? order Int recipe Recipe? @relation(fields: [recipeId], references: [id], onDelete: Cascade) section RecipeSection? @relation(fields: [sectionId], references: [id], onDelete: Cascade) instructions IngredientInstructionMapping[] @@index([recipeId]) @@index([sectionId]) } model Instruction { id String @id @default(cuid()) recipeId String? // Optional - can be derived from section sectionId String? // Optional - if null, belongs to recipe directly step Int text String @db.Text imageUrl String? timing String? // e.g., "8:00am", "After 30 minutes", "Day 2 - Morning" recipe Recipe? @relation(fields: [recipeId], references: [id], onDelete: Cascade) section RecipeSection? @relation(fields: [sectionId], references: [id], onDelete: Cascade) ingredients IngredientInstructionMapping[] @@index([recipeId]) @@index([sectionId]) } model IngredientInstructionMapping { id String @id @default(cuid()) ingredientId String instructionId String order Int // Display order within the instruction ingredient Ingredient @relation(fields: [ingredientId], references: [id], onDelete: Cascade) instruction Instruction @relation(fields: [instructionId], references: [id], onDelete: Cascade) @@unique([ingredientId, instructionId]) @@index([instructionId]) @@index([ingredientId]) } model RecipeImage { id String @id @default(cuid()) recipeId String url String order Int recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) @@index([recipeId]) } model Tag { id String @id @default(cuid()) name String @unique recipes RecipeTag[] } model RecipeTag { recipeId String tagId String recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([recipeId, tagId]) @@index([recipeId]) @@index([tagId]) } model RecipeShare { id String @id @default(cuid()) recipeId String userId String createdAt DateTime @default(now()) recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([recipeId, userId]) @@index([recipeId]) @@index([userId]) } model Cookbook { id String @id @default(cuid()) name String description String? coverImageUrl String? userId String? // Cookbook owner autoFilterCategories String[] @default([]) // Auto-add recipes matching these categories autoFilterTags String[] @default([]) // Auto-add recipes matching these tags createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id], onDelete: SetNull) recipes CookbookRecipe[] @@index([name]) @@index([userId]) } model CookbookRecipe { id String @id @default(cuid()) cookbookId String recipeId String addedAt DateTime @default(now()) cookbook Cookbook @relation(fields: [cookbookId], references: [id], onDelete: Cascade) recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) @@unique([cookbookId, recipeId]) @@index([cookbookId]) @@index([recipeId]) }