Files
jmartin 43928f7a49 Initial portfolio CMS: Fastify API + Vite SPA
Single-container app with Postgres catalog, Typesense search,
Authelia Remote-User admin, portfolio views, resume/PDF, media
uploads, and ArtStation import tooling.
2026-07-23 17:01:34 -04:00

193 lines
5.6 KiB
Plaintext

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")
}