generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) pubkey String @unique role String? // admin, moderator, writer. Null means Guest (no elevated role). SuperAdmin is env-sourced and never stored. displayName String? username String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // Scoped API key for programmatic access. The raw key is shown to the creator // only once; only its SHA-256 hash is stored. `permissions` is a JSON array of // permission keys that gate what the key may do. model ApiKey { id String @id @default(uuid()) name String prefix String // leading characters of the raw key, for display keyHash String @unique // SHA-256 hex of the full key permissions String // JSON array of permission keys createdByPubkey String lastUsedAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) } // Maps an assignable role to a granted permission key. Editable at runtime. // One row per (role, permission). SuperAdmin is never stored here; it bypasses // all checks via the env list. model RolePermission { role String permission String createdAt DateTime @default(now()) @@id([role, permission]) } model Organizer { id String @id @default(uuid()) name String slug String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt meetups Meetup[] } model Meetup { id String @id @default(uuid()) title String description String date String time String location String link String? imageId String? status String @default("DRAFT") // DRAFT, PUBLISHED, CANCELLED (Upcoming/Past derived from date) featured Boolean @default(false) visibility String @default("PUBLIC") // PUBLIC, HIDDEN organizerId String organizer Organizer @relation(fields: [organizerId], references: [id], onDelete: Restrict) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Media { id String @id // ULID, not auto-generated slug String type String // "image" | "video" mimeType String size Int originalFilename String uploadedBy String title String? description String? altText String? createdAt DateTime @default(now()) } model Post { id String @id @default(uuid()) nostrEventId String @unique naddr String? title String slug String @unique content String @default("") excerpt String? authorPubkey String authorName String? featured Boolean @default(false) visible Boolean @default(true) publishedAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt categories PostCategory[] } model Category { id String @id @default(uuid()) name String slug String @unique sortOrder Int @default(0) createdAt DateTime @default(now()) posts PostCategory[] } model PostCategory { postId String categoryId String post Post @relation(fields: [postId], references: [id], onDelete: Cascade) category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) @@id([postId, categoryId]) } model HiddenContent { id String @id @default(uuid()) nostrEventId String reason String? hiddenBy String createdAt DateTime @default(now()) } model BlockedPubkey { id String @id @default(uuid()) pubkey String reason String? blockedBy String createdAt DateTime @default(now()) } model Relay { id String @id @default(uuid()) url String @unique priority Int @default(0) active Boolean @default(true) createdAt DateTime @default(now()) } model UserRelay { id String @id @default(uuid()) pubkey String url String read Boolean @default(true) write Boolean @default(true) createdAt DateTime @default(now()) @@unique([pubkey, url]) } model Setting { id String @id @default(uuid()) key String @unique value String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model NostrEventCache { id String @id @default(uuid()) eventId String @unique kind Int pubkey String content String tags String // JSON string createdAt Int // event timestamp cachedAt DateTime @default(now()) } model Submission { id String @id @default(uuid()) eventId String? naddr String? title String authorPubkey String status String @default("PENDING") // PENDING, APPROVED, REJECTED reviewedBy String? reviewNote String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Faq { id String @id @default(uuid()) question String answer String order Int @default(0) showOnHomepage Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } /// Pending Lightning invoice metadata (message is created only after payment via webhook). model BoardInvoicePending { paymentHash String @id content String guestName String? pubkey String? profilePic String? createdAt DateTime @default(now()) } /// Paid board message (LNbits payment confirmed). model BoardMessage { id String @id @default(uuid()) paymentHash String @unique content String authorName String pubkey String? profilePic String? satsPaid Int status String @default("active") // active, hidden, deleted likeCount Int @default(0) createdAt DateTime @default(now()) }