Files
Philip e7b7536e70 Add password change, rename Viewer→Commissioner, fix login logo
- Rename VIEWER role to COMMISSIONER throughout (schema, middleware,
  admin layout, users page); add psql pre-migration step in entrypoint
  to rename the PostgreSQL enum value without data loss
- Install postgresql-client in Docker runner stage for psql access
- Login page: fetch sbLogo from settings API instead of hardcoded path
- Password change for all authenticated users:
  - New PATCH /api/users/me endpoint (verifies current password, hashes new)
  - Change Password button/modal on /my-squares page
  - Change Password link in admin sidebar (links to /my-squares)
  - New password_change email template (seeded, editable in admin)
  - sendPasswordChangedEmail auto-email triggered on change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:36:16 -07:00

142 lines
4.0 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
ADMIN
COMMISSIONER
PLAYER
}
enum PaymentType {
VENMO
PAYPAL
CASHAPP
ZELLE
CASH
}
model User {
id String @id @default(cuid())
email String @unique
name String
passwordHash String
role Role @default(PLAYER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
squares Square[]
chatMessages ChatMessage[]
}
model GameSettings {
id String @id @default("singleton")
title String @default("Super Bowl Squares")
commissioner String @default("")
eventName String @default("Super Bowl")
eventDate String @default("")
eventTime String @default("")
sbLogo String @default("/images/superbowlnumber.png")
nfcTeam String @default("NFC Team")
nfcLogo String @default("/images/nfc-generic.png")
afcTeam String @default("AFC Team")
afcLogo String @default("/images/afc-generic.png")
betAmount Float @default(10)
winFirstPct Float @default(20)
winSecondPct Float @default(20)
winThirdPct Float @default(20)
winFinalPct Float @default(30)
donationPct Float @default(10)
graceHours Int @default(48)
rulesText String @default("")
paymentInstructions String @default("")
paymentMethods PaymentMethod[]
}
model PaymentMethod {
id String @id @default(cuid())
gameSettingsId String @default("singleton")
gameSettings GameSettings @relation(fields: [gameSettingsId], references: [id], onDelete: Cascade)
type PaymentType
value String
enabled Boolean @default(true)
}
model Square {
id String @id @default(cuid())
position String @unique // "00" to "99"
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
guestName String?
guestEmail String?
notes String?
confirmed Boolean @default(false)
signupDate DateTime?
firstWin Boolean @default(false)
halfWin Boolean @default(false)
thirdWin Boolean @default(false)
finalWin Boolean @default(false)
reminderSent Boolean @default(false)
}
model GridNumber {
id String @id @default(cuid())
position Int @unique // 0-9 (column/row index)
nfcNumber Int // 0-9
afcNumber Int // 0-9
}
model Score {
id String @id @default("singleton")
nfcFirst Int?
afcFirst Int?
nfcHalf Int?
afcHalf Int?
nfcThird Int?
afcThird Int?
nfcFinal Int?
afcFinal Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model EmailSettings {
id String @id @default("singleton")
smtpHost String @default("")
smtpPort Int @default(587)
smtpUser String @default("")
smtpPass String @default("")
useSsl Boolean @default(false)
fromEmail String @default("")
fromName String @default("")
}
model EmailTemplate {
id String @id @default(cuid())
name String @unique
subject String
body String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ChatMessage {
id String @id @default(cuid())
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
guestName String?
message String
deleted Boolean @default(false)
createdAt DateTime @default(now())
}
model ChatBlacklist {
id String @id @default(cuid())
word String @unique
createdAt DateTime @default(now())
}