2
0
Files
cal/calcom/packages/prisma/schema.prisma
2024-08-09 00:39:27 +02:00

1417 lines
46 KiB
Plaintext

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_DIRECT_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["views"]
}
generator zod {
provider = "zod-prisma"
output = "./zod"
imports = "./zod-utils"
relationModel = "default"
}
generator kysely {
provider = "prisma-kysely"
output = "../kysely"
fileName = "types.ts"
}
generator enums {
provider = "ts-node --transpile-only ./enum-generator"
}
enum SchedulingType {
ROUND_ROBIN @map("roundRobin")
COLLECTIVE @map("collective")
MANAGED @map("managed")
}
enum PeriodType {
UNLIMITED @map("unlimited")
ROLLING @map("rolling")
ROLLING_WINDOW @map("rolling_window")
RANGE @map("range")
}
model Host {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
eventTypeId Int
isFixed Boolean @default(false)
priority Int?
@@id([userId, eventTypeId])
@@index([userId])
@@index([eventTypeId])
}
model EventType {
id Int @id @default(autoincrement())
/// @zod.min(1)
title String
/// @zod.custom(imports.eventTypeSlug)
slug String
description String?
position Int @default(0)
/// @zod.custom(imports.eventTypeLocations)
locations Json?
/// @zod.min(1)
length Int
offsetStart Int @default(0)
hidden Boolean @default(false)
hosts Host[]
users User[] @relation("user_eventtype")
owner User? @relation("owner", fields: [userId], references: [id], onDelete: Cascade)
userId Int?
profileId Int?
profile Profile? @relation(fields: [profileId], references: [id], onDelete: Cascade)
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int?
hashedLink HashedLink?
bookings Booking[]
availability Availability[]
webhooks Webhook[]
destinationCalendar DestinationCalendar?
eventName String?
customInputs EventTypeCustomInput[]
parentId Int?
parent EventType? @relation("managed_eventtype", fields: [parentId], references: [id], onDelete: Cascade)
children EventType[] @relation("managed_eventtype")
/// @zod.custom(imports.eventTypeBookingFields)
bookingFields Json?
timeZone String?
periodType PeriodType @default(UNLIMITED)
/// @zod.custom(imports.coerceToDate)
periodStartDate DateTime?
/// @zod.custom(imports.coerceToDate)
periodEndDate DateTime?
periodDays Int?
periodCountCalendarDays Boolean?
lockTimeZoneToggleOnBookingPage Boolean @default(false)
requiresConfirmation Boolean @default(false)
requiresBookerEmailVerification Boolean @default(false)
/// @zod.custom(imports.recurringEventType)
recurringEvent Json?
disableGuests Boolean @default(false)
hideCalendarNotes Boolean @default(false)
/// @zod.min(0)
minimumBookingNotice Int @default(120)
beforeEventBuffer Int @default(0)
afterEventBuffer Int @default(0)
seatsPerTimeSlot Int?
onlyShowFirstAvailableSlot Boolean @default(false)
seatsShowAttendees Boolean? @default(false)
seatsShowAvailabilityCount Boolean? @default(true)
schedulingType SchedulingType?
schedule Schedule? @relation(fields: [scheduleId], references: [id])
scheduleId Int?
// price is deprecated. It has now moved to metadata.apps.stripe.price. Plan to drop this column.
price Int @default(0)
// currency is deprecated. It has now moved to metadata.apps.stripe.currency. Plan to drop this column.
currency String @default("usd")
slotInterval Int?
/// @zod.custom(imports.EventTypeMetaDataSchema)
metadata Json?
/// @zod.custom(imports.successRedirectUrl)
successRedirectUrl String?
forwardParamsSuccessRedirect Boolean? @default(true)
workflows WorkflowsOnEventTypes[]
/// @zod.custom(imports.intervalLimitsType)
bookingLimits Json?
/// @zod.custom(imports.intervalLimitsType)
durationLimits Json?
isInstantEvent Boolean @default(false)
instantMeetingExpiryTimeOffsetInSeconds Int @default(90)
assignAllTeamMembers Boolean @default(false)
useEventTypeDestinationCalendarEmail Boolean @default(false)
aiPhoneCallConfig AIPhoneCallConfiguration?
secondaryEmailId Int?
secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id], onDelete: Cascade)
@@unique([userId, slug])
@@unique([teamId, slug])
@@unique([userId, parentId])
@@index([userId])
@@index([teamId])
@@index([scheduleId])
@@index([secondaryEmailId])
}
model Credential {
id Int @id @default(autoincrement())
// @@type is deprecated
type String
key Json
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int?
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int?
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
// How to make it a required column?
appId String?
// paid apps
subscriptionId String?
paymentStatus String?
billingCycleStart Int?
destinationCalendars DestinationCalendar[]
selectedCalendars SelectedCalendar[]
invalid Boolean? @default(false)
CalendarCache CalendarCache[]
@@index([userId])
@@index([appId])
@@index([subscriptionId])
}
enum IdentityProvider {
CAL
GOOGLE
SAML
}
model DestinationCalendar {
id Int @id @default(autoincrement())
integration String
externalId String
/// @zod.email()
primaryEmail String?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int? @unique
booking Booking[]
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
eventTypeId Int? @unique
credentialId Int?
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([eventTypeId])
@@index([credentialId])
}
enum UserPermissionRole {
USER
ADMIN
}
// It holds the password of a User, separate from the User model to avoid leaking the password hash
model UserPassword {
hash String
userId Int @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model TravelSchedule {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
timeZone String
startDate DateTime
endDate DateTime?
prevTimeZone String?
@@index([startDate])
@@index([endDate])
}
// It holds Personal Profiles of a User plus it has email, password and other core things..
model User {
id Int @id @default(autoincrement())
username String?
name String?
/// @zod.email()
email String
emailVerified DateTime?
password UserPassword?
bio String?
avatarUrl String?
timeZone String @default("Europe/London")
travelSchedules TravelSchedule[]
weekStart String @default("Sunday")
// DEPRECATED - TO BE REMOVED
startTime Int @default(0)
endTime Int @default(1440)
// </DEPRECATED>
bufferTime Int @default(0)
hideBranding Boolean @default(false)
// TODO: should be renamed since it only affects the booking page
theme String?
appTheme String?
createdDate DateTime @default(now()) @map(name: "created")
trialEndsAt DateTime?
eventTypes EventType[] @relation("user_eventtype")
credentials Credential[]
teams Membership[]
bookings Booking[]
schedules Schedule[]
defaultScheduleId Int?
selectedCalendars SelectedCalendar[]
completedOnboarding Boolean @default(false)
locale String?
timeFormat Int? @default(12)
twoFactorSecret String?
twoFactorEnabled Boolean @default(false)
backupCodes String?
identityProvider IdentityProvider @default(CAL)
identityProviderId String?
availability Availability[]
invitedTo Int?
webhooks Webhook[]
brandColor String?
darkBrandColor String?
// the location where the events will end up
destinationCalendar DestinationCalendar?
// participate in dynamic group booking or not
allowDynamicBooking Boolean? @default(true)
// participate in SEO indexing or not
allowSEOIndexing Boolean? @default(true)
// receive monthly digest email for teams or not
receiveMonthlyDigestEmail Boolean? @default(true)
/// @zod.custom(imports.userMetadata)
metadata Json?
verified Boolean? @default(false)
role UserPermissionRole @default(USER)
disableImpersonation Boolean @default(false)
impersonatedUsers Impersonations[] @relation("impersonated_user")
impersonatedBy Impersonations[] @relation("impersonated_by_user")
apiKeys ApiKey[]
accounts Account[]
sessions Session[]
Feedback Feedback[]
ownedEventTypes EventType[] @relation("owner")
workflows Workflow[]
routingForms App_RoutingForms_Form[] @relation("routing-form")
verifiedNumbers VerifiedNumber[]
verifiedEmails VerifiedEmail[]
hosts Host[]
// organizationId is deprecated. Instead, rely on the Profile to search profiles by organizationId and then get user from the profile.
organizationId Int?
organization Team? @relation("scope", fields: [organizationId], references: [id], onDelete: SetNull)
accessCodes AccessCode[]
bookingRedirects OutOfOfficeEntry[]
bookingRedirectsTo OutOfOfficeEntry[] @relation(name: "toUser")
// Used to lock the user account
locked Boolean @default(false)
platformOAuthClients PlatformOAuthClient[]
AccessToken AccessToken[]
RefreshToken RefreshToken[]
PlatformAuthorizationToken PlatformAuthorizationToken[]
profiles Profile[]
movedToProfileId Int?
movedToProfile Profile? @relation("moved_to_profile", fields: [movedToProfileId], references: [id], onDelete: SetNull)
secondaryEmails SecondaryEmail[]
isPlatformManaged Boolean @default(false)
OutOfOfficeReasons OutOfOfficeReason[]
smsLockState SMSLockState @default(UNLOCKED)
@@unique([email])
@@unique([email, username])
@@unique([username, organizationId])
@@unique([movedToProfileId])
@@index([username])
@@index([emailVerified])
@@index([identityProvider])
@@index([identityProviderId])
@@map(name: "users")
}
// It holds Organization Profiles as well as User Profiles for users that have been added to an organization
model Profile {
id Int @id @default(autoincrement())
// uid allows us to set an identifier chosen by us which is helpful in migration when we create the Profile from User directly.
uid String
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
organizationId Int
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
username String
eventTypes EventType[]
movedFromUser User? @relation("moved_to_profile")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// A user can have multiple profiles in different organizations
@@unique([userId, organizationId])
// Allow username reuse only across different organizations
@@unique([username, organizationId])
@@index([uid])
@@index([userId])
@@index([organizationId])
}
model Team {
id Int @id @default(autoincrement())
/// @zod.min(1)
name String
/// @zod.min(1)
slug String?
logoUrl String?
calVideoLogo String?
appLogo String?
appIconLogo String?
bio String?
hideBranding Boolean @default(false)
isPrivate Boolean @default(false)
hideBookATeamMember Boolean @default(false)
members Membership[]
eventTypes EventType[]
workflows Workflow[]
createdAt DateTime @default(now())
/// @zod.custom(imports.teamMetadataSchema)
metadata Json?
theme String?
brandColor String?
darkBrandColor String?
verifiedNumbers VerifiedNumber[]
verifiedEmails VerifiedEmail[]
bannerUrl String?
parentId Int?
parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade)
children Team[] @relation("organization")
orgUsers User[] @relation("scope")
inviteTokens VerificationToken[]
webhooks Webhook[]
timeFormat Int?
timeZone String @default("Europe/London")
weekStart String @default("Sunday")
routingForms App_RoutingForms_Form[]
apiKeys ApiKey[]
credentials Credential[]
accessCodes AccessCode[]
isOrganization Boolean @default(false)
organizationSettings OrganizationSettings?
instantMeetingTokens InstantMeetingToken[]
orgProfiles Profile[]
pendingPayment Boolean @default(false)
dsyncTeamGroupMapping DSyncTeamGroupMapping[]
isPlatform Boolean @default(false)
platformOAuthClient PlatformOAuthClient[]
smsLockState SMSLockState @default(UNLOCKED)
platformBilling PlatformBilling?
activeOrgWorkflows WorkflowsOnTeams[]
@@unique([slug, parentId])
@@index([parentId])
}
model OrganizationSettings {
id Int @id @default(autoincrement())
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId Int @unique
isOrganizationConfigured Boolean @default(false)
// It decides if new organization members can be auto-accepted or not
isOrganizationVerified Boolean @default(false)
orgAutoAcceptEmail String
lockEventTypeCreationForUsers Boolean @default(false)
adminGetsNoSlotsNotification Boolean @default(false)
// It decides if instance ADMIN has reviewed the organization or not.
// It is used to allow super sensitive operations like 'impersonation of Org members by Org admin'
isAdminReviewed Boolean @default(false)
dSyncData DSyncData?
}
enum MembershipRole {
MEMBER
ADMIN
OWNER
}
model Membership {
id Int @id @default(autoincrement())
teamId Int
userId Int
accepted Boolean @default(false)
role MembershipRole
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
disableImpersonation Boolean @default(false)
@@unique([userId, teamId])
@@index([teamId])
@@index([userId])
}
model VerificationToken {
id Int @id @default(autoincrement())
identifier String
token String @unique
expires DateTime
expiresInDays Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamId Int?
team Team? @relation(fields: [teamId], references: [id])
secondaryEmailId Int?
secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id])
@@unique([identifier, token])
@@index([token])
@@index([teamId])
@@index([secondaryEmailId])
}
model InstantMeetingToken {
id Int @id @default(autoincrement())
token String @unique
expires DateTime
teamId Int
team Team @relation(fields: [teamId], references: [id])
bookingId Int? @unique
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([token])
}
model BookingReference {
id Int @id @default(autoincrement())
/// @zod.min(1)
type String
/// @zod.min(1)
uid String
meetingId String?
thirdPartyRecurringEventId String?
meetingPassword String?
meetingUrl String?
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
bookingId Int?
externalCalendarId String?
deleted Boolean?
credentialId Int?
@@index([bookingId])
@@index([credentialId])
@@index([type])
@@index([uid])
}
model Attendee {
id Int @id @default(autoincrement())
email String
name String
timeZone String
locale String? @default("en")
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
bookingId Int?
bookingSeat BookingSeat?
noShow Boolean? @default(false)
@@index([email])
@@index([bookingId])
}
enum BookingStatus {
CANCELLED @map("cancelled")
ACCEPTED @map("accepted")
REJECTED @map("rejected")
PENDING @map("pending")
AWAITING_HOST @map("awaiting_host")
}
model Booking {
id Int @id @default(autoincrement())
uid String @unique
// (optional) UID based on slot start/end time & email against duplicates
idempotencyKey String? @unique
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int?
// User's email at the time of booking
/// @zod.email()
userPrimaryEmail String?
references BookingReference[]
eventType EventType? @relation(fields: [eventTypeId], references: [id])
eventTypeId Int?
title String
description String?
customInputs Json?
/// @zod.custom(imports.bookingResponses)
responses Json?
startTime DateTime
endTime DateTime
attendees Attendee[]
location String?
createdAt DateTime @default(now())
updatedAt DateTime?
status BookingStatus @default(ACCEPTED)
paid Boolean @default(false)
payment Payment[]
destinationCalendar DestinationCalendar? @relation(fields: [destinationCalendarId], references: [id])
destinationCalendarId Int?
cancellationReason String?
rejectionReason String?
dynamicEventSlugRef String?
dynamicGroupSlugRef String?
rescheduled Boolean?
fromReschedule String?
recurringEventId String?
smsReminderNumber String?
workflowReminders WorkflowReminder[]
scheduledJobs String[] // scheduledJobs is deprecated, please use scheduledTriggers instead
seatsReferences BookingSeat[]
/// @zod.custom(imports.bookingMetadataSchema)
metadata Json?
isRecorded Boolean @default(false)
iCalUID String? @default("")
iCalSequence Int @default(0)
instantMeetingToken InstantMeetingToken?
rating Int?
ratingFeedback String?
noShowHost Boolean?
scheduledTriggers WebhookScheduledTriggers[]
@@index([eventTypeId])
@@index([userId])
@@index([destinationCalendarId])
@@index([recurringEventId])
@@index([uid])
@@index([status])
@@index([startTime, endTime, status])
}
model Schedule {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
eventType EventType[]
name String
timeZone String?
availability Availability[]
@@index([userId])
}
model Availability {
id Int @id @default(autoincrement())
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int?
eventType EventType? @relation(fields: [eventTypeId], references: [id])
eventTypeId Int?
days Int[]
startTime DateTime @db.Time
endTime DateTime @db.Time
date DateTime? @db.Date
Schedule Schedule? @relation(fields: [scheduleId], references: [id])
scheduleId Int?
@@index([userId])
@@index([eventTypeId])
@@index([scheduleId])
}
model SelectedCalendar {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
integration String
externalId String
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
credentialId Int?
@@id([userId, integration, externalId])
@@index([userId])
@@index([integration])
@@index([externalId])
}
enum EventTypeCustomInputType {
TEXT @map("text")
TEXTLONG @map("textLong")
NUMBER @map("number")
BOOL @map("bool")
RADIO @map("radio")
PHONE @map("phone")
}
model EventTypeCustomInput {
id Int @id @default(autoincrement())
eventTypeId Int
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
label String
type EventTypeCustomInputType
/// @zod.custom(imports.customInputOptionSchema)
options Json?
required Boolean
placeholder String @default("")
@@index([eventTypeId])
}
model ResetPasswordRequest {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String
expires DateTime
}
enum ReminderType {
PENDING_BOOKING_CONFIRMATION
}
model ReminderMail {
id Int @id @default(autoincrement())
referenceId Int
reminderType ReminderType
elapsedMinutes Int
createdAt DateTime @default(now())
@@index([referenceId])
@@index([reminderType])
}
model Payment {
id Int @id @default(autoincrement())
uid String @unique
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
appId String?
bookingId Int
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
amount Int
fee Int
currency String
success Boolean
refunded Boolean
data Json
externalId String @unique
paymentOption PaymentOption? @default(ON_BOOKING)
@@index([bookingId])
@@index([externalId])
}
enum PaymentOption {
ON_BOOKING
HOLD
}
enum WebhookTriggerEvents {
BOOKING_CREATED
BOOKING_PAYMENT_INITIATED
BOOKING_PAID
BOOKING_RESCHEDULED
BOOKING_REQUESTED
BOOKING_CANCELLED
BOOKING_REJECTED
BOOKING_NO_SHOW_UPDATED
FORM_SUBMITTED
MEETING_ENDED
MEETING_STARTED
RECORDING_READY
INSTANT_MEETING
RECORDING_TRANSCRIPTION_GENERATED
}
model Webhook {
id String @id @unique
userId Int?
teamId Int?
eventTypeId Int?
/// @zod.url()
subscriberUrl String
payloadTemplate String?
createdAt DateTime @default(now())
active Boolean @default(true)
eventTriggers WebhookTriggerEvents[]
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
appId String?
secret String?
platform Boolean @default(false)
scheduledTriggers WebhookScheduledTriggers[]
@@unique([userId, subscriberUrl], name: "courseIdentifier")
}
model Impersonations {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
impersonatedUser User @relation("impersonated_user", fields: [impersonatedUserId], references: [id], onDelete: Cascade)
impersonatedBy User @relation("impersonated_by_user", fields: [impersonatedById], references: [id], onDelete: Cascade)
impersonatedUserId Int
impersonatedById Int
}
model ApiKey {
id String @id @unique @default(cuid())
userId Int
teamId Int?
note String?
createdAt DateTime @default(now())
expiresAt DateTime?
lastUsedAt DateTime?
hashedKey String @unique()
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
appId String?
@@index([userId])
}
model HashedLink {
id Int @id @default(autoincrement())
link String @unique()
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
eventTypeId Int @unique
}
model Account {
id String @id @default(cuid())
userId Int
type String
provider String
providerAccountId String
providerEmail String?
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@index([userId])
@@index([type])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId Int
expires DateTime
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
enum AppCategories {
calendar
messaging
other
payment
video // deprecated, please use 'conferencing' instead
web3 // deprecated, we should no longer have any web3 apps
automation
analytics
// Wherever video is in use, conferencing should also be used for legacy apps can have it.
conferencing
crm
}
model App {
// The slug for the app store public page inside `/apps/[slug]`
slug String @id @unique
// The directory name for `/packages/app-store/[dirName]`
dirName String @unique
// Needed API Keys
keys Json?
// One or multiple categories to which this app belongs
categories AppCategories[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
credentials Credential[]
payments Payment[]
Webhook Webhook[]
ApiKey ApiKey[]
enabled Boolean @default(false)
@@index([enabled])
}
model App_RoutingForms_Form {
id String @id @default(cuid())
description String?
position Int @default(0)
routes Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
fields Json?
user User @relation("routing-form", fields: [userId], references: [id], onDelete: Cascade)
// This is the user who created the form and also the user who has read-write access to the form
// If teamId is set, the members of the team would also have access to form readOnly or read-write depending on their permission level as team member.
userId Int
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int?
responses App_RoutingForms_FormResponse[]
disabled Boolean @default(false)
/// @zod.custom(imports.RoutingFormSettings)
settings Json?
@@index([userId])
@@index([disabled])
}
model App_RoutingForms_FormResponse {
id Int @id @default(autoincrement())
formFillerId String @default(cuid())
form App_RoutingForms_Form @relation(fields: [formId], references: [id], onDelete: Cascade)
formId String
response Json
createdAt DateTime @default(now())
@@unique([formFillerId, formId])
@@index([formFillerId])
@@index([formId])
}
model Feedback {
id Int @id @default(autoincrement())
date DateTime @default(now())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
rating String
comment String?
@@index([userId])
@@index([rating])
}
enum WorkflowTriggerEvents {
BEFORE_EVENT
EVENT_CANCELLED
NEW_EVENT
AFTER_EVENT
RESCHEDULE_EVENT
}
enum WorkflowActions {
EMAIL_HOST
EMAIL_ATTENDEE
SMS_ATTENDEE
SMS_NUMBER
EMAIL_ADDRESS
WHATSAPP_ATTENDEE
WHATSAPP_NUMBER
}
model WorkflowStep {
id Int @id @default(autoincrement())
stepNumber Int
action WorkflowActions
workflowId Int
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
sendTo String?
reminderBody String?
emailSubject String?
template WorkflowTemplates @default(REMINDER)
workflowReminders WorkflowReminder[]
numberRequired Boolean?
sender String?
numberVerificationPending Boolean @default(true)
includeCalendarEvent Boolean @default(false)
@@index([workflowId])
}
model Workflow {
id Int @id @default(autoincrement())
position Int @default(0)
name String
userId Int?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int?
activeOn WorkflowsOnEventTypes[]
activeOnTeams WorkflowsOnTeams[]
isActiveOnAll Boolean @default(false)
trigger WorkflowTriggerEvents
time Int?
timeUnit TimeUnit?
steps WorkflowStep[]
@@index([userId])
@@index([teamId])
}
model AIPhoneCallConfiguration {
id Int @id @default(autoincrement())
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
eventTypeId Int
generalPrompt String
yourPhoneNumber String
numberToCall String
guestName String
guestEmail String?
guestCompany String?
enabled Boolean @default(false)
beginMessage String?
llmId String?
@@unique([eventTypeId])
@@index([eventTypeId])
}
model WorkflowsOnEventTypes {
id Int @id @default(autoincrement())
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
workflowId Int
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
eventTypeId Int
@@unique([workflowId, eventTypeId])
@@index([workflowId])
@@index([eventTypeId])
}
model WorkflowsOnTeams {
id Int @id @default(autoincrement())
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
workflowId Int
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int
@@unique([workflowId, teamId])
@@index([workflowId])
@@index([teamId])
}
model Deployment {
/// This is a single row table, so we use a fixed id
id Int @id @default(1)
logo String?
/// @zod.custom(imports.DeploymentTheme)
theme Json?
licenseKey String?
agreedLicenseAt DateTime?
}
enum TimeUnit {
DAY @map("day")
HOUR @map("hour")
MINUTE @map("minute")
}
model WorkflowReminder {
id Int @id @default(autoincrement())
bookingUid String?
booking Booking? @relation(fields: [bookingUid], references: [uid])
method WorkflowMethods
scheduledDate DateTime
referenceId String? @unique
scheduled Boolean
workflowStepId Int?
workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id], onDelete: Cascade)
cancelled Boolean?
seatReferenceId String?
isMandatoryReminder Boolean? @default(false)
retryCount Int @default(0)
@@index([bookingUid])
@@index([workflowStepId])
@@index([seatReferenceId])
@@index([method, scheduled, scheduledDate])
@@index([cancelled, scheduledDate])
}
model WebhookScheduledTriggers {
id Int @id @default(autoincrement())
jobName String? // jobName is deprecated, not needed when webhook and booking is set
subscriberUrl String
payload String
startAfter DateTime
retryCount Int @default(0)
createdAt DateTime? @default(now())
appId String?
webhookId String?
webhook Webhook? @relation(fields: [webhookId], references: [id], onDelete: Cascade)
bookingId Int?
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
}
enum WorkflowTemplates {
REMINDER
CUSTOM
CANCELLED
RESCHEDULED
COMPLETED
RATING
}
enum WorkflowMethods {
EMAIL
SMS
WHATSAPP
}
model BookingSeat {
id Int @id @default(autoincrement())
referenceUid String @unique
bookingId Int
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
attendeeId Int @unique
attendee Attendee @relation(fields: [attendeeId], references: [id], onDelete: Cascade)
/// @zod.custom(imports.bookingSeatDataSchema)
data Json?
@@index([bookingId])
@@index([attendeeId])
}
model VerifiedNumber {
id Int @id @default(autoincrement())
userId Int?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
teamId Int?
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
phoneNumber String
@@index([userId])
@@index([teamId])
}
model VerifiedEmail {
id Int @id @default(autoincrement())
userId Int?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
teamId Int?
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
email String
@@index([userId])
@@index([teamId])
}
model Feature {
// The feature slug, ex: 'v2-workflows'
slug String @id @unique
// If the feature is currently enabled
enabled Boolean @default(false)
// A short description of the feature
description String?
// The type of feature flag
type FeatureType? @default(RELEASE)
// If the flag is considered stale
stale Boolean? @default(false)
lastUsedAt DateTime?
createdAt DateTime? @default(now())
updatedAt DateTime? @default(now()) @updatedAt
updatedBy Int?
@@index([enabled])
@@index([stale])
}
enum FeatureType {
RELEASE
EXPERIMENT
OPERATIONAL
KILL_SWITCH
PERMISSION
}
model SelectedSlots {
id Int @id @default(autoincrement())
eventTypeId Int
userId Int
slotUtcStartDate DateTime
slotUtcEndDate DateTime
uid String
releaseAt DateTime
isSeat Boolean @default(false)
@@unique(fields: [userId, slotUtcStartDate, slotUtcEndDate, uid], name: "selectedSlotUnique")
}
model OAuthClient {
clientId String @id @unique
redirectUri String
clientSecret String
name String
logo String?
accessCodes AccessCode[]
}
model AccessCode {
id Int @id @default(autoincrement())
code String
clientId String?
client OAuthClient? @relation(fields: [clientId], references: [clientId], onDelete: Cascade)
expiresAt DateTime
scopes AccessScope[]
userId Int?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
teamId Int?
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
}
enum AccessScope {
READ_BOOKING
READ_PROFILE
}
view BookingTimeStatus {
id Int @unique
uid String?
eventTypeId Int?
title String?
description String?
startTime DateTime?
endTime DateTime?
createdAt DateTime?
location String?
paid Boolean?
status BookingStatus?
rescheduled Boolean?
userId Int?
teamId Int?
eventLength Int?
timeStatus String?
eventParentId Int?
userEmail String?
username String?
ratingFeedback String?
rating Int?
noShowHost Boolean?
}
model CalendarCache {
// The key would be the unique URL that is requested by the user
key String
value Json
expiresAt DateTime
credentialId Int
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
@@id([credentialId, key])
@@unique([credentialId, key])
}
enum RedirectType {
UserEventType @map("user-event-type")
TeamEventType @map("team-event-type")
User @map("user")
Team @map("team")
}
model TempOrgRedirect {
id Int @id @default(autoincrement())
// Better would be to have fromOrgId and toOrgId as well and then we should have just to instead toUrl
from String
// 0 would mean it is non org
fromOrgId Int
type RedirectType
// It doesn't have any query params
toUrl String
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([from, type, fromOrgId])
}
model Avatar {
// e.g. NULL(0), organization ID or team logo
teamId Int @default(0)
// Avatar, NULL(0) if team logo
userId Int @default(0)
// base64 string
data String
// different every time to pop the cache.
objectKey String @unique
isBanner Boolean @default(false)
@@unique([teamId, userId, isBanner])
@@map(name: "avatars")
}
model OutOfOfficeEntry {
id Int @id @default(autoincrement())
uuid String @unique
start DateTime
end DateTime
notes String?
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
toUserId Int?
toUser User? @relation(name: "toUser", fields: [toUserId], references: [id], onDelete: Cascade)
reasonId Int?
reason OutOfOfficeReason? @relation(fields: [reasonId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([uuid])
@@index([userId])
@@index([toUserId])
@@index([start, end])
}
model OutOfOfficeReason {
id Int @id @default(autoincrement())
emoji String
reason String @unique
enabled Boolean @default(true)
userId Int?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
entries OutOfOfficeEntry[]
}
// Platform
model PlatformOAuthClient {
id String @id @default(cuid())
name String
secret String
permissions Int
users User[]
logo String?
redirectUris String[]
organizationId Int
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
accessTokens AccessToken[]
refreshToken RefreshToken[]
authorizationTokens PlatformAuthorizationToken[]
bookingRedirectUri String?
bookingCancelRedirectUri String?
bookingRescheduleRedirectUri String?
areEmailsEnabled Boolean @default(false)
createdAt DateTime @default(now())
}
model PlatformAuthorizationToken {
id String @id @default(cuid())
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
client PlatformOAuthClient @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
platformOAuthClientId String
userId Int
createdAt DateTime @default(now())
@@unique([userId, platformOAuthClientId])
}
model AccessToken {
id Int @id @default(autoincrement())
secret String @unique
createdAt DateTime @default(now())
expiresAt DateTime
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
client PlatformOAuthClient @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
platformOAuthClientId String
userId Int
}
model RefreshToken {
id Int @id @default(autoincrement())
secret String @unique
createdAt DateTime @default(now())
expiresAt DateTime
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
client PlatformOAuthClient @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
platformOAuthClientId String
userId Int
}
model DSyncData {
id Int @id @default(autoincrement())
directoryId String @unique
tenant String
organizationId Int? @unique
org OrganizationSettings? @relation(fields: [organizationId], references: [organizationId], onDelete: Cascade)
teamGroupMapping DSyncTeamGroupMapping[]
}
model DSyncTeamGroupMapping {
id Int @id @default(autoincrement())
organizationId Int
teamId Int
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
directoryId String
directory DSyncData @relation(fields: [directoryId], references: [directoryId], onDelete: Cascade)
groupName String
@@unique([teamId, groupName])
}
model SecondaryEmail {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
email String
emailVerified DateTime?
verificationTokens VerificationToken[]
eventTypes EventType[]
@@unique([email])
@@unique([userId, email])
@@index([userId])
}
// Needed to store tasks that need to be processed by a background worker or Tasker
model Task {
id String @id @unique @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The time at which the task should be executed
scheduledAt DateTime @default(now())
// The time at which the task was successfully executed
succeededAt DateTime?
// The task type to be executed. Left it as a freeform string to avoid more migrations for now. Will be enforced at type level.
type String
// Generic payload for the task
payload String
// The number of times the task has been attempted
attempts Int @default(0)
// The maximum number of times the task can be attempted
maxAttempts Int @default(3)
lastError String?
}
enum SMSLockState {
LOCKED
UNLOCKED
REVIEW_NEEDED
}
model PlatformBilling {
id Int @id @unique // team id
customerId String @unique
subscriptionId String?
plan String @default("none")
billingCycleStart Int?
billingCycleEnd Int?
overdue Boolean? @default(false)
team Team @relation(fields: [id], references: [id], onDelete: Cascade)
}