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