invite/prisma/schema.prisma
chris 2c78112a4f Initial commit: E-Invite, a self-hosted Evite-lite
Docker-ready Next.js + Prisma/Postgres e-invitation app: themed invite
pages, RSVP, comments, share links, email + SMS invites with
reply-to-RSVP-by-text, co-hosts, photo dropbox, and browser push
notifications.
2026-07-11 10:11:12 -04:00

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