generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum Visibility { published draft private } enum MediaType { image video external } enum VideoSource { youtube vimeo self_hosted external } model Category { id String @id @default(cuid()) name String @unique slug String @unique description String? sortOrder Int @default(0) @map("sort_order") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") projects ProjectCategory[] viewPriorities PortfolioViewCategory[] @@map("categories") } model Tag { id String @id @default(cuid()) name String @unique slug String @unique createdAt DateTime @default(now()) @map("created_at") projects ProjectTag[] @@map("tags") } model Project { id String @id @default(cuid()) title String slug String @unique description String @default("") shortDescription String? @map("short_description") date DateTime? @db.Date software String[] @default([]) externalLinks Json @default("[]") @map("external_links") featured Boolean @default(false) displayPriority Int @default(0) @map("display_priority") visibility Visibility @default(draft) thumbnailId String? @map("thumbnail_id") sourceUrl String? @map("source_url") sourcePlatform String? @map("source_platform") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") publishedAt DateTime? @map("published_at") thumbnail Media? @relation("ProjectThumbnail", fields: [thumbnailId], references: [id], onDelete: SetNull) media Media[] @relation("ProjectMedia") categories ProjectCategory[] tags ProjectTag[] @@index([visibility, displayPriority]) @@index([featured]) @@index([date]) @@map("projects") } model ProjectCategory { projectId String @map("project_id") categoryId String @map("category_id") project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) @@id([projectId, categoryId]) @@map("project_categories") } model ProjectTag { projectId String @map("project_id") tagId String @map("tag_id") project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([projectId, tagId]) @@map("project_tags") } model Media { id String @id @default(cuid()) projectId String? @map("project_id") type MediaType @default(image) url String thumbnailUrl String? @map("thumbnail_url") filename String? mimeType String? @map("mime_type") width Int? height Int? sizeBytes Int? @map("size_bytes") alt String? caption String? sortOrder Int @default(0) @map("sort_order") videoSource VideoSource? @map("video_source") videoId String? @map("video_id") externalUrl String? @map("external_url") createdAt DateTime @default(now()) @map("created_at") project Project? @relation("ProjectMedia", fields: [projectId], references: [id], onDelete: Cascade) thumbnailFor Project[] @relation("ProjectThumbnail") @@index([projectId, sortOrder]) @@map("media") } /// Named portfolio landing experiences e.g. game-dev, engineering model PortfolioView { id String @id @default(cuid()) name String slug String @unique description String? /// Default sort when no category match: priority | date | title defaultSort String @default("priority") @map("default_sort") /// Show projects outside configured categories after prioritized ones showOthers Boolean @default(true) @map("show_others") isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") categoryPriorities PortfolioViewCategory[] @@map("portfolio_views") } model PortfolioViewCategory { viewId String @map("view_id") categoryId String @map("category_id") priority Int @default(0) view PortfolioView @relation(fields: [viewId], references: [id], onDelete: Cascade) category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) @@id([viewId, categoryId]) @@map("portfolio_view_categories") } model Resume { id String @id @default(cuid()) /// Single active resume — only one should be isActive=true isActive Boolean @default(true) @map("is_active") fullName String @map("full_name") title String @default("") email String? phone String? location String? website String? summary String @default("") /// Structured sections as JSON: experience, education, skills, etc. sections Json @default("[]") /// Optional raw HTML override for public render htmlContent String? @map("html_content") @db.Text theme String @default("dark") updatedAt DateTime @updatedAt @map("updated_at") createdAt DateTime @default(now()) @map("created_at") @@map("resumes") } model SiteSetting { key String @id value Json updatedAt DateTime @updatedAt @map("updated_at") @@map("site_settings") }