Files
BelgianBitcoinEmbassy/backend/prisma/schema.prisma
T
bbeandCursor 70e3e0633d feat: roles/permissions system and Nostr profile display on admin users
Introduce granular role-based permissions with SuperAdmin env override, admin roles UI, and permission-gated API routes. Fix admin user Nostr metadata by batching relay profile fetches, normalizing npub pubkeys to hex, and adding reusable NostrAvatar/useNostrProfile components.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 08:46:56 +02:00

208 lines
5.4 KiB
Plaintext

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
}
// 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())
}