- Photos: guests and hosts can download all event photos as a ZIP; HEIC/HEIF uploads are now accepted, with a WebP thumbnail generated for gallery display while the original file is preserved for download; uploader name now always shows as a caption - Hosts can send a thank-you note (email/SMS) to guests who RSVP'd yes - Guests can add the event to Google Calendar or download an .ics file, and see a live countdown to the event - Hosts can override a theme's primary/accent colors per event - New textures for a few themes' card backgrounds
194 lines
5.6 KiB
Plaintext
194 lines
5.6 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String
|
|
name String
|
|
resetToken String? @unique
|
|
resetTokenExpiry DateTime?
|
|
emailVerified Boolean @default(false)
|
|
verificationToken String? @unique
|
|
verificationTokenExpiry DateTime?
|
|
notifyOnRsvp Boolean @default(true)
|
|
notifyOnComment Boolean @default(true)
|
|
notifyOnCoHostAccepted Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
|
|
sessions Session[]
|
|
events Event[]
|
|
collaboratorInvites EventCollaborator[]
|
|
pushSubscriptions PushSubscription[]
|
|
}
|
|
|
|
model PushSubscription {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
endpoint String @unique
|
|
p256dh String
|
|
auth String
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
enum RsvpStatus {
|
|
YES
|
|
NO
|
|
MAYBE
|
|
}
|
|
|
|
enum CollaboratorStatus {
|
|
PENDING
|
|
ACCEPTED
|
|
}
|
|
|
|
model Event {
|
|
id String @id @default(cuid())
|
|
hostId String
|
|
title String
|
|
occasion String @default("")
|
|
description String @default("")
|
|
contactInfo String @default("")
|
|
theme String @default("classic-elegant")
|
|
themePrimaryColor String?
|
|
themeAccentColor String?
|
|
timezone String @default("UTC")
|
|
eventDate DateTime
|
|
endDate DateTime?
|
|
location String @default("")
|
|
slug String @unique
|
|
rsvpDeadline DateTime?
|
|
restrictRsvpToInvited Boolean @default(false)
|
|
allowPlusOnes Boolean @default(true)
|
|
commentsEnabled Boolean @default(true)
|
|
foodProvided Boolean @default(false)
|
|
drinksProvided Boolean @default(false)
|
|
photosEnabled Boolean @default(true)
|
|
photoCloseDate DateTime?
|
|
photosClosed Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
host User @relation(fields: [hostId], references: [id], onDelete: Cascade)
|
|
rsvps Rsvp[]
|
|
comments Comment[]
|
|
guestInvites GuestInvite[]
|
|
collaborators EventCollaborator[]
|
|
photos Photo[]
|
|
|
|
@@index([hostId])
|
|
}
|
|
|
|
model Photo {
|
|
id String @id @default(cuid())
|
|
eventId String
|
|
filename String
|
|
// WebP thumbnail filename for formats browsers can't render inline (HEIC/HEIF).
|
|
// Null means `filename` itself is already displayable - use it directly.
|
|
thumbnailFilename String?
|
|
uploaderName String @default("")
|
|
hidden Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([eventId])
|
|
}
|
|
|
|
model EventCollaborator {
|
|
id String @id @default(cuid())
|
|
eventId String
|
|
invitedEmail String
|
|
userId String?
|
|
status CollaboratorStatus @default(PENDING)
|
|
token String @unique
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime
|
|
acceptedAt DateTime?
|
|
|
|
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([eventId, invitedEmail])
|
|
@@index([eventId])
|
|
}
|
|
|
|
model Rsvp {
|
|
id String @id @default(cuid())
|
|
eventId String
|
|
guestName String
|
|
// Exactly one of these identifies the guest, depending on whether they
|
|
// RSVP'd via the web form (email) or by replying to an SMS invite
|
|
// (phone) - enforced by a CHECK constraint added in the migration since
|
|
// Prisma's schema language doesn't express "at least one of" directly.
|
|
guestEmail String?
|
|
guestPhone String?
|
|
status RsvpStatus
|
|
numGuests Int @default(1)
|
|
note String @default("")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([eventId, guestEmail])
|
|
@@unique([eventId, guestPhone])
|
|
}
|
|
|
|
model GuestInvite {
|
|
id String @id @default(cuid())
|
|
eventId String
|
|
// Exactly one of email/phone identifies this guest-list entry (CHECK
|
|
// constraint added in the migration).
|
|
email String?
|
|
phone String?
|
|
name String @default("")
|
|
// Being on this list (guest-list membership, used to gate RSVPs when
|
|
// Event.restrictRsvpToInvited is on, and to route an inbound SMS reply to
|
|
// the right event) is independent of whether the invite itself actually
|
|
// went out - emailSent/smsSent are purely informational.
|
|
emailSent Boolean @default(false)
|
|
smsSent Boolean @default(false)
|
|
sentAt DateTime @default(now())
|
|
|
|
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([eventId, email])
|
|
@@unique([eventId, phone])
|
|
}
|
|
|
|
model Comment {
|
|
id String @id @default(cuid())
|
|
eventId String
|
|
authorName String
|
|
authorEmail String @default("")
|
|
body String
|
|
hidden Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([eventId])
|
|
}
|