Prisma Schema - Database Models

I'm waiting for this issue to be fixed to split schemas into module files: Support for splitting Prisma schema into multiple files #2377.

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model AppConfiguration {
  id                                     String   @id @default(cuid())
  updatedAt                              DateTime @updatedAt
  name                                   String
  url                                    String
  authRequireEmailVerification           Boolean  @default(false)
  authRequireOrganization                Boolean  @default(true)
  authRequireName                        Boolean  @default(true)
  authRecaptchaSiteKey                   String?
  analyticsEnabled                       Boolean  @default(true)
  analyticsSimpleAnalytics               Boolean  @default(false)
  analyticsPlausibleAnalytics            Boolean  @default(false)
  analyticsGoogleAnalyticsTrackingId     String?
  subscriptionRequired                   Boolean  @default(true)
  subscriptionAllowSubscribeBeforeSignUp Boolean  @default(true)
  subscriptionAllowSignUpBeforeSubscribe Boolean  @default(true)
  cookiesEnabled                         Boolean  @default(false)
}

model AppCookie {
  id          String  @id @default(cuid())
  category    Int
  name        String
  description String
  enabled     Boolean @default(true)
  expiry      String?
  domain      String?
  type        String?
  href        String?
}

model User {
  id                         String                       @id @default(cuid())
  createdAt                  DateTime                     @default(now())
  updatedAt                  DateTime                     @updatedAt
  email                      String                       @unique
  passwordHash               String
  firstName                  String
  lastName                   String
  avatar                     String?
  phone                      String?
  defaultTenantId            String?
  verifyToken                String?
  githubId                   String?                      @unique
  googleId                   String?                      @unique
  locale                     String?
  active                     Boolean                      @default(false)
  admin                      AdminUser?
  createdApiKeys             ApiKey[]
  workflowSteps              EntityWorkflowStepAssignee[]
  createdGroups              Group[]
  groups                     GroupUser[]
  createdLinkedAccounts      LinkedAccount[]
  logs                       Log[]
  createdRows                Row[]
  createdRowComments         RowComment[]
  createdRowCommentReactions RowCommentReaction[]
  createdEntityViews         EntityView[]                 @relation("createdByUser")
  rowPermissions             RowPermission[]
  assignedTasks              RowTask[]                    @relation("assignedToUser")
  completedTasks             RowTask[]                    @relation("completedByUser")
  createdRowTasks            RowTask[]                    @relation("createdByUser")
  workflowTransitions        RowWorkflowTransition[]
  tenants                    TenantUser[]
  invitation                 TenantUserInvitation?
  roles                      UserRole[]
  readEmails                 EmailRead[]
  entityViews                EntityView[]
  onboardingSessions         OnboardingSession[]
  tags                       TagUser[]
}

