Files
formbricks/packages/database/schema.prisma

777 lines
24 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")
extensions = [pgvector(map: "vector")]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
// generator dbml {
// provider = "prisma-dbml-generator"
// }
generator json {
provider = "prisma-json-types-generator"
}
enum PipelineTriggers {
responseCreated
responseUpdated
responseFinished
}
enum WebhookSource {
user
zapier
make
n8n
}
model Webhook {
id String @id @default(cuid())
name String?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
url String
source WebhookSource @default(user)
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
environmentId String
triggers PipelineTriggers[]
surveyIds String[]
@@index([environmentId])
}
model ContactAttribute {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
attributeKey ContactAttributeKey @relation(fields: [attributeKeyId], references: [id], onDelete: Cascade)
attributeKeyId String
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
contactId String
value String
@@unique([contactId, attributeKeyId])
@@index([attributeKeyId, value])
}
enum ContactAttributeType {
default
custom
}
model ContactAttributeKey {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
isUnique Boolean @default(false)
key String
name String?
description String?
type ContactAttributeType @default(custom)
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
environmentId String
attributes ContactAttribute[]
attributeFilters SurveyAttributeFilter[]
@@unique([key, environmentId])
@@index([environmentId, createdAt])
}
model Contact {
id String @id @default(cuid())
userId String?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
environmentId String
responses Response[]
attributes ContactAttribute[]
displays Display[]
@@index([environmentId])
}
model Response {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
finished Boolean @default(false)
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
contact Contact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
contactId String?
endingId String?
notes ResponseNote[]
/// [ResponseData]
data Json @default("{}")
/// [ResponseVariables]
variables Json @default("{}")
/// [ResponseTtc]
ttc Json @default("{}")
/// [ResponseMeta]
meta Json @default("{}")
tags TagsOnResponses[]
/// [ResponseContactAttributes]
contactAttributes Json?
// singleUseId, used to prevent multiple responses
singleUseId String?
language String?
documents Document[]
displayId String? @unique
display Display? @relation(fields: [displayId], references: [id])
@@unique([surveyId, singleUseId])
@@index([surveyId, createdAt]) // to determine monthly response count
@@index([contactId, createdAt]) // to determine monthly identified users (persons)
@@index([surveyId])
}
model ResponseNote {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
response Response @relation(fields: [responseId], references: [id], onDelete: Cascade)
responseId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
text String
isResolved Boolean @default(false)
isEdited Boolean @default(false)
@@index([responseId])
}
model Tag {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
responses TagsOnResponses[]
environmentId String
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@unique([environmentId, name])
@@index([environmentId])
}
model TagsOnResponses {
responseId String
response Response @relation(fields: [responseId], references: [id], onDelete: Cascade)
tagId String
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([responseId, tagId])
@@index([responseId])
}
enum SurveyStatus {
draft
scheduled
inProgress
paused
completed
}
enum DisplayStatus {
seen
responded
}
model Display {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
contact Contact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
contactId String?
responseId String? @unique //deprecated
status DisplayStatus?
response Response?
@@index([surveyId])
@@index([contactId, createdAt])
}
model SurveyTrigger {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
actionClass ActionClass @relation(fields: [actionClassId], references: [id], onDelete: Cascade)
actionClassId String
@@unique([surveyId, actionClassId])
@@index([surveyId])
}
enum SurveyAttributeFilterCondition {
equals
notEquals
}
model SurveyAttributeFilter {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
attributeKey ContactAttributeKey @relation(fields: [attributeKeyId], references: [id], onDelete: Cascade)
attributeKeyId String
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
condition SurveyAttributeFilterCondition
value String
@@unique([surveyId, attributeKeyId])
@@index([surveyId])
@@index([attributeKeyId])
}
enum SurveyType {
link
web
website
app
}
enum displayOptions {
displayOnce
displayMultiple
displaySome
respondMultiple
}
model Survey {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
redirectUrl String?
type SurveyType @default(web)
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
environmentId String
creator User? @relation(fields: [createdBy], references: [id])
createdBy String?
status SurveyStatus @default(draft)
/// [SurveyWelcomeCard]
welcomeCard Json @default("{\"enabled\": false}")
/// [SurveyQuestions]
questions Json @default("[]")
/// [SurveyEnding]
endings Json[] @default([])
thankYouCard Json? //deprecated
/// [SurveyHiddenFields]
hiddenFields Json @default("{\"enabled\": false}")
/// [SurveyVariables]
variables Json @default("[]")
responses Response[]
displayOption displayOptions @default(displayOnce)
recontactDays Int?
displayLimit Int?
triggers SurveyTrigger[]
/// [SurveyInlineTriggers]
inlineTriggers Json?
attributeFilters SurveyAttributeFilter[]
displays Display[]
autoClose Int?
autoComplete Int?
delay Int @default(0)
runOnDate DateTime?
closeOnDate DateTime?
/// [SurveyClosedMessage]
surveyClosedMessage Json?
segmentId String?
segment Segment? @relation(fields: [segmentId], references: [id])
/// [SurveyProjectOverwrites]
projectOverwrites Json?
/// [SurveyStyling]
styling Json?
/// [SurveySingleUse]
singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}")
/// [SurveyVerifyEmail]
verifyEmail Json? // deprecated
isVerifyEmailEnabled Boolean @default(false)
isSingleResponsePerEmailEnabled Boolean @default(false)
pin String?
resultShareKey String? @unique
displayPercentage Decimal?
languages SurveyLanguage[]
showLanguageSwitch Boolean?
documents Document[]
followUps SurveyFollowUp[]
@@index([environmentId, updatedAt])
@@index([segmentId])
}
model SurveyFollowUp {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
name String
/// [SurveyFollowUpTrigger]
trigger Json
/// [SurveyFollowUpAction]
action Json
}
enum ActionType {
code
noCode
automatic
}
model ActionClass {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
description String?
type ActionType
key String?
/// [ActionClassNoCodeConfig]
noCodeConfig Json?
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
environmentId String
surveyTriggers SurveyTrigger[]
@@unique([key, environmentId])
@@unique([name, environmentId])
@@index([environmentId, createdAt])
}
enum EnvironmentType {
production
development
}
enum IntegrationType {
googleSheets
notion
airtable
slack
}
model Integration {
id String @id @default(cuid())
type IntegrationType
environmentId String
/// [IntegrationConfig]
config Json
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@unique([type, environmentId])
@@index([environmentId])
}
enum DataMigrationStatus {
pending
applied
failed
}
model DataMigration {
id String @id @default(cuid())
startedAt DateTime @default(now()) @map(name: "started_at")
finishedAt DateTime? @map(name: "finished_at")
name String @unique
status DataMigrationStatus
}
model Environment {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
type EnvironmentType
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId String
widgetSetupCompleted Boolean @default(false)
appSetupCompleted Boolean @default(false)
surveys Survey[]
contacts Contact[]
actionClasses ActionClass[]
attributeKeys ContactAttributeKey[]
apiKeys ApiKey[]
webhooks Webhook[]
tags Tag[]
segments Segment[]
integration Integration[]
documents Document[]
insights Insight[]
@@index([projectId])
}
enum WidgetPlacement {
bottomLeft
bottomRight
topLeft
topRight
center
}
model Project {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
environments Environment[]
brandColor String? // deprecated; use styling.brandColor instead
highlightBorderColor String? // deprecated
/// [Styling]
styling Json @default("{\"allowStyleOverwrite\":true}")
/// [ProjectConfig]
config Json @default("{}")
recontactDays Int @default(7)
linkSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in link surveys
inAppSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in in-app surveys
placement WidgetPlacement @default(bottomRight)
clickOutsideClose Boolean @default(true)
darkOverlay Boolean @default(false)
languages Language[]
/// [Logo]
logo Json?
projectTeams ProjectTeam[]
@@unique([organizationId, name])
@@index([organizationId])
}
model Organization {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
memberships Membership[]
projects Project[]
/// [OrganizationBilling]
billing Json
/// [OrganizationWhitelabel]
whitelabel Json @default("{}")
invites Invite[]
isAIEnabled Boolean @default(false)
teams Team[]
}
enum OrganizationRole {
owner
manager
member
billing
}
enum MembershipRole {
owner
admin
editor
developer
viewer
}
model Membership {
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
accepted Boolean @default(false)
deprecatedRole MembershipRole? //deprecated
role OrganizationRole @default(member)
@@id([userId, organizationId])
@@index([userId])
@@index([organizationId])
}
model Invite {
id String @id @default(uuid())
email String
name String?
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
creator User @relation("inviteCreatedBy", fields: [creatorId], references: [id])
creatorId String
acceptor User? @relation("inviteAcceptedBy", fields: [acceptorId], references: [id], onDelete: Cascade)
acceptorId String?
createdAt DateTime @default(now())
expiresAt DateTime
deprecatedRole MembershipRole? //deprecated
role OrganizationRole @default(member)
teamIds String[] @default([])
@@index([email, organizationId])
@@index([organizationId])
}
model ApiKey {
id String @id @unique @default(cuid())
createdAt DateTime @default(now())
lastUsedAt DateTime?
label String?
hashedKey String @unique()
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
environmentId String
@@index([environmentId])
}
enum IdentityProvider {
email
github
google
azuread
openid
}
model Account {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
type String
provider String
providerAccountId String
access_token String? @db.Text
refresh_token String? @db.Text
expires_at Int?
ext_expires_in Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
@@unique([provider, providerAccountId])
@@index([userId])
}
enum Role {
project_manager
engineer
founder
marketing_specialist
other
}
enum Objective {
increase_conversion
improve_user_retention
increase_user_adoption
sharpen_marketing_messaging
support_sales
other
}
enum Intention {
survey_user_segments
survey_at_specific_point_in_user_journey
enrich_customer_profiles
collect_all_user_feedback_on_one_platform
other
}
model User {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
email String @unique
emailVerified DateTime? @map(name: "email_verified")
imageUrl String?
twoFactorSecret String?
twoFactorEnabled Boolean @default(false)
backupCodes String?
password String?
identityProvider IdentityProvider @default(email)
identityProviderAccountId String?
memberships Membership[]
accounts Account[]
responseNotes ResponseNote[]
groupId String?
invitesCreated Invite[] @relation("inviteCreatedBy")
invitesAccepted Invite[] @relation("inviteAcceptedBy")
role Role?
objective Objective?
/// [UserNotificationSettings]
notificationSettings Json @default("{}")
/// [Locale]
locale String @default("en-US")
surveys Survey[]
teamUsers TeamUser[]
@@index([email])
}
model ShortUrl {
id String @id // generate nanoId in service
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
url String @unique
}
model Segment {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
title String
description String?
isPrivate Boolean @default(true)
/// [SegmentFilter]
filters Json @default("[]")
environmentId String
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
surveys Survey[]
@@unique([environmentId, title])
@@index([environmentId])
}
model Language {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
code String
alias String?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId String
surveyLanguages SurveyLanguage[]
@@unique([projectId, code])
}
model SurveyLanguage {
language Language @relation(fields: [languageId], references: [id], onDelete: Cascade)
languageId String
surveyId String
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
default Boolean @default(false)
enabled Boolean @default(true)
@@id([languageId, surveyId])
@@index([surveyId])
@@index([languageId])
}
enum InsightCategory {
featureRequest
complaint
praise
other
}
model Insight {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
environmentId String
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
category InsightCategory
title String
description String
vector Unsupported("vector(512)")?
documentInsights DocumentInsight[]
}
model DocumentInsight {
documentId String
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
insightId String
insight Insight @relation(fields: [insightId], references: [id], onDelete: Cascade)
@@id([documentId, insightId])
@@index([insightId])
}
enum Sentiment {
positive
negative
neutral
}
model Document {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
environmentId String
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
surveyId String?
survey Survey? @relation(fields: [surveyId], references: [id], onDelete: Cascade)
responseId String?
response Response? @relation(fields: [responseId], references: [id], onDelete: Cascade)
questionId String?
sentiment Sentiment
isSpam Boolean
text String
vector Unsupported("vector(512)")?
documentInsights DocumentInsight[]
@@unique([responseId, questionId])
@@index([createdAt])
}
model Team {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
teamUsers TeamUser[]
projectTeams ProjectTeam[]
@@unique([organizationId, name])
}
enum TeamUserRole {
admin
contributor
}
model TeamUser {
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
teamId String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role TeamUserRole
@@id([teamId, userId])
@@index([userId])
}
enum ProjectTeamPermission {
read
readWrite
manage
}
model ProjectTeam {
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
teamId String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
permission ProjectTeamPermission @default(read)
@@id([projectId, teamId])
@@index([teamId])
}