// 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") } generator client { provider = "prisma-client-js" } // generator dbml { // provider = "prisma-dbml-generator" // } generator zod { provider = "zod-prisma" output = "./zod" imports = "./zod-utils" relationModel = "default" } 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 Attribute { id String @id @default(cuid()) createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") attributeClass AttributeClass @relation(fields: [attributeClassId], references: [id], onDelete: Cascade) attributeClassId String person Person @relation(fields: [personId], references: [id], onDelete: Cascade) personId String value String @@unique([personId, attributeClassId]) } enum AttributeType { code noCode automatic } model AttributeClass { id String @id @default(cuid()) createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") name String description String? archived Boolean @default(false) type AttributeType environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) environmentId String attributes Attribute[] attributeFilters SurveyAttributeFilter[] @@unique([name, environmentId]) @@index([environmentId, createdAt]) @@index([environmentId, archived]) } model Person { 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 Attribute[] displays Display[] actions Action[] @@unique([environmentId, userId]) @@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 person Person? @relation(fields: [personId], references: [id], onDelete: Cascade) personId String? notes ResponseNote[] /// @zod.custom(imports.ZResponseData) /// [ResponseData] data Json @default("{}") /// @zod.custom(imports.ZResponseTtc) /// [ResponseTtc] ttc Json @default("{}") /// @zod.custom(imports.ZResponseMeta) /// [ResponseMeta] meta Json @default("{}") tags TagsOnResponses[] /// @zod.custom(imports.ZResponsePersonAttributes) /// [ResponsePersonAttributes] personAttributes Json? // singleUseId, used to prevent multiple responses singleUseId String? language String? @@unique([surveyId, singleUseId]) @@index([surveyId, createdAt]) // to determine monthly response count @@index([personId, 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 person Person? @relation(fields: [personId], references: [id], onDelete: Cascade) personId String? responseId String? @unique status DisplayStatus? @@index([surveyId]) @@index([personId, 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") attributeClass AttributeClass @relation(fields: [attributeClassId], references: [id], onDelete: Cascade) attributeClassId String survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade) surveyId String condition SurveyAttributeFilterCondition value String @@unique([surveyId, attributeClassId]) @@index([surveyId]) @@index([attributeClassId]) } 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) /// @zod.custom(imports.ZSurveyWelcomeCard) /// [SurveyWelcomeCard] welcomeCard Json @default("{\"enabled\": false}") /// @zod.custom(imports.ZSurveyQuestions) /// [SurveyQuestions] questions Json @default("[]") /// @zod.custom(imports.ZSurveyEnding) /// [SurveyEnding] endings Json[] @default([]) thankYouCard Json? //deprecated /// @zod.custom(imports.ZSurveyHiddenFields) /// [SurveyHiddenFields] hiddenFields Json @default("{\"enabled\": false}") responses Response[] displayOption displayOptions @default(displayOnce) recontactDays Int? displayLimit Int? triggers SurveyTrigger[] /// @zod.custom(imports.ZSurveyInlineTriggers) /// [SurveyInlineTriggers] inlineTriggers Json? attributeFilters SurveyAttributeFilter[] displays Display[] autoClose Int? autoComplete Int? delay Int @default(0) runOnDate DateTime? closeOnDate DateTime? /// @zod.custom(imports.ZSurveyClosedMessage) /// [SurveyClosedMessage] surveyClosedMessage Json? segmentId String? segment Segment? @relation(fields: [segmentId], references: [id]) /// @zod.custom(imports.ZSurveyProductOverwrites) /// [SurveyProductOverwrites] productOverwrites Json? /// @zod.custom(imports.ZSurveyStyling) /// [SurveyStyling] styling Json? /// @zod.custom(imports.ZSurveySingleUse) /// [SurveySingleUse] singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}") /// @zod.custom(imports.ZSurveyVerifyEmail) /// [SurveyVerifyEmail] verifyEmail Json? // deprecated isVerifyEmailEnabled Boolean @default(false) pin String? resultShareKey String? @unique displayPercentage Decimal? languages SurveyLanguage[] showLanguageSwitch Boolean? @@index([environmentId, updatedAt]) @@index([segmentId]) } 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? /// @zod.custom(imports.ZActionClassNoCodeConfig) /// [ActionClassNoCodeConfig] noCodeConfig Json? environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) environmentId String surveyTriggers SurveyTrigger[] actions Action[] @@unique([key, environmentId]) @@unique([name, environmentId]) @@index([environmentId, createdAt]) } model Action { id String @id @default(cuid()) createdAt DateTime @default(now()) @map(name: "created_at") actionClass ActionClass @relation(fields: [actionClassId], references: [id], onDelete: Cascade) actionClassId String person Person @relation(fields: [personId], references: [id], onDelete: Cascade) personId String /// @zod.custom(imports.ZActionProperties) /// @zod.custom(imports.ZActionProperties) /// [ActionProperties] properties Json @default("{}") @@index([personId, actionClassId, createdAt]) @@index([actionClassId, createdAt]) @@index([personId, createdAt]) } enum EnvironmentType { production development } enum IntegrationType { googleSheets notion airtable slack } model Integration { id String @id @default(cuid()) type IntegrationType environmentId String /// @zod.custom(imports.ZIntegrationConfig) /// [IntegrationConfig] config Json environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) @@unique([type, environmentId]) @@index([environmentId]) } model Environment { id String @id @default(cuid()) createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") type EnvironmentType product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String widgetSetupCompleted Boolean @default(false) appSetupCompleted Boolean @default(false) websiteSetupCompleted Boolean @default(false) surveys Survey[] people Person[] actionClasses ActionClass[] attributeClasses AttributeClass[] apiKeys ApiKey[] webhooks Webhook[] tags Tag[] segments Segment[] integration Integration[] @@index([productId]) } enum WidgetPlacement { bottomLeft bottomRight topLeft topRight center } model Product { 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 /// @zod.custom(imports.ZProductStyling) /// [Styling] styling Json @default("{\"allowStyleOverwrite\":true}") /// @zod.custom(imports.ZProductConfig) /// [ProductConfig] 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[] /// @zod.custom(imports.ZLogo) /// [Logo] logo Json? @@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[] products Product[] /// @zod.custom(imports.ZTeamBilling) /// [TeamBilling] billing Json invites Invite[] } 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) role MembershipRole @@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? accepted Boolean @default(false) createdAt DateTime @default(now()) expiresAt DateTime role MembershipRole @default(admin) @@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? /// @zod.custom(imports.ZUserNotificationSettings) /// @zod.custom(imports.ZUserNotificationSettings) /// [UserNotificationSettings] notificationSettings Json @default("{}") surveys Survey[] @@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) /// @zod.custom(imports.ZSegmentFilters) /// [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? product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String surveyLanguages SurveyLanguage[] @@unique([productId, 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]) }