model AdminUser {
  userId String @unique
  role   Int
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model Tenant {
  id                       String                       @id @default(cuid())
  createdAt                DateTime                     @default(now())
  updatedAt                DateTime                     @updatedAt
  slug                     String                       @unique
  name                     String
  icon                     String?
  subscriptionId           String?
  active                   Boolean                      @default(false)
  apiKeys                  ApiKey[]
  workflowSteps            EntityWorkflowStepAssignee[]
  groups                   Group[]
  asClientLinkedAccounts   LinkedAccount[]              @relation("clientTenant")
  createdLinkedAccounts    LinkedAccount[]              @relation("createdByTenant")
  asProviderLinkedAccounts LinkedAccount[]              @relation("providerTenant")
  logs                     Log[]
  rows                     Row[]
  rowPermissions           RowPermission[]
  subscription             TenantSubscription?
  users                    TenantUser[]
  invitations              TenantUserInvitation[]
  userRoles                UserRole[]
  inboundAddresses         TenantInboundAddress[]
  events                   Event[]
  fromRegistration         Registration?
  entityViews              EntityView[]
  emailSenders             EmailSender[]
  campaigns                Campaign[]
  outboundEmails           OutboundEmail[]
  tags                     TagTenant[]
  onboardingSessions       OnboardingSession[]
}

model Registration {
  id                          String   @id @default(cuid())
  createdAt                   DateTime @default(now())
  email                       String   @unique
  firstName                   String
  lastName                    String
  token                       String   @unique
  ipAddress                   String?
  company                     String?
  selectedSubscriptionPriceId String?
  createdTenantId             String?  @unique
  createdTenant               Tenant?  @relation(fields: [createdTenantId], references: [id], onDelete: Cascade)
}

model Blacklist {
  id               String   @id @default(cuid())
  createdAt        DateTime @default(now())
  type             String // email, domain, ip
  value            String
  active           Boolean  @default(true)
  registerAttempts Int      @default(0)
}

model TenantSubscription {
  id               String                      @id @default(cuid())
  tenantId         String                      @unique
  stripeCustomerId String?
  tenant           Tenant                      @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  products         TenantSubscriptionProduct[]
}

model TenantSubscriptionProduct {
  id                    String                           @id @default(cuid())
  createdAt             DateTime                         @default(now())
  tenantSubscriptionId  String
  subscriptionProductId String
  cancelledAt           DateTime?
  endsAt                DateTime?
  stripeSubscriptionId  String?
  quantity              Int?
  fromCheckoutSessionId String?
  tenantSubscription    TenantSubscription               @relation(fields: [tenantSubscriptionId], references: [id], onDelete: Cascade)
  subscriptionProduct   SubscriptionProduct              @relation(fields: [subscriptionProductId], references: [id])
  prices                TenantSubscriptionProductPrice[]
}

model TenantSubscriptionProductPrice {
  id                            String                          @id @default(cuid())
  tenantSubscriptionProductId   String
  tenantSubscriptionProduct     TenantSubscriptionProduct       @relation(fields: [tenantSubscriptionProductId], references: [id], onDelete: Cascade)
  subscriptionPriceId           String?
  subscriptionPrice             SubscriptionPrice?              @relation(fields: [subscriptionPriceId], references: [id])
  subscriptionUsageBasedPriceId String?
  subscriptionUsageBasedPrice   SubscriptionUsageBasedPrice?    @relation(fields: [subscriptionUsageBasedPriceId], references: [id])
  usageRecords                  TenantSubscriptionUsageRecord[]
}

model TenantSubscriptionUsageRecord {
  id                               String                         @id @default(cuid())
  tenantSubscriptionProductPriceId String
  tenantSubscriptionProductPrice   TenantSubscriptionProductPrice @relation(fields: [tenantSubscriptionProductPriceId], references: [id], onDelete: Cascade)
  timestamp                        Int
  quantity                         Int
  stripeSubscriptionItemId         String?
}

model CheckoutSessionStatus {
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  id              String   @unique
  pending         Boolean  @default(true) // has not added products to tenant
  email           String
  fromUrl         String
  fromUserId      String?
  fromTenantId    String?
  createdUserId   String?
  createdTenantId String?
}

model TenantUser {
  id        String           @id @default(cuid())
  createdAt DateTime         @default(now())
  tenantId  String
  userId    String
  type      Int
  joined    Int
  status    Int
  tenant    Tenant           @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user      User             @relation(fields: [userId], references: [id], onDelete: Cascade)
  roles     TenantUserRole[]
}

model Role {
  id               String                       @id @default(cuid())
  createdAt        DateTime                     @default(now())
  updatedAt        DateTime                     @updatedAt
  name             String                       @unique
  description      String
  type             String
  assignToNewUsers Boolean
  isDefault        Boolean
  order            Int
  workflowSteps    EntityWorkflowStepAssignee[]
  permissions      RolePermission[]
  rowPermissions   RowPermission[]
  users            UserRole[]
}

model Permission {
  id          String           @id @default(cuid())
  createdAt   DateTime         @default(now())
  updatedAt   DateTime         @updatedAt
  name        String           @unique
  description String
  type        String
  isDefault   Boolean
  order       Int
  inRoles     RolePermission[]
  entityId    String?
  entity      Entity?          @relation(fields: [entityId], references: [id], onDelete: Cascade)
}

model RolePermission {
  id           String     @id @default(cuid())
  roleId       String
  permissionId String
  permission   Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
  role         Role       @relation(fields: [roleId], references: [id], onDelete: Cascade)
}

model UserRole {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  userId    String
  roleId    String
  tenantId  String?
  role      Role     @relation(fields: [roleId], references: [id], onDelete: Cascade)
  tenant    Tenant?  @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([userId, roleId, tenantId])
}

model Group {
  id              String                       @id @default(cuid())
  createdAt       DateTime                     @default(now())
  createdByUserId String
  tenantId        String?
  name            String
  description     String
  color           Int
  createdByUser   User                         @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
  tenant          Tenant?                      @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  workflowSteps   EntityWorkflowStepAssignee[]
  users           GroupUser[]
  rowPermissions  RowPermission[]
}

model GroupUser {
  id      String @id @default(cuid())
  groupId String
  userId  String
  group   Group  @relation(fields: [groupId], references: [id], onDelete: Cascade)
  user    User   @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model TenantUserRole {
  id           String     @id @default(cuid())
  tenantUserId String
  order        Int
  name         String
  tenantUser   TenantUser @relation(fields: [tenantUserId], references: [id], onDelete: Cascade)
}

model TenantUserInvitation {
  id            String  @id @default(cuid())
  tenantId      String
  email         String
  firstName     String
  lastName      String
  type          Int
  pending       Boolean
  createdUserId String? @unique
  user          User?   @relation(fields: [createdUserId], references: [id], onDelete: Cascade)
  tenant        Tenant  @relation(fields: [tenantId], references: [id], onDelete: Cascade)
}

model LinkedAccount {
  id                String   @id @default(cuid())
  createdAt         DateTime @default(now())
  createdByUserId   String
  createdByTenantId String
  providerTenantId  String
  clientTenantId    String
  status            Int
  clientTenant      Tenant   @relation("clientTenant", fields: [clientTenantId], references: [id], onDelete: Cascade)
  createdByTenant   Tenant   @relation("createdByTenant", fields: [createdByTenantId], references: [id], onDelete: Cascade)
  createdByUser     User     @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
  providerTenant    Tenant   @relation("providerTenant", fields: [providerTenantId], references: [id], onDelete: Cascade)
}

model ApiKey {
  id              String                  @id @default(cuid())
  createdAt       DateTime                @default(now())
  createdByUserId String
  tenantId        String
  key             String                  @default(uuid())
  alias           String
  expires         DateTime?
  active          Boolean
  createdByUser   User                    @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
  tenant          Tenant                  @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  entities        ApiKeyEntity[]
  apiKeyLogs      ApiKeyLog[]
  logs            Log[]
  createdRows     Row[]
  transitions     RowWorkflowTransition[]

  @@unique([tenantId, alias])
}

model ApiKeyEntity {
  id       String  @id @default(cuid())
  apiKeyId String
  entityId String
  create   Boolean
  read     Boolean
  update   Boolean
  delete   Boolean
  apiKey   ApiKey  @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
  entity   Entity  @relation(fields: [entityId], references: [id], onDelete: Cascade)
}

model Log {
  id                   String                 @id @default(cuid())
  createdAt            DateTime               @default(now())
  tenantId             String?
  userId               String?
  apiKeyId             String?
  rowId                String?
  url                  String
  action               String
  details              String?
  commentId            String?
  workflowTransitionId String?
  apiKey               ApiKey?                @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
  comment              RowComment?            @relation(fields: [commentId], references: [id])
  row                  Row?                   @relation(fields: [rowId], references: [id])
  tenant               Tenant?                @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user                 User?                  @relation(fields: [userId], references: [id], onDelete: Cascade)
  workflowTransition   RowWorkflowTransition? @relation(fields: [workflowTransitionId], references: [id])
  webhookLogs          EntityWebhookLog[]
}

model ApiKeyLog {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  apiKeyId  String?
  ip        String
  endpoint  String
  method    String
  params    String
  status    Int?
  error     String?
  apiKey    ApiKey?  @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
}

model SubscriptionProduct {
  id               String                        @id @default(cuid())
  stripeId         String
  order            Int
  title            String
  active           Boolean
  model            Int
  public           Boolean
  groupTitle       String?
  groupDescription String?
  description      String?
  badge            String?
  prices           SubscriptionPrice[]
  features         SubscriptionFeature[]
  tenantProducts   TenantSubscriptionProduct[]
  usageBasedPrices SubscriptionUsageBasedPrice[]
}

model SubscriptionPrice {
  id                    String                           @id @default(cuid())
  subscriptionProductId String
  subscriptionProduct   SubscriptionProduct              @relation(fields: [subscriptionProductId], references: [id], onDelete: Cascade)
  stripeId              String
  type                  Int
  billingPeriod         Int
  price                 Decimal
  currency              String
  trialDays             Int
  active                Boolean
  tenantProductPrices   TenantSubscriptionProductPrice[]
}

model SubscriptionUsageBasedPrice {
  id                    String                           @id @default(cuid())
  subscriptionProductId String
  subscriptionProduct   SubscriptionProduct              @relation(fields: [subscriptionProductId], references: [id], onDelete: Cascade)
  stripeId              String
  billingPeriod         Int
  currency              String
  unit                  String
  unitTitle             String
  unitTitlePlural       String
  usageType             String
  aggregateUsage        String
  tiersMode             String
  billingScheme         String
  tiers                 SubscriptionUsageBasedTier[]
  tenantProductPrices   TenantSubscriptionProductPrice[]
}

model SubscriptionUsageBasedTier {
  id                            String                      @id @default(cuid())
  subscriptionUsageBasedPriceId String
  subscriptionUsageBasedPrice   SubscriptionUsageBasedPrice @relation(fields: [subscriptionUsageBasedPriceId], references: [id], onDelete: Cascade)
  from                          Int
  to                            Int?
  perUnitPrice                  Decimal?
  flatFeePrice                  Decimal?
}

model SubscriptionFeature {
  id                    String              @id @default(cuid())
  subscriptionProductId String
  order                 Int
  title                 String
  name                  String
  type                  Int
  value                 Int
  href                  String?
  subscriptionProduct   SubscriptionProduct @relation(fields: [subscriptionProductId], references: [id], onDelete: Cascade)
}

model BlogAuthor {
  id        String     @id @default(cuid())
  createdAt DateTime   @default(now())
  slug      String     @unique
  firstName String
  lastName  String
  image     String
  url       String
  posts     BlogPost[]
}

model BlogCategory {
  id        String     @id @default(cuid())
  createdAt DateTime   @default(now())
  name      String     @unique
  color     Int
  posts     BlogPost[]
}

model BlogTag {
  id        String        @id @default(cuid())
  createdAt DateTime      @default(now())
  name      String        @unique
  color     Int
  posts     BlogPostTag[]
}

model BlogPostTag {
  id     String   @id @default(cuid())
  postId String
  tagId  String
  post   BlogPost @relation(fields: [postId], references: [id], onDelete: Cascade)
  tag    BlogTag  @relation(fields: [tagId], references: [id], onDelete: Cascade)
}

model BlogPost {
  id          String        @id @default(cuid())
  createdAt   DateTime      @default(now())
  slug        String        @unique
  title       String
  description String
  date        DateTime
  image       String
  content     String
  readingTime String
  published   Boolean
  authorId    String
  categoryId  String
  author      BlogAuthor    @relation(fields: [authorId], references: [id], onDelete: Cascade)
  category    BlogCategory  @relation(fields: [categoryId], references: [id], onDelete: Cascade)
  tags        BlogPostTag[]
}

// <--- START: Entities --->

// TODO: Wrap entities in modules (e.g. CRM, HelpDesk...)
model Module {
  id          String   @id @default(cuid())
  type        String   @default("app") // app, admin, all
  order       Int
  name        String   @unique
  title       String
  description String
  icon        String
  entities    Entity[]
}

model Entity {
  id                 String                       @id @default(cuid())
  createdAt          DateTime                     @default(now())
  updatedAt          DateTime                     @updatedAt
  moduleId           String?
  name               String                       @unique
  slug               String                       @unique
  order              Int
  prefix             String                       @unique
  type               String                       @default("app") // app, admin, all
  title              String
  titlePlural        String
  isAutogenerated    Boolean
  hasApi             Boolean
  icon               String
  active             Boolean
  showInSidebar      Boolean                      @default(true)
  hasTags            Boolean                      @default(true)
  hasComments        Boolean                      @default(true)
  hasTasks           Boolean                      @default(true)
  hasWorkflow        Boolean                      @default(false)
  defaultVisibility  String                       @default("private")
  createdPermissions Permission[]
  apiKeys            ApiKeyEntity[]
  tags               EntityTag[]
  permissions        EntityTenantUserPermission[]
  webhooks           EntityWebhook[]
  workflowStates     EntityWorkflowState[]
  workflowSteps      EntityWorkflowStep[]
  properties         Property[]
  rows               Row[]
  views              EntityView[]
  parentEntities     EntityRelationship[]         @relation(name: "childEntities")
  childEntities      EntityRelationship[]         @relation(name: "parentEntities")
  module             Module?                      @relation(fields: [moduleId], references: [id])
}

model Property {
  id               String               @id @default(cuid())
  entityId         String
  order            Int
  name             String
  title            String
  type             Int
  formula          String?
  isDynamic        Boolean
  isDefault        Boolean
  isRequired       Boolean
  isHidden         Boolean
  isDisplay        Boolean              @default(false)
  isUnique         Boolean              @default(false) // TODO: Validate uniqueness
  attributes       PropertyAttribute[]
  entity           Entity               @relation(fields: [entityId], references: [id], onDelete: Cascade)
  options          PropertyOption[]
  values           RowValue[]
  inViewProperties EntityViewProperty[]
  inViewGroupBy    EntityView[]

  @@unique([entityId, order])
  @@unique([entityId, name])
  @@unique([entityId, title])
}

model EntityView {
  id                    String               @id @default(cuid())
  createdAt             DateTime             @default(now())
  updatedAt             DateTime             @updatedAt
  createdByUserId       String?
  createdByUser         User?                @relation(name: "createdByUser", fields: [createdByUserId], references: [id])
  entityId              String
  entity                Entity               @relation(fields: [entityId], references: [id], onDelete: Cascade)
  tenantId              String?
  tenant                Tenant?              @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  userId                String?
  user                  User?                @relation(fields: [userId], references: [id], onDelete: Cascade)
  layout                String               @default("table") // table, board, calendar, list, gallery...
  order                 Int
  name                  String
  title                 String
  pageSize              Int
  isDefault             Boolean
  columns               Int?
  properties            EntityViewProperty[]
  filters               EntityViewFilter[]
  sort                  EntityViewSort[]
  groupByWorkflowStates Boolean              @default(false)
  groupByPropertyId     String?
  groupByProperty       Property?            @relation(fields: [groupByPropertyId], references: [id], onDelete: Cascade)
}

model EntityViewProperty {
  id           String     @id @default(cuid())
  entityViewId String
  entityView   EntityView @relation(fields: [entityViewId], references: [id], onDelete: Cascade)
  propertyId   String?
  property     Property?  @relation(fields: [propertyId], references: [id], onDelete: Cascade)
  name         String? // if not a property, e.g. "default.folio"
  order        Int

  @@unique([entityViewId, propertyId])
}

model EntityViewFilter {
  id           String     @id @default(cuid())
  entityViewId String
  entityView   EntityView @relation(fields: [entityViewId], references: [id], onDelete: Cascade)
  match        String     @default("and") // and, or
  name         String
  condition    String // is, isNot, contains, doesNotContain...
  value        String
}

model EntityViewSort {
  id           String     @id @default(cuid())
  entityViewId String
  entityView   EntityView @relation(fields: [entityViewId], references: [id], onDelete: Cascade)
  name         String
  asc          Boolean
  order        Int
}

model PropertyAttribute {
  id         String   @id @default(cuid())
  propertyId String
  property   Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
  name       String // pattern, min, max, step, rows, defaultValue, maxSize, acceptFileTypes, uppercase...
  value      String

  @@unique([propertyId, name])
}

model PropertyOption {
  id         String              @id @default(cuid())
  propertyId String
  order      Int
  value      String
  name       String?
  color      Int                 @default(0)
  property   Property            @relation(fields: [propertyId], references: [id], onDelete: Cascade)
  values     RowValueSelection[]
}

model EntityTag {
  id       String   @id @default(cuid())
  entityId String
  value    String
  color    Int
  entity   Entity   @relation(fields: [entityId], references: [id], onDelete: Cascade)
  rowTags  RowTag[]
}

model EntityTenantUserPermission {
  id       String @id @default(cuid())
  entityId String
  level    Int
  entity   Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
}

model EntityWebhook {
  id       String             @id @default(cuid())
  entityId String
  action   String
  method   String
  endpoint String
  entity   Entity             @relation(fields: [entityId], references: [id], onDelete: Cascade)
  logs     EntityWebhookLog[]
}

model EntityWebhookLog {
  id        String        @id @default(cuid())
  webhookId String
  logId     String
  status    Int
  error     String?
  log       Log           @relation(fields: [logId], references: [id], onDelete: Cascade)
  webhook   EntityWebhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
}

model EntityWorkflowState {
  id           String               @id @default(cuid())
  entityId     String
  order        Int
  name         String
  title        String
  color        Int
  canUpdate    Boolean
  canDelete    Boolean
  emailSubject String
  emailBody    String
  progress     Int?
  entity       Entity               @relation(fields: [entityId], references: [id], onDelete: Cascade)
  fromStates   EntityWorkflowStep[] @relation("fromState")
  toStates     EntityWorkflowStep[] @relation("toState")
  rows         Row[]
}

model EntityWorkflowStep {
  id          String                       @id @default(cuid())
  entityId    String
  action      String
  fromStateId String
  toStateId   String
  assignTo    String                       @default("private")
  entity      Entity                       @relation(fields: [entityId], references: [id], onDelete: Cascade)
  fromState   EntityWorkflowState          @relation("fromState", fields: [fromStateId], references: [id], onDelete: Cascade)
  toState     EntityWorkflowState          @relation("toState", fields: [toStateId], references: [id], onDelete: Cascade)
  assignees   EntityWorkflowStepAssignee[]
  transitions RowWorkflowTransition[]
}

model EntityWorkflowStepAssignee {
  id       String             @id @default(cuid())
  stepId   String
  tenantId String?
  roleId   String?
  groupId  String?
  userId   String?
  group    Group?             @relation(fields: [groupId], references: [id], onDelete: Cascade)
  role     Role?              @relation(fields: [roleId], references: [id], onDelete: Cascade)
  step     EntityWorkflowStep @relation(fields: [stepId], references: [id], onDelete: Cascade)
  tenant   Tenant?            @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user     User?              @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model EntityRelationship {
  id       String            @id @default(cuid())
  parentId String
  parent   Entity            @relation(name: "parentEntities", fields: [parentId], references: [id], onDelete: Cascade)
  childId  String
  child    Entity            @relation(name: "childEntities", fields: [childId], references: [id], onDelete: Cascade)
  multiple Boolean
  required Boolean
  rows     RowRelationship[]

  @@unique([parentId, childId])
}

model SampleCustomEntity {
  rowId         String   @unique
  row           Row      @relation(fields: [rowId], references: [id], onDelete: Cascade)
  customText    String
  customNumber  Decimal
  customDate    DateTime
  customBoolean Boolean
  customSelect  String
}

model RowRelationship {
  id             String             @id @default(cuid())
  relationshipId String
  relationship   EntityRelationship @relation(fields: [relationshipId], references: [id], onDelete: Cascade)
  parentId       String
  parent         Row                @relation(name: "parentRow", fields: [parentId], references: [id], onDelete: Cascade)
  childId        String
  child          Row                @relation(name: "childRow", fields: [childId], references: [id], onDelete: Cascade)

  @@unique([parentId, childId])
}

model Row {
  id                 String                  @id @default(cuid())
  createdAt          DateTime                @default(now())
  updatedAt          DateTime                @updatedAt
  entityId           String
  tenantId           String?
  folio              Int
  visibility         String
  createdByUserId    String?
  createdByApiKeyId  String?
  canComment         Boolean                 @default(true)
  canUpdate          Boolean                 @default(true)
  canDelete          Boolean                 @default(true)
  workflowStateId    String?
  createdByApiKey    ApiKey?                 @relation(fields: [createdByApiKeyId], references: [id])
  createdByUser      User?                   @relation(fields: [createdByUserId], references: [id])
  entity             Entity                  @relation(fields: [entityId], references: [id])
  tenant             Tenant?                 @relation(fields: [tenantId], references: [id])
  workflowState      EntityWorkflowState?    @relation(fields: [workflowStateId], references: [id])
  logs               Log[]
  comments           RowComment[]
  permissions        RowPermission[]
  tags               RowTag[]
  tasks              RowTask[]
  values             RowValue[]
  transitions        RowWorkflowTransition[]
  childRows          RowRelationship[]       @relation("parentRow")
  parentRows         RowRelationship[]       @relation("childRow")
  sampleCustomEntity SampleCustomEntity?
  outboundEmails     OutboundEmail[]
}

model RowValue {
  id           String              @id @default(cuid())
  rowId        String
  propertyId   String
  textValue    String?
  numberValue  Decimal?
  dateValue    DateTime?
  booleanValue Boolean?
  selection    RowValueSelection[] // TODO: MULTI SELECT
  property     Property            @relation(fields: [propertyId], references: [id], onDelete: Cascade)
  row          Row                 @relation(fields: [rowId], references: [id], onDelete: Cascade)
  media        RowMedia[]
}

// TODO: MULTI SELECT
model RowValueSelection {
  id               String         @id @default(cuid())
  rowValueId       String
  propertyOptionId String
  propertyOption   PropertyOption @relation(fields: [propertyOptionId], references: [id], onDelete: Cascade)
  rowValue         RowValue       @relation(fields: [rowValueId], references: [id], onDelete: Cascade)
}

model RowPermission {
  id       String  @id @default(cuid())
  rowId    String
  tenantId String?
  roleId   String?
  groupId  String?
  userId   String?
  group    Group?  @relation(fields: [groupId], references: [id], onDelete: Cascade)
  role     Role?   @relation(fields: [roleId], references: [id], onDelete: Cascade)
  row      Row     @relation(fields: [rowId], references: [id], onDelete: Cascade)
  tenant   Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user     User?   @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model RowMedia {
  id              String   @id @default(cuid())
  rowValueId      String
  title           String
  name            String
  file            String
  type            String
  publicUrl       String?
  storageBucket   String?
  storageProvider String?
  rowValue        RowValue @relation(fields: [rowValueId], references: [id], onDelete: Cascade)
}

model RowTag {
  id    String    @id @default(cuid())
  rowId String
  tagId String
  row   Row       @relation(fields: [rowId], references: [id], onDelete: Cascade)
  tag   EntityTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
}

model RowComment {
  id              String               @id @default(cuid())
  createdAt       DateTime             @default(now())
  createdByUserId String
  rowId           String
  value           String
  isDeleted       Boolean?
  createdByUser   User                 @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
  row             Row                  @relation(fields: [rowId], references: [id], onDelete: Cascade)
  logs            Log[]
  reactions       RowCommentReaction[]
}

model RowCommentReaction {
  id              String     @id @default(cuid())
  createdAt       DateTime   @default(now())
  createdByUserId String
  rowCommentId    String
  reaction        String
  createdByUser   User       @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
  rowComment      RowComment @relation(fields: [rowCommentId], references: [id], onDelete: Cascade)
}

model RowTask {
  id                String    @id @default(cuid())
  createdAt         DateTime  @default(now())
  createdByUserId   String
  rowId             String
  title             String
  description       String
  completed         Boolean
  completedAt       DateTime?
  completedByUserId String?
  assignedToUserId  String?
  deadline          DateTime?
  assignedToUser    User?     @relation("assignedToUser", fields: [assignedToUserId], references: [id], onDelete: Cascade)
  completedByUser   User?     @relation("completedByUser", fields: [completedByUserId], references: [id], onDelete: Cascade)
  createdByUser     User      @relation("createdByUser", fields: [createdByUserId], references: [id], onDelete: Cascade)
  row               Row       @relation(fields: [rowId], references: [id], onDelete: Cascade)
}

model RowWorkflowTransition {
  id                      String               @id @default(cuid())
  createdAt               DateTime             @default(now())
  byUserId                String?
  byApiKeyId              String?
  byEmailId               String?
  byEventWebhookAttemptId String?
  rowId                   String
  workflowStepId          String
  byUser                  User?                @relation(fields: [byUserId], references: [id], onDelete: Cascade)
  byApiKey                ApiKey?              @relation(fields: [byApiKeyId], references: [id], onDelete: Cascade)
  byEmail                 Email?               @relation(fields: [byEmailId], references: [id], onDelete: Cascade)
  byEventWebhookAttempt   EventWebhookAttempt? @relation(fields: [byEventWebhookAttemptId], references: [id], onDelete: Cascade)
  row                     Row                  @relation(fields: [rowId], references: [id], onDelete: Cascade)
  workflowStep            EntityWorkflowStep   @relation(fields: [workflowStepId], references: [id], onDelete: Cascade)
  logs                    Log[]
}

// <--- END: Entities --->

// <--- START: Inbound Emails --->

model TenantInboundAddress {
  id       String  @id @default(cuid())
  tenantId String
  tenant   Tenant  @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  address  String  @unique
  email    Email[]
}

model Email {
  id                     String                  @id @default(cuid())
  tenantInboundAddressId String?
  tenantInboundAddress   TenantInboundAddress?   @relation(fields: [tenantInboundAddressId], references: [id], onDelete: Cascade)
  messageId              String                  @unique
  type                   String // inbound, outbound
  date                   DateTime
  subject                String
  fromEmail              String
  fromName               String?
  toEmail                String
  toName                 String?
  textBody               String
  htmlBody               String
  reads                  EmailRead[]
  attachments            EmailAttachment[]
  cc                     EmailCc[]
  transitions            RowWorkflowTransition[]
}

model EmailRead {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  emailId   String
  email     Email    @relation(fields: [emailId], references: [id], onDelete: Cascade)
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model EmailCc {
  id      String  @id @default(cuid())
  emailId String
  toEmail String
  toName  String?
  email   Email   @relation(fields: [emailId], references: [id], onDelete: Cascade)
}

model EmailAttachment {
  id              String  @id @default(cuid())
  emailId         String
  name            String
  type            String
  length          Int
  content         String
  publicUrl       String?
  storageBucket   String?
  storageProvider String?
  email           Email   @relation(fields: [emailId], references: [id], onDelete: Cascade)
}

// <--- END: Inbound Emails --->

// <--- START: Events --->

model Event {
  id        String                @id @default(cuid())
  createdAt DateTime              @default(now())
  tenantId  String?
  tenant    Tenant?               @relation(fields: [tenantId], references: [id])
  name      String
  data      String
  resource  String?
  attempts  EventWebhookAttempt[]
}

model EventWebhookAttempt {
  id          String                  @id @default(cuid())
  createdAt   DateTime                @default(now())
  startedAt   DateTime?
  finishedAt  DateTime?
  eventId     String
  event       Event                   @relation(fields: [eventId], references: [id], onDelete: Cascade)
  endpoint    String
  success     Boolean?
  status      Int?
  message     String?
  body        String?
  transitions RowWorkflowTransition[]
}

// <--- END: Events --->

// <!--- Analytics --->

model AnalyticsSettings {
  id          String  @id @default(cuid())
  public      Boolean @default(false)
  ignorePages String
  onlyPages   String
}

model AnalyticsUniqueVisitor {
  id             String              @id @default(cuid())
  createdAt      DateTime            @default(now())
  cookie         String              @unique
  via            String?
  httpReferrer   String?
  browser        String?
  browserVersion String?
  os             String?
  osVersion      String?
  device         String?
  source         String?
  medium         String?
  campaign       String?
  content        String?
  term           String?
  country        String?
  city           String?
  fromUrl        String?
  fromRoute      String?
  pageViews      AnalyticsPageView[]
  events         AnalyticsEvent[]
}

model AnalyticsPageView {
  id              String                 @id @default(cuid())
  createdAt       DateTime               @default(now())
  uniqueVisitorId String
  uniqueVisitor   AnalyticsUniqueVisitor @relation(fields: [uniqueVisitorId], references: [id], onDelete: Cascade)
  url             String
  route           String?
}

model AnalyticsEvent {
  id              String                 @id @default(cuid())
  createdAt       DateTime               @default(now())
  uniqueVisitorId String
  uniqueVisitor   AnalyticsUniqueVisitor @relation(fields: [uniqueVisitorId], references: [id], onDelete: Cascade)
  action          String
  category        String?
  label           String?
  value           String?
  url             String?
  route           String?
}

// <!--- Email marketing --->

model EmailSender {
  id             String          @id @default(cuid())
  tenantId       String?
  provider       String
  stream         String
  apiKey         String
  fromEmail      String
  fromName       String?
  replyToEmail   String?
  tenant         Tenant?         @relation(fields: [tenantId], references: [id])
  campaigns      Campaign[]
  outboundEmails OutboundEmail[]
}

model Campaign {
  id            String          @id @default(cuid())
  tenantId      String?
  emailSenderId String
  name          String
  subject       String
  htmlBody      String
  textBody      String?
  status        String          @default("draft")
  track         Boolean
  sentAt        DateTime?
  emailSender   EmailSender     @relation(fields: [emailSenderId], references: [id])
  recipients    OutboundEmail[]
  tenant        Tenant?         @relation(fields: [tenantId], references: [id], onDelete: Cascade)
}

model OutboundEmail {
  id               String               @id @default(cuid())
  createdAt        DateTime             @default(now())
  tenantId         String?
  tenant           Tenant?              @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  campaignId       String?
  campaign         Campaign?            @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  contactRowId     String?
  contactRow       Row?                 @relation(fields: [contactRowId], references: [id])
  email            String
  fromSenderId     String
  isPreview        Boolean?
  error            String?
  sentAt           DateTime?
  deliveredAt      DateTime?
  bouncedAt        DateTime?
  spamComplainedAt DateTime?
  unsubscribedAt   DateTime?
  fromSender       EmailSender          @relation(fields: [fromSenderId], references: [id])
  opens            OutboundEmailOpen[]
  clicks           OutboundEmailClick[]
}

model OutboundEmailOpen {
  id              String        @id @default(cuid())
  createdAt       DateTime      @default(now())
  firstOpen       Boolean
  outboundEmailId String
  outboundEmail   OutboundEmail @relation(fields: [outboundEmailId], references: [id], onDelete: Cascade)
}

model OutboundEmailClick {
  id              String        @id @default(cuid())
  createdAt       DateTime      @default(now())
  link            String
  outboundEmailId String
  outboundEmail   OutboundEmail @relation(fields: [outboundEmailId], references: [id], onDelete: Cascade)
}

model Page {
  id          String        @id @default(cuid())
  createdAt   DateTime      @default(now())
  updatedAt   DateTime      @updatedAt
  slug        String
  isPublished Boolean       @default(false)
  isPublic    Boolean       @default(false)
  metaTags    PageMetaTag[]
  blocks      PageBlock[]
}

model PageMetaTag {
  id     String  @id @default(cuid())
  pageId String?
  page   Page?   @relation(fields: [pageId], references: [id], onDelete: Cascade)
  order  Int?
  name   String
  value  String

  @@unique([pageId, name, value])
}

model PageBlock {
  id     String @id @default(cuid())
  pageId String
  page   Page   @relation(fields: [pageId], references: [id])
  order  Int
  type   String // hero, gallery, logoClouds, video...
  value  String
}

model Tag {
  id      String      @id @default(cuid())
  name    String      @unique
  color   Int?
  users   TagUser[]
  tenants TagTenant[]
}

model TagUser {
  id     String @id @default(cuid())
  userId String
  user   User   @relation(fields: [userId], references: [id])
  tagId  String
  tag    Tag    @relation(fields: [tagId], references: [id])
}

model TagTenant {
  id       String @id @default(cuid())
  tenantId String
  tenant   Tenant @relation(fields: [tenantId], references: [id])
  tagId    String
  tag      Tag    @relation(fields: [tagId], references: [id])
}

model Onboarding {
  id             String              @id @default(cuid())
  createdAt      DateTime            @default(now())
  updatedAt      DateTime            @updatedAt
  title          String
  type           String // modal, page
  realtime       Boolean             @default(false)
  active         Boolean             @default(false)
  canBeDismissed Boolean             @default(true)
  height         String?
  filters        OnboardingFilter[]
  steps          OnboardingStep[]
  sessions       OnboardingSession[]
}

model OnboardingFilter {
  id           String                         @id @default(cuid())
  createdAt    DateTime                       @default(now())
  onboardingId String
  onboarding   Onboarding                     @relation(fields: [onboardingId], references: [id], onDelete: Cascade)
  type         String
  value        String?
  matches      OnboardingSessionFilterMatch[]
}

model OnboardingStep {
  id           String                  @id @default(cuid())
  onboardingId String
  onboarding   Onboarding              @relation(fields: [onboardingId], references: [id], onDelete: Cascade)
  order        Int
  block        String
  sessionSteps OnboardingSessionStep[]
}

model OnboardingSession {
  id              String                         @id @default(cuid())
  createdAt       DateTime                       @default(now())
  updatedAt       DateTime                       @updatedAt
  onboardingId    String
  onboarding      Onboarding                     @relation(fields: [onboardingId], references: [id], onDelete: Cascade)
  userId          String
  user            User                           @relation(fields: [userId], references: [id], onDelete: Cascade)
  tenantId        String?
  tenant          Tenant?                        @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  status          String // active, completed, dismissed
  startedAt       DateTime?
  completedAt     DateTime?
  dismissedAt     DateTime?
  createdRealtime Boolean                        @default(false)
  matches         OnboardingSessionFilterMatch[]
  sessionSteps    OnboardingSessionStep[]
  actions         OnboardingSessionAction[]

  @@unique([onboardingId, userId, tenantId])
}

model OnboardingSessionAction {
  id                  String            @id @default(cuid())
  createdAt           DateTime          @default(now())
  onboardingSessionId String
  onboardingSession   OnboardingSession @relation(fields: [onboardingSessionId], references: [id], onDelete: Cascade)
  type                String
  name                String
  value               String
}

model OnboardingSessionFilterMatch {
  id                  String            @id @default(cuid())
  onboardingFilterId  String
  onboardingFilter    OnboardingFilter  @relation(fields: [onboardingFilterId], references: [id], onDelete: Cascade)
  onboardingSessionId String
  onboardingSession   OnboardingSession @relation(fields: [onboardingSessionId], references: [id], onDelete: Cascade)
}

model OnboardingSessionStep {
  id                  String            @id @default(cuid())
  onboardingSessionId String
  onboardingSession   OnboardingSession @relation(fields: [onboardingSessionId], references: [id], onDelete: Cascade)
  stepId              String
  step                OnboardingStep    @relation(fields: [stepId], references: [id], onDelete: Cascade)
  seenAt              DateTime?
  completedAt         DateTime?
}