stack/apps/backend/prisma/schema.prisma
TheCactusBlue 952aa78981 fix
2025-03-25 12:02:36 -07:00

1001 lines
33 KiB
Plaintext

generator client {
provider = "prisma-client-js"
previewFeatures = ["tracing", "relationJoins"]
}
datasource db {
provider = "postgresql"
url = env("STACK_DATABASE_CONNECTION_STRING")
directUrl = env("STACK_DIRECT_DATABASE_CONNECTION_STRING")
}
model Project {
// Note that the project with ID `internal` is handled as a special case. All other project IDs are UUIDs.
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
displayName String
description String @default("")
configId String @db.Uuid
config ProjectConfig @relation(fields: [configId], references: [id], onDelete: Cascade)
isProductionMode Boolean
userCount Int @default(0)
apiKeySets ApiKeySet[]
projectUsers ProjectUser[]
neonProvisionedProject NeonProvisionedProject?
tenancies Tenancy[]
verificationCodes VerificationCode[]
}
model ProjectConfig {
id String @id @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
allowLocalhost Boolean
signUpEnabled Boolean @default(true)
createTeamOnSignUp Boolean
clientTeamCreationEnabled Boolean
clientUserDeletionEnabled Boolean @default(false)
// TODO: remove this after moving everyone to project specific JWTs
legacyGlobalJwtSigning Boolean @default(false)
teamCreateDefaultSystemPermissions TeamSystemPermission[]
teamMemberDefaultSystemPermissions TeamSystemPermission[]
projects Project[]
oauthProviderConfigs OAuthProviderConfig[]
emailServiceConfig EmailServiceConfig?
domains ProjectDomain[]
permissions Permission[]
authMethodConfigs AuthMethodConfig[]
connectedAccountConfigs ConnectedAccountConfig[]
oauthAccountMergeStrategy OAuthAccountMergeStrategy @default(LINK_METHOD)
}
enum OAuthAccountMergeStrategy {
LINK_METHOD
RAISE_ERROR
ALLOW_DUPLICATES
}
model Tenancy {
id String @id @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
branchId String
// If organizationId is NULL, hasNoOrganization must be TRUE. If organizationId is not NULL, hasNoOrganization must be NULL.
organizationId String? @db.Uuid
hasNoOrganization BooleanTrue?
teams Team[] @relation("TenancyTeams")
projectUsers ProjectUser[] @relation("TenancyProjectUsers")
authMethods AuthMethod[] @relation("TenancyAuthMethods")
contactChannels ContactChannel[] @relation("TenancyContactChannels")
connectedAccounts ConnectedAccount[] @relation("TenancyConnectedAccounts")
SentEmail SentEmail[]
cliAuthAttempts CliAuthAttempt[]
@@unique([projectId, branchId, organizationId])
@@unique([projectId, branchId, hasNoOrganization])
}
model ProjectDomain {
projectConfigId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
domain String
handlerPath String
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
@@unique([projectConfigId, domain])
}
model Team {
tenancyId String @db.Uuid
teamId String @default(uuid()) @db.Uuid
// Team IDs must be unique across all organizations (but not necessarily across all branches).
// To model this in the DB, we add two columns that are always equal to tenancy.projectId and tenancy.branchId.
mirroredProjectId String
mirroredBranchId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
displayName String
profileImageUrl String?
clientMetadata Json?
clientReadOnlyMetadata Json?
serverMetadata Json?
tenancy Tenancy @relation("TenancyTeams", fields: [tenancyId], references: [id], onDelete: Cascade)
permissions Permission[]
teamMembers TeamMember[]
@@id([tenancyId, teamId])
@@unique([mirroredProjectId, mirroredBranchId, teamId])
}
// This is used for fields that are boolean but only the true value is part of a unique constraint.
// For example if you want to allow only one selected team per user, you can make an optional field with this type and add a unique constraint.
// Only the true value is considered for the unique constraint, the null value is not.
enum BooleanTrue {
TRUE
}
model TeamMember {
tenancyId String @db.Uuid
projectUserId String @db.Uuid
teamId String @db.Uuid
// This will override the displayName of the user in this team.
displayName String?
// This will override the profileImageUrl of the user in this team.
profileImageUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
team Team @relation(fields: [tenancyId, teamId], references: [tenancyId, teamId], onDelete: Cascade)
isSelected BooleanTrue?
directPermissions TeamMemberDirectPermission[]
@@id([tenancyId, projectUserId, teamId])
@@unique([tenancyId, projectUserId, isSelected])
}
model ProjectUserDirectPermission {
id String @id @default(uuid()) @db.Uuid
tenancyId String @db.Uuid
projectUserId String @db.Uuid
permissionDbId String? @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
// no [systemPermission] yet, we'll add it when the need arises
permission Permission? @relation(fields: [permissionDbId], references: [dbId], onDelete: Cascade)
@@unique([tenancyId, projectUserId, permissionDbId])
}
model TeamMemberDirectPermission {
id String @id @default(uuid()) @db.Uuid
tenancyId String @db.Uuid
projectUserId String @db.Uuid
teamId String @db.Uuid
permissionDbId String? @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamMember TeamMember @relation(fields: [tenancyId, projectUserId, teamId], references: [tenancyId, projectUserId, teamId], onDelete: Cascade)
// exactly one of [permissionId && permission] or [systemPermission] must be set
permission Permission? @relation(fields: [permissionDbId], references: [dbId], onDelete: Cascade)
systemPermission TeamSystemPermission?
@@unique([tenancyId, projectUserId, teamId, permissionDbId])
@@unique([tenancyId, projectUserId, teamId, systemPermission])
}
model Permission {
// The ID of this permission, as is chosen by and exposed to the user. It is different from the database ID, which is randomly generated and only used internally.
queryableId String
// The database ID of this permission. This is never exposed to any client and is only used to make sure the database has an ID column.
dbId String @id @default(uuid()) @db.Uuid
// exactly one of [projectConfigId && projectConfig] or [projectId && teamId && team] must be set
projectConfigId String? @db.Uuid
tenancyId String? @db.Uuid
teamId String? @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
description String?
// The scope of the permission. If projectConfigId is set, may be USER or TEAM; if teamId is set, must be TEAM.
scope PermissionScope
projectConfig ProjectConfig? @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
team Team? @relation(fields: [tenancyId, teamId], references: [tenancyId, teamId], onDelete: Cascade)
parentEdges PermissionEdge[] @relation("ChildPermission")
childEdges PermissionEdge[] @relation("ParentPermission")
teamMemberDirectPermission TeamMemberDirectPermission[]
projectUserDirectPermission ProjectUserDirectPermission[]
isDefaultTeamCreatorPermission Boolean @default(false)
isDefaultTeamMemberPermission Boolean @default(false)
isDefaultProjectPermission Boolean @default(false)
@@unique([projectConfigId, queryableId])
@@unique([tenancyId, teamId, queryableId])
}
enum PermissionScope {
PROJECT
TEAM
}
enum TeamSystemPermission {
UPDATE_TEAM
DELETE_TEAM
READ_MEMBERS
REMOVE_MEMBERS
INVITE_MEMBERS
}
model PermissionEdge {
edgeId String @id @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// exactly one of [parentPermissionDbId && parentPermission] or [parentTeamSystemPermission] must be set
parentPermissionDbId String? @db.Uuid
parentPermission Permission? @relation("ParentPermission", fields: [parentPermissionDbId], references: [dbId], onDelete: Cascade)
parentTeamSystemPermission TeamSystemPermission?
childPermissionDbId String @db.Uuid
childPermission Permission @relation("ChildPermission", fields: [childPermissionDbId], references: [dbId], onDelete: Cascade)
@@index([parentPermissionDbId])
@@index([childPermissionDbId])
}
model ProjectUser {
tenancyId String @db.Uuid
projectUserId String @default(uuid()) @db.Uuid
// User IDs must be unique across all organizations (but not necessarily across all branches).
// To model this in the DB, we add two columns that are always equal to tenancy.projectId and tenancy.branchId.
mirroredProjectId String
mirroredBranchId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profileImageUrl String?
displayName String?
serverMetadata Json?
clientReadOnlyMetadata Json?
clientMetadata Json?
requiresTotpMfa Boolean @default(false)
totpSecret Bytes?
isAnonymous Boolean @default(false)
tenancy Tenancy @relation("TenancyProjectUsers", fields: [tenancyId], references: [id], onDelete: Cascade)
project Project @relation(fields: [mirroredProjectId], references: [id], onDelete: Cascade)
projectUserRefreshTokens ProjectUserRefreshToken[]
projectUserAuthorizationCodes ProjectUserAuthorizationCode[]
projectUserOAuthAccounts ProjectUserOAuthAccount[]
teamMembers TeamMember[]
contactChannels ContactChannel[]
authMethods AuthMethod[]
connectedAccounts ConnectedAccount[]
// some backlinks for the unique constraints on some auth methods
passwordAuthMethod PasswordAuthMethod[]
passkeyAuthMethod PasskeyAuthMethod[]
otpAuthMethod OtpAuthMethod[]
oauthAuthMethod OAuthAuthMethod[]
SentEmail SentEmail[]
directPermissions ProjectUserDirectPermission[]
@@id([tenancyId, projectUserId])
@@unique([mirroredProjectId, mirroredBranchId, projectUserId])
// indices for sorting and filtering
@@index([tenancyId, displayName(sort: Asc)], name: "ProjectUser_displayName_asc")
@@index([tenancyId, displayName(sort: Desc)], name: "ProjectUser_displayName_desc")
@@index([tenancyId, createdAt(sort: Asc)], name: "ProjectUser_createdAt_asc")
@@index([tenancyId, createdAt(sort: Desc)], name: "ProjectUser_createdAt_desc")
}
// This should be renamed to "OAuthAccount" as it is not always bound to a user
// When ever a user goes through the OAuth flow and gets an account ID from the OAuth provider, we store that here.
model ProjectUserOAuthAccount {
tenancyId String @db.Uuid
projectUserId String @db.Uuid
projectConfigId String @db.Uuid
oauthProviderConfigId String
providerAccountId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// This is used for the user to distinguish between multiple accounts from the same provider.
// we might want to add more user info here later
email String?
providerConfig OAuthProviderConfig @relation(fields: [projectConfigId, oauthProviderConfigId], references: [projectConfigId, id], onDelete: Cascade)
// Before the OAuth account is connected to a use (for example, in the link oauth process), the projectUser is null.
projectUser ProjectUser? @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
oauthTokens OAuthToken[]
oauthAccessToken OAuthAccessToken[]
// At lease one of the authMethod or connectedAccount should be set.
connectedAccount ConnectedAccount?
oauthAuthMethod OAuthAuthMethod?
@@id([tenancyId, oauthProviderConfigId, providerAccountId])
@@index([tenancyId, projectUserId])
}
enum ContactChannelType {
EMAIL
// PHONE
}
model ContactChannel {
tenancyId String @db.Uuid
projectUserId String @db.Uuid
id String @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
type ContactChannelType
isPrimary BooleanTrue?
usedForAuth BooleanTrue?
isVerified Boolean
value String
tenancy Tenancy @relation("TenancyContactChannels", fields: [tenancyId], references: [id], onDelete: Cascade)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, projectUserId, id])
// each user has at most one primary contact channel of each type
@@unique([tenancyId, projectUserId, type, isPrimary])
// value must be unique per user per type
@@unique([tenancyId, projectUserId, type, value])
// only one contact channel per project with the same value and type can be used for auth
@@unique([tenancyId, type, value, usedForAuth])
}
model ConnectedAccountConfig {
projectConfigId String @db.Uuid
id String @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
enabled Boolean @default(true)
// this should never be null
oauthProviderConfig OAuthProviderConfig?
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
connectedAccounts ConnectedAccount[]
@@id([projectConfigId, id])
}
model ConnectedAccount {
tenancyId String @db.Uuid
id String @default(uuid()) @db.Uuid
projectConfigId String @db.Uuid
connectedAccountConfigId String @db.Uuid
projectUserId String @db.Uuid
oauthProviderConfigId String
providerAccountId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
oauthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oauthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId])
tenancy Tenancy @relation("TenancyConnectedAccounts", fields: [tenancyId], references: [id], onDelete: Cascade)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
connectedAccountConfig ConnectedAccountConfig @relation(fields: [projectConfigId, connectedAccountConfigId], references: [projectConfigId, id], onDelete: Cascade)
oauthProviderConfig OAuthProviderConfig @relation(fields: [projectConfigId, oauthProviderConfigId], references: [projectConfigId, id], onDelete: Cascade)
@@id([tenancyId, id])
@@unique([tenancyId, oauthProviderConfigId, providerAccountId])
}
model AuthMethodConfig {
projectConfigId String @db.Uuid
id String @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
enabled Boolean @default(true)
// exactly one of xyzConfig should be set
otpConfig OtpAuthMethodConfig?
oauthProviderConfig OAuthProviderConfig?
passwordConfig PasswordAuthMethodConfig?
passkeyConfig PasskeyAuthMethodConfig?
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
authMethods AuthMethod[]
@@id([projectConfigId, id])
}
model OtpAuthMethodConfig {
projectConfigId String @db.Uuid
authMethodConfigId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
contactChannelType ContactChannelType
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
@@id([projectConfigId, authMethodConfigId])
}
model PasswordAuthMethodConfig {
projectConfigId String @db.Uuid
authMethodConfigId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
@@id([projectConfigId, authMethodConfigId])
}
model PasskeyAuthMethodConfig {
projectConfigId String @db.Uuid
authMethodConfigId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
@@id([projectConfigId, authMethodConfigId])
}
// Both the connected account and auth methods can use this configuration.
model OAuthProviderConfig {
projectConfigId String @db.Uuid
id String
authMethodConfigId String? @db.Uuid
connectedAccountConfigId String? @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Exactly one of the xyzOAuthConfig variables should be set.
proxiedOAuthConfig ProxiedOAuthProviderConfig?
standardOAuthConfig StandardOAuthProviderConfig?
// At least one of authMethodConfig or connectedAccountConfig should be set.
// The config relation will never be unset, but the enabled field might be set to false on authMethodConfig or connectedAccountConfig.
authMethodConfig AuthMethodConfig? @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
connectedAccountConfig ConnectedAccountConfig? @relation(fields: [projectConfigId, connectedAccountConfigId], references: [projectConfigId, id], onDelete: Cascade)
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
projectUserOAuthAccounts ProjectUserOAuthAccount[]
connectedAccounts ConnectedAccount[]
oauthAuthMethod OAuthAuthMethod[]
@@id([projectConfigId, id])
@@unique([projectConfigId, authMethodConfigId])
@@unique([projectConfigId, connectedAccountConfigId])
}
model ProxiedOAuthProviderConfig {
projectConfigId String @db.Uuid
id String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
type ProxiedOAuthProviderType
providerConfig OAuthProviderConfig @relation(fields: [projectConfigId, id], references: [projectConfigId, id], onDelete: Cascade)
@@id([projectConfigId, id])
// each type of proxied OAuth provider can only be used once per project
@@unique([projectConfigId, type])
}
enum ProxiedOAuthProviderType {
GITHUB
GOOGLE
MICROSOFT
SPOTIFY
}
model StandardOAuthProviderConfig {
projectConfigId String @db.Uuid
id String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
type StandardOAuthProviderType
clientId String
clientSecret String
// optional extra parameters for specific oauth providers
// Facebook business integration requires a config_id
facebookConfigId String?
// Microsoft organizational directory (tenant)
microsoftTenantId String?
providerConfig OAuthProviderConfig @relation(fields: [projectConfigId, id], references: [projectConfigId, id], onDelete: Cascade)
// each type of standard OAuth provider can only be used once per project
@@id([projectConfigId, id])
}
model AuthMethod {
tenancyId String @db.Uuid
id String @default(uuid()) @db.Uuid
projectUserId String @db.Uuid
authMethodConfigId String @db.Uuid
projectConfigId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// exactly one of the xyzAuthMethods should be set
otpAuthMethod OtpAuthMethod?
passwordAuthMethod PasswordAuthMethod?
passkeyAuthMethod PasskeyAuthMethod?
oauthAuthMethod OAuthAuthMethod?
tenancy Tenancy @relation("TenancyAuthMethods", fields: [tenancyId], references: [id], onDelete: Cascade)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
@@id([tenancyId, id])
@@index([tenancyId, projectUserId])
}
model OtpAuthMethod {
tenancyId String @db.Uuid
authMethodId String @db.Uuid
projectUserId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, authMethodId])
// a user can only have one OTP auth method
@@unique([tenancyId, projectUserId])
}
model PasswordAuthMethod {
tenancyId String @db.Uuid
authMethodId String @db.Uuid
projectUserId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
passwordHash String
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, authMethodId])
// a user can only have one password auth method
@@unique([tenancyId, projectUserId])
}
model PasskeyAuthMethod {
tenancyId String @db.Uuid
authMethodId String @db.Uuid
projectUserId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
credentialId String
publicKey String
userHandle String
transports String[]
credentialDeviceType String
counter Int
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, authMethodId])
// a user can only have one password auth method
@@unique([tenancyId, projectUserId])
}
// This connects to projectUserOauthAccount, which might be shared between auth method and connected account.
model OAuthAuthMethod {
tenancyId String @db.Uuid
projectConfigId String @db.Uuid
authMethodId String @db.Uuid
oauthProviderConfigId String
providerAccountId String
projectUserId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
oauthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oauthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId])
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
oauthProviderConfig OAuthProviderConfig @relation(fields: [projectConfigId, oauthProviderConfigId], references: [projectConfigId, id], onDelete: Cascade)
@@id([tenancyId, authMethodId])
@@unique([tenancyId, oauthProviderConfigId, providerAccountId])
}
enum StandardOAuthProviderType {
GITHUB
FACEBOOK
GOOGLE
MICROSOFT
SPOTIFY
DISCORD
GITLAB
BITBUCKET
LINKEDIN
APPLE
X
}
model OAuthToken {
id String @id @default(uuid()) @db.Uuid
tenancyId String @db.Uuid
oAuthProviderConfigId String
providerAccountId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projectUserOAuthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oAuthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId], onDelete: Cascade)
refreshToken String
scopes String[]
}
model OAuthAccessToken {
id String @id @default(uuid()) @db.Uuid
tenancyId String @db.Uuid
oAuthProviderConfigId String
providerAccountId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projectUserOAuthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oAuthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId], onDelete: Cascade)
accessToken String
scopes String[]
expiresAt DateTime
}
model OAuthOuterInfo {
id String @id @default(uuid()) @db.Uuid
info Json
innerState String @unique
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ProjectUserRefreshToken {
id String @default(uuid()) @db.Uuid
tenancyId String @db.Uuid
projectUserId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
refreshToken String @unique
expiresAt DateTime?
isImpersonation Boolean @default(false)
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, id])
}
model ProjectUserAuthorizationCode {
tenancyId String @db.Uuid
projectUserId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorizationCode String @unique
redirectUri String
expiresAt DateTime
codeChallenge String
codeChallengeMethod String
newUser Boolean
afterCallbackRedirectUrl String?
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, authorizationCode])
}
model VerificationCode {
projectId String
branchId String
id String @default(uuid()) @db.Uuid
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
type VerificationCodeType
code String
expiresAt DateTime
usedAt DateTime?
redirectUrl String?
method Json @default("null")
data Json
attemptCount Int @default(0)
@@id([projectId, branchId, id])
@@unique([projectId, branchId, code])
@@index([data(ops: JsonbPathOps)], type: Gin)
}
enum VerificationCodeType {
ONE_TIME_PASSWORD
PASSWORD_RESET
CONTACT_CHANNEL_VERIFICATION
TEAM_INVITATION
MFA_ATTEMPT
PASSKEY_REGISTRATION_CHALLENGE
PASSKEY_AUTHENTICATION_CHALLENGE
NEON_INTEGRATION_PROJECT_TRANSFER
}
//#region API keys
model ApiKeySet {
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
id String @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
description String
expiresAt DateTime
manuallyRevokedAt DateTime?
publishableClientKey String? @unique
secretServerKey String? @unique
superSecretAdminKey String? @unique
@@id([projectId, id])
}
model EmailServiceConfig {
projectConfigId String @id @db.Uuid
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
proxiedEmailServiceConfig ProxiedEmailServiceConfig?
standardEmailServiceConfig StandardEmailServiceConfig?
emailTemplates EmailTemplate[]
}
enum EmailTemplateType {
EMAIL_VERIFICATION
PASSWORD_RESET
MAGIC_LINK
TEAM_INVITATION
}
model EmailTemplate {
projectConfigId String @db.Uuid
emailServiceConfig EmailServiceConfig @relation(fields: [projectConfigId], references: [projectConfigId], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
content Json
type EmailTemplateType
subject String
@@id([projectConfigId, type])
}
model ProxiedEmailServiceConfig {
projectConfigId String @id @db.Uuid
emailServiceConfig EmailServiceConfig @relation(fields: [projectConfigId], references: [projectConfigId], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model StandardEmailServiceConfig {
projectConfigId String @id @db.Uuid
emailServiceConfig EmailServiceConfig @relation(fields: [projectConfigId], references: [projectConfigId], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
senderName String
senderEmail String
host String
port Int
username String
password String
}
//#endregion
//#region IdP
model IdPAccountToCdfcResultMapping {
idpId String
id String
idpAccountId String @unique @db.Uuid
cdfcResult Json
@@id([idpId, id])
}
model ProjectWrapperCodes {
idpId String
id String @default(uuid()) @db.Uuid
interactionUid String
authorizationCode String @unique
cdfcResult Json
@@id([idpId, id])
}
model IdPAdapterData {
idpId String
model String
id String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
payload Json
expiresAt DateTime
@@id([idpId, model, id])
@@index([payload(ops: JsonbPathOps)], type: Gin)
@@index([expiresAt])
}
//#endregion
//#region Neon integration
model NeonProvisionedProject {
projectId String @id
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
neonClientId String
}
//#endregion
//#region Events
model Event {
id String @id @default(uuid()) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// if isWide == false, then eventEndedAt is always equal to eventStartedAt
isWide Boolean
eventStartedAt DateTime
eventEndedAt DateTime
// TODO: add event_type, and at least one of either system_event_type or event_type is always set
systemEventTypeIds String[]
data Json
// ============================== BEGIN END USER PROPERTIES ==============================
// Below are properties describing the end user that caused this event to be logged
// This is different from a request IP. See: apps/backend/src/lib/end-users.tsx
// Note that the IP may have been spoofed, unless isEndUserIpInfoGuessTrusted is true
endUserIpInfoGuessId String? @db.Uuid
endUserIpInfoGuess EventIpInfo? @relation("EventIpInfo", fields: [endUserIpInfoGuessId], references: [id])
// If true, then endUserIpInfoGuess is not spoofed (might still be behind VPNs/proxies). If false, then the values may be spoofed.
isEndUserIpInfoGuessTrusted Boolean @default(false)
// =============================== END END USER PROPERTIES ===============================
@@index([data(ops: JsonbPathOps)], type: Gin)
}
// An IP address that was seen in an event. Use the location fields instead of refetching the location from the ip, as the real-world geoip data may have changed since the event was logged.
model EventIpInfo {
id String @id @default(uuid()) @db.Uuid
ip String
countryCode String?
regionCode String?
cityName String?
latitude Float?
longitude Float?
tzIdentifier String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
events Event[] @relation("EventIpInfo")
}
//#endregion
model SentEmail {
tenancyId String @db.Uuid
id String @default(uuid()) @db.Uuid
userId String? @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
senderConfig Json
to String[]
subject String
html String?
text String?
error Json?
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
user ProjectUser? @relation(fields: [tenancyId, userId], references: [tenancyId, projectUserId], onDelete: Cascade)
@@id([tenancyId, id])
}
model CliAuthAttempt {
tenancyId String @db.Uuid
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
id String @default(uuid()) @db.Uuid
pollingCode String @unique
loginCode String @unique
refreshToken String?
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@id([tenancyId, id])
}