mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
fix #13531 #### PR Dependency Tree * **PR #14372** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * CalDAV calendar integration: link and sync CalDAV-compatible calendars (discovery, listing, event sync). * New UI flow and dialog to link CalDAV accounts with provider selection, credentials, and display name. * **API / Config** * Server exposes CalDAV provider presets in config and new GraphQL mutation to link CalDAV accounts. * New calendar config section for CalDAV with validation and defaults. * **Tests** * Comprehensive CalDAV integration test suite added. * **Chores** * Removed analytics tokens from build configuration and reduced Cloud E2E test shards. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1075 lines
43 KiB
Plaintext
1075 lines
43 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
output = "../../../node_modules/.prisma/client"
|
|
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
|
|
previewFeatures = ["metrics", "relationJoins", "nativeDistinct", "postgresqlExtensions"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
extensions = [pgvector(map: "vector")]
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
name String @db.VarChar
|
|
email String @unique @db.VarChar
|
|
emailVerifiedAt DateTime? @map("email_verified") @db.Timestamptz(3)
|
|
avatarUrl String? @map("avatar_url") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
/// Not available if user signed up through OAuth providers
|
|
password String? @db.VarChar
|
|
/// Indicate whether the user finished the signup progress.
|
|
/// for example, the value will be false if user never registered and invited into a workspace by others.
|
|
registered Boolean @default(true)
|
|
disabled Boolean @default(false)
|
|
|
|
features UserFeature[]
|
|
userStripeCustomer UserStripeCustomer?
|
|
workspaces WorkspaceUserRole[]
|
|
// Invite others to join the workspace
|
|
WorkspaceInvitations WorkspaceUserRole[] @relation("inviter")
|
|
docPermissions WorkspaceDocUserRole[]
|
|
connectedAccounts ConnectedAccount[]
|
|
calendarAccounts CalendarAccount[]
|
|
sessions UserSession[]
|
|
aiSessions AiSession[]
|
|
appConfigs AppConfig[]
|
|
userSnapshots UserSnapshot[]
|
|
createdSnapshot Snapshot[] @relation("createdSnapshot")
|
|
updatedSnapshot Snapshot[] @relation("updatedSnapshot")
|
|
createdUpdate Update[] @relation("createdUpdate")
|
|
createdHistory SnapshotHistory[] @relation("createdHistory")
|
|
createdAiJobs AiJobs[] @relation("createdAiJobs")
|
|
// receive notifications
|
|
notifications Notification[] @relation("user_notifications")
|
|
settings UserSettings?
|
|
comments Comment[]
|
|
replies Reply[]
|
|
commentAttachments CommentAttachment[] @relation("createdCommentAttachments")
|
|
AccessToken AccessToken[]
|
|
workspaceCalendars WorkspaceCalendar[]
|
|
|
|
@@index([email])
|
|
@@map("users")
|
|
}
|
|
|
|
model ConnectedAccount {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
provider String @db.VarChar
|
|
providerAccountId String @map("provider_account_id") @db.VarChar
|
|
scope String? @db.Text
|
|
accessToken String? @map("access_token") @db.Text
|
|
refreshToken String? @map("refresh_token") @db.Text
|
|
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([providerAccountId])
|
|
@@map("user_connected_accounts")
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
userSessions UserSession[]
|
|
|
|
@@map("multiple_users_sessions")
|
|
}
|
|
|
|
model UserSession {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
sessionId String @map("session_id") @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([sessionId, userId])
|
|
@@map("user_sessions")
|
|
}
|
|
|
|
model VerificationToken {
|
|
token String @db.VarChar
|
|
type Int @db.SmallInt
|
|
credential String? @db.Text
|
|
expiresAt DateTime @db.Timestamptz(3)
|
|
|
|
@@unique([type, token])
|
|
@@map("verification_tokens")
|
|
}
|
|
|
|
model MagicLinkOtp {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
email String @unique @db.Text
|
|
otpHash String @map("otp_hash") @db.VarChar
|
|
token String @db.Text
|
|
clientNonce String? @map("client_nonce") @db.Text
|
|
attempts Int @default(0)
|
|
expiresAt DateTime @map("expires_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
@@index([expiresAt])
|
|
@@map("magic_link_otps")
|
|
}
|
|
|
|
model Workspace {
|
|
// NOTE: manually set this column type to identity in migration file
|
|
sid Int @unique @default(autoincrement())
|
|
id String @id @default(uuid()) @db.VarChar
|
|
public Boolean
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
// workspace level feature flags
|
|
enableAi Boolean @default(true) @map("enable_ai")
|
|
enableSharing Boolean @default(true) @map("enable_sharing")
|
|
enableUrlPreview Boolean @default(false) @map("enable_url_preview")
|
|
enableDocEmbedding Boolean @default(true) @map("enable_doc_embedding")
|
|
name String? @db.VarChar
|
|
avatarKey String? @map("avatar_key") @db.VarChar
|
|
indexed Boolean @default(false)
|
|
lastCheckEmbeddings DateTime @default("1970-01-01T00:00:00-00:00") @map("last_check_embeddings") @db.Timestamptz(3)
|
|
|
|
features WorkspaceFeature[]
|
|
docs WorkspaceDoc[]
|
|
permissions WorkspaceUserRole[]
|
|
docPermissions WorkspaceDocUserRole[]
|
|
blobs Blob[]
|
|
ignoredDocs AiWorkspaceIgnoredDocs[]
|
|
embedFiles AiWorkspaceFiles[]
|
|
comments Comment[]
|
|
commentAttachments CommentAttachment[]
|
|
workspaceCalendars WorkspaceCalendar[]
|
|
workspaceAdminStats WorkspaceAdminStats[]
|
|
workspaceAdminStatsDirties WorkspaceAdminStatsDirty[]
|
|
|
|
@@index([lastCheckEmbeddings])
|
|
@@index([createdAt])
|
|
@@map("workspaces")
|
|
}
|
|
|
|
// Table for workspace page meta data
|
|
// NOTE:
|
|
// We won't make sure every page has a corresponding record in this table.
|
|
// Only the ones that have ever changed will have records here,
|
|
// and for others we will make sure it's has a default value return in our business logic.
|
|
model WorkspaceDoc {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("page_id") @db.VarChar
|
|
public Boolean @default(false)
|
|
// Workspace user's default role in this page, default is `Manager`
|
|
defaultRole Int @default(30) @db.SmallInt
|
|
// Page/Edgeless
|
|
mode Int @default(0) @db.SmallInt
|
|
// Whether the doc is blocked
|
|
blocked Boolean @default(false)
|
|
title String? @db.VarChar
|
|
summary String? @db.VarChar
|
|
publishedAt DateTime? @map("published_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([workspaceId, docId])
|
|
@@index([workspaceId, public])
|
|
@@map("workspace_pages")
|
|
}
|
|
|
|
enum WorkspaceMemberStatus {
|
|
/// Wait for the invitee to accept the invitation
|
|
Pending
|
|
/// Wait for administrators to review and accept the link invitation
|
|
UnderReview
|
|
/// Temporary state for team workspace. There is some time gap between invitation and bill payed
|
|
AllocatingSeat
|
|
/// Insufficient seat for user becoming active workspace member
|
|
NeedMoreSeat
|
|
/// Activate workspace member
|
|
Accepted
|
|
/// @deprecated
|
|
NeedMoreSeatAndReview
|
|
}
|
|
|
|
enum WorkspaceMemberSource {
|
|
/// Invited by email
|
|
Email
|
|
/// Invited by link
|
|
Link
|
|
}
|
|
|
|
model WorkspaceUserRole {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
// Workspace Role, Owner/Admin/Collaborator/External
|
|
type Int @db.SmallInt
|
|
/// the invite status of the workspace member
|
|
status WorkspaceMemberStatus @default(Pending)
|
|
source WorkspaceMemberSource @default(Email)
|
|
inviterId String? @map("inviter_id") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
inviter User? @relation(name: "inviter", fields: [inviterId], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([workspaceId, userId])
|
|
// optimize for querying user's workspace permissions
|
|
@@index([workspaceId, type, status])
|
|
@@index(userId)
|
|
@@map("workspace_user_permissions")
|
|
}
|
|
|
|
model WorkspaceDocUserRole {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("page_id") @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
// External/Reader/Editor/Manager/Owner
|
|
type Int @db.SmallInt
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([workspaceId, docId, userId])
|
|
@@map("workspace_page_user_permissions")
|
|
}
|
|
|
|
model UserFeature {
|
|
id Int @id @default(autoincrement())
|
|
userId String @map("user_id") @db.VarChar
|
|
// it should be typed as `optional` in the codebase, but we would keep all values exists during data migration.
|
|
// so it's safe to assert it a non-null value.
|
|
name String @default("") @map("name") @db.VarChar
|
|
// a little redundant, but fast the queries
|
|
type Int @default(0) @map("type") @db.Integer
|
|
reason String @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
expiredAt DateTime? @map("expired_at") @db.Timestamptz(3)
|
|
activated Boolean @default(false)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([name])
|
|
@@map("user_features")
|
|
}
|
|
|
|
model WorkspaceFeature {
|
|
id Int @id @default(autoincrement())
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
// it should be typed as `optional` in the codebase, but we would keep all values exists during data migration.
|
|
// so it's safe to assert it a non-null value.
|
|
name String @default("") @map("name") @db.VarChar
|
|
// a little redundant, but fast the queries
|
|
type Int @default(0) @map("type") @db.Integer
|
|
/// overrides for the default feature configs
|
|
configs Json @default("{}") @db.Json
|
|
reason String @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
activated Boolean @default(false)
|
|
expiredAt DateTime? @map("expired_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([workspaceId])
|
|
@@index([name])
|
|
// Partial index on (workspace_id, name) where activated = true is created via SQL migration
|
|
@@index([workspaceId, name, activated])
|
|
@@map("workspace_features")
|
|
}
|
|
|
|
model WorkspaceAdminStats {
|
|
workspaceId String @id @map("workspace_id") @db.VarChar
|
|
snapshotCount BigInt @default(0) @map("snapshot_count") @db.BigInt
|
|
snapshotSize BigInt @default(0) @map("snapshot_size") @db.BigInt
|
|
blobCount BigInt @default(0) @map("blob_count") @db.BigInt
|
|
blobSize BigInt @default(0) @map("blob_size") @db.BigInt
|
|
memberCount BigInt @default(0) @map("member_count") @db.BigInt
|
|
publicPageCount BigInt @default(0) @map("public_page_count") @db.BigInt
|
|
features String[] @default([]) @db.Text
|
|
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([snapshotSize(sort: Desc)])
|
|
@@index([blobCount(sort: Desc)])
|
|
@@index([blobSize(sort: Desc)])
|
|
@@index([snapshotCount(sort: Desc)])
|
|
@@index([memberCount(sort: Desc)])
|
|
@@index([publicPageCount(sort: Desc)])
|
|
@@map("workspace_admin_stats")
|
|
}
|
|
|
|
model WorkspaceAdminStatsDirty {
|
|
workspaceId String @id @map("workspace_id") @db.VarChar
|
|
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([updatedAt])
|
|
@@map("workspace_admin_stats_dirty")
|
|
}
|
|
|
|
// the latest snapshot of each doc that we've seen
|
|
// Snapshot + Updates are the latest state of the doc
|
|
model Snapshot {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
id String @default(uuid()) @map("guid") @db.VarChar
|
|
blob Bytes @db.ByteA
|
|
// persisted size of blob to avoid summing over bytea
|
|
size BigInt? @map("size") @db.BigInt
|
|
state Bytes? @db.ByteA
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
// the `updated_at` field will not record the time of record changed,
|
|
// but the created time of last seen update that has been merged into snapshot.
|
|
updatedAt DateTime @map("updated_at") @db.Timestamptz(3)
|
|
createdBy String? @map("created_by") @db.VarChar
|
|
updatedBy String? @map("updated_by") @db.VarChar
|
|
|
|
// should not delete origin snapshot even if user is deleted
|
|
// we only delete the snapshot if the workspace is deleted
|
|
createdByUser User? @relation(name: "createdSnapshot", fields: [createdBy], references: [id], onDelete: SetNull)
|
|
updatedByUser User? @relation(name: "updatedSnapshot", fields: [updatedBy], references: [id], onDelete: SetNull)
|
|
|
|
// we need to clear all hanging updates and snapshots before enable the foreign key on workspaceId
|
|
// workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
embedding AiWorkspaceEmbedding[]
|
|
|
|
@@id([workspaceId, id])
|
|
@@index([workspaceId, updatedAt])
|
|
@@map("snapshots")
|
|
}
|
|
|
|
// user snapshots are special snapshots for user storage like personal app settings, distinguished from workspace snapshots
|
|
// basically they share the same structure with workspace snapshots
|
|
// but for convenience, we don't fork the updates queue and history for user snapshots, until we have to
|
|
// which means all operation on user snapshot will happen in-pace
|
|
model UserSnapshot {
|
|
userId String @map("user_id") @db.VarChar
|
|
id String @map("id") @db.VarChar
|
|
blob Bytes @db.ByteA
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([userId, id])
|
|
@@map("user_snapshots")
|
|
}
|
|
|
|
model Update {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
id String @map("guid") @db.VarChar
|
|
blob Bytes @db.ByteA
|
|
createdAt DateTime @map("created_at") @db.Timestamptz(3)
|
|
createdBy String? @map("created_by") @db.VarChar
|
|
|
|
// will delete creator record if creator's account is deleted
|
|
createdByUser User? @relation(name: "createdUpdate", fields: [createdBy], references: [id], onDelete: SetNull)
|
|
|
|
@@id([workspaceId, id, createdAt])
|
|
@@map("updates")
|
|
}
|
|
|
|
model SnapshotHistory {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
id String @map("guid") @db.VarChar
|
|
timestamp DateTime @db.Timestamptz(3)
|
|
blob Bytes @db.ByteA
|
|
state Bytes? @db.ByteA
|
|
expiredAt DateTime @map("expired_at") @db.Timestamptz(3)
|
|
createdBy String? @map("created_by") @db.VarChar
|
|
|
|
// will delete creator record if creator's account is deleted
|
|
createdByUser User? @relation(name: "createdHistory", fields: [createdBy], references: [id], onDelete: SetNull)
|
|
|
|
@@id([workspaceId, id, timestamp])
|
|
@@map("snapshot_histories")
|
|
}
|
|
|
|
enum AiPromptRole {
|
|
system
|
|
assistant
|
|
user
|
|
}
|
|
|
|
model AiPromptMessage {
|
|
promptId Int @map("prompt_id") @db.Integer
|
|
// if a group of prompts contains multiple sentences, idx specifies the order of each sentence
|
|
idx Int @db.Integer
|
|
// system/assistant/user
|
|
role AiPromptRole
|
|
// prompt content
|
|
content String @db.Text
|
|
attachments Json? @db.Json
|
|
params Json? @db.Json
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
prompt AiPrompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([promptId, idx])
|
|
@@map("ai_prompts_messages")
|
|
}
|
|
|
|
model AiPrompt {
|
|
id Int @id @default(autoincrement()) @db.Integer
|
|
name String @unique @db.VarChar(32)
|
|
// an mark identifying which view to use to display the session
|
|
// it is only used in the frontend and does not affect the backend
|
|
action String? @db.VarChar
|
|
model String @db.VarChar
|
|
optionalModels String[] @default([]) @map("optional_models") @db.VarChar
|
|
config Json? @db.Json
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamptz(3)
|
|
// whether the prompt is modified by the admin panel
|
|
modified Boolean @default(false)
|
|
|
|
messages AiPromptMessage[]
|
|
sessions AiSession[]
|
|
|
|
@@map("ai_prompts_metadata")
|
|
}
|
|
|
|
model AiSessionMessage {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
sessionId String @map("session_id") @db.VarChar
|
|
role AiPromptRole
|
|
content String @db.Text
|
|
streamObjects Json? @db.Json
|
|
attachments Json? @db.Json
|
|
params Json? @db.Json
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
session AiSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([sessionId])
|
|
@@map("ai_sessions_messages")
|
|
}
|
|
|
|
model AiSession {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String? @map("doc_id") @db.VarChar
|
|
promptName String @map("prompt_name") @db.VarChar(32)
|
|
promptAction String? @default("") @map("prompt_action") @db.VarChar(32)
|
|
pinned Boolean @default(false)
|
|
title String? @db.VarChar
|
|
// the session id of the parent session if this session is a forked session
|
|
parentSessionId String? @map("parent_session_id") @db.VarChar
|
|
messageCost Int @default(0)
|
|
tokenCost Int @default(0)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
prompt AiPrompt @relation(fields: [promptName], references: [name], onDelete: Cascade)
|
|
messages AiSessionMessage[]
|
|
context AiContext[]
|
|
|
|
//NOTE:
|
|
// unrecorded index:
|
|
// @@index([userId, workspaceId]) where pinned = true and deleted_at is null
|
|
// @@index([userId, workspaceId, docId]) where prompt_action is null and parent_session_id is null and doc_id is not null and deleted_at is null
|
|
// since prisma does not support partial indexes, those indexes are only exists in migration files.
|
|
@@index([promptName])
|
|
@@index([userId])
|
|
@@index([userId, workspaceId, docId])
|
|
@@map("ai_sessions_metadata")
|
|
}
|
|
|
|
model AiContext {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
sessionId String @map("session_id") @db.VarChar
|
|
config Json @db.Json
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
embeddings AiContextEmbedding[]
|
|
session AiSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("ai_contexts")
|
|
}
|
|
|
|
model AiContextEmbedding {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
contextId String @map("context_id") @db.VarChar
|
|
fileId String @map("file_id") @db.VarChar
|
|
// a file can be divided into multiple chunks and embedded separately.
|
|
chunk Int @db.Integer
|
|
content String @db.VarChar
|
|
embedding Unsupported("vector(1024)")
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
context AiContext @relation(fields: [contextId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([contextId, fileId, chunk])
|
|
@@index([embedding], map: "ai_context_embeddings_idx")
|
|
@@map("ai_context_embeddings")
|
|
}
|
|
|
|
model AiWorkspaceEmbedding {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("doc_id") @db.VarChar
|
|
// a doc can be divided into multiple chunks and embedded separately.
|
|
chunk Int @db.Integer
|
|
content String @db.VarChar
|
|
embedding Unsupported("vector(1024)")
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
// workspace level search not available for non-cloud workspaces
|
|
// so we can match this record with the snapshot one by one
|
|
snapshot Snapshot @relation(fields: [workspaceId, docId], references: [workspaceId, id], onDelete: Cascade)
|
|
|
|
@@id([workspaceId, docId, chunk])
|
|
@@index([embedding], map: "ai_workspace_embeddings_idx")
|
|
@@map("ai_workspace_embeddings")
|
|
}
|
|
|
|
model AiWorkspaceIgnoredDocs {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("doc_id") @db.VarChar
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([workspaceId, docId])
|
|
@@map("ai_workspace_ignored_docs")
|
|
}
|
|
|
|
model AiWorkspaceFiles {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
fileId String @map("file_id") @db.VarChar
|
|
blobId String @default("") @map("blob_id") @db.VarChar
|
|
fileName String @map("file_name") @db.VarChar
|
|
mimeType String @map("mime_type") @db.VarChar
|
|
size Int @db.Integer
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
|
|
embeddings AiWorkspaceFileEmbedding[]
|
|
|
|
@@id([workspaceId, fileId])
|
|
@@map("ai_workspace_files")
|
|
}
|
|
|
|
model AiWorkspaceFileEmbedding {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
fileId String @map("file_id") @db.VarChar
|
|
// a file can be divided into multiple chunks and embedded separately.
|
|
chunk Int @db.Integer
|
|
content String @db.VarChar
|
|
embedding Unsupported("vector(1024)")
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
file AiWorkspaceFiles @relation(fields: [workspaceId, fileId], references: [workspaceId, fileId], onDelete: Cascade)
|
|
|
|
@@id([workspaceId, fileId, chunk])
|
|
@@index([embedding], map: "ai_workspace_file_embeddings_idx")
|
|
@@map("ai_workspace_file_embeddings")
|
|
}
|
|
|
|
model AiWorkspaceBlobEmbedding {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
blobId String @map("blob_id") @db.VarChar
|
|
// a file can be divided into multiple chunks and embedded separately.
|
|
chunk Int @db.Integer
|
|
content String @db.VarChar
|
|
embedding Unsupported("vector(1024)")
|
|
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
blob Blob @relation(fields: [workspaceId, blobId], references: [workspaceId, key], onDelete: Cascade)
|
|
|
|
@@id([workspaceId, blobId, chunk])
|
|
@@index([embedding], map: "ai_workspace_blob_embeddings_idx")
|
|
@@map("ai_workspace_blob_embeddings")
|
|
}
|
|
|
|
enum AiJobStatus {
|
|
pending
|
|
running
|
|
finished
|
|
claimed
|
|
failed
|
|
}
|
|
|
|
enum AiJobType {
|
|
transcription
|
|
}
|
|
|
|
model AiJobs {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
blobId String @map("blob_id") @db.VarChar
|
|
createdBy String? @map("created_by") @db.VarChar
|
|
// job type, like "transcription"
|
|
type AiJobType
|
|
status AiJobStatus @default(pending)
|
|
// job result
|
|
payload Json @db.Json
|
|
|
|
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz(3)
|
|
finishedAt DateTime? @map("finished_at") @db.Timestamptz(3)
|
|
|
|
// will delete creator record if creator's account is deleted
|
|
createdByUser User? @relation(name: "createdAiJobs", fields: [createdBy], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([createdBy, workspaceId, blobId])
|
|
@@map("ai_jobs")
|
|
}
|
|
|
|
model DataMigration {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
name String @unique @db.VarChar
|
|
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz(3)
|
|
finishedAt DateTime? @map("finished_at") @db.Timestamptz(3)
|
|
|
|
@@map("_data_migrations")
|
|
}
|
|
|
|
model AppConfig {
|
|
id String @id @db.VarChar
|
|
value Json @db.JsonB
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
lastUpdatedBy String? @map("last_updated_by") @db.VarChar
|
|
|
|
lastUpdatedByUser User? @relation(fields: [lastUpdatedBy], references: [id], onDelete: SetNull)
|
|
|
|
@@map("app_configs")
|
|
}
|
|
|
|
model UserStripeCustomer {
|
|
userId String @id @map("user_id") @db.VarChar
|
|
stripeCustomerId String @unique @map("stripe_customer_id") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("user_stripe_customers")
|
|
}
|
|
|
|
model Subscription {
|
|
id Int @id @default(autoincrement()) @db.Integer
|
|
targetId String @map("target_id") @db.VarChar
|
|
plan String @db.VarChar(20)
|
|
// yearly/monthly/lifetime
|
|
recurring String @db.VarChar(20)
|
|
// onetime subscription or anything else
|
|
variant String? @db.VarChar(20)
|
|
quantity Int @default(1) @db.Integer
|
|
// subscription.id, null for lifetime payment or one time payment subscription
|
|
stripeSubscriptionId String? @unique @map("stripe_subscription_id")
|
|
// stripe schedule id
|
|
stripeScheduleId String? @map("stripe_schedule_id") @db.VarChar
|
|
// subscription provider: stripe or revenuecat
|
|
provider Provider @default(stripe)
|
|
// iap store for revenuecat subscriptions
|
|
iapStore IapStore? @map("iap_store")
|
|
// revenuecat entitlement name like "Pro" / "AI"
|
|
rcEntitlement String? @map("rc_entitlement") @db.VarChar
|
|
// revenuecat product id like "app.affine.pro.Annual"
|
|
rcProductId String? @map("rc_product_id") @db.VarChar
|
|
// external reference, appstore originalTransactionId or play purchaseToken
|
|
rcExternalRef String? @map("rc_external_ref") @db.VarChar
|
|
// subscription.status, active/past_due/canceled/unpaid...
|
|
status String @db.VarChar(20)
|
|
// subscription.current_period_start
|
|
start DateTime @map("start") @db.Timestamptz(3)
|
|
// subscription.current_period_end, null for lifetime payment
|
|
end DateTime? @map("end") @db.Timestamptz(3)
|
|
// subscription.billing_cycle_anchor
|
|
nextBillAt DateTime? @map("next_bill_at") @db.Timestamptz(3)
|
|
// subscription.canceled_at
|
|
canceledAt DateTime? @map("canceled_at") @db.Timestamptz(3)
|
|
// subscription.trial_start
|
|
trialStart DateTime? @map("trial_start") @db.Timestamptz(3)
|
|
// subscription.trial_end
|
|
trialEnd DateTime? @map("trial_end") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
@@unique([targetId, plan])
|
|
@@map("subscriptions")
|
|
}
|
|
|
|
enum Provider {
|
|
stripe
|
|
revenuecat
|
|
}
|
|
|
|
enum IapStore {
|
|
app_store
|
|
play_store
|
|
}
|
|
|
|
model Invoice {
|
|
stripeInvoiceId String @id @map("stripe_invoice_id")
|
|
targetId String @map("target_id") @db.VarChar
|
|
currency String @db.VarChar(3)
|
|
// CNY 12.50 stored as 1250
|
|
amount Int @db.Integer
|
|
status String @db.VarChar(20)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
// billing reason
|
|
reason String? @db.VarChar
|
|
lastPaymentError String? @map("last_payment_error") @db.Text
|
|
// stripe hosted invoice link
|
|
link String? @db.Text
|
|
// whether the onetime subscription has been redeemed
|
|
onetimeSubscriptionRedeemed Boolean @default(false) @map("onetime_subscription_redeemed")
|
|
|
|
@@index([targetId])
|
|
@@map("invoices")
|
|
}
|
|
|
|
model License {
|
|
key String @id @map("key") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
revealedAt DateTime? @map("revealed_at") @db.Timestamptz(3)
|
|
installedAt DateTime? @map("installed_at") @db.Timestamptz(3)
|
|
validateKey String? @map("validate_key") @db.VarChar
|
|
|
|
@@map("licenses")
|
|
}
|
|
|
|
model InstalledLicense {
|
|
key String @id @map("key") @db.VarChar
|
|
workspaceId String @unique @map("workspace_id") @db.VarChar
|
|
quantity Int @default(1) @db.Integer
|
|
recurring String @db.VarChar
|
|
variant String? @db.VarChar
|
|
installedAt DateTime @default(now()) @map("installed_at") @db.Timestamptz(3)
|
|
validateKey String @map("validate_key") @db.VarChar
|
|
validatedAt DateTime @map("validated_at") @db.Timestamptz(3)
|
|
expiredAt DateTime? @map("expired_at") @db.Timestamptz(3)
|
|
license Bytes? @db.ByteA
|
|
|
|
@@map("installed_licenses")
|
|
}
|
|
|
|
enum BlobStatus {
|
|
pending
|
|
completed
|
|
}
|
|
|
|
// Blob table only exists for fast non-data queries.
|
|
// like, total size of blobs in a workspace, or blob list for sync service.
|
|
// it should only be a map of metadata of blobs stored anywhere else
|
|
model Blob {
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
key String @db.VarChar
|
|
size Int @db.Integer
|
|
mime String @db.VarChar
|
|
status BlobStatus @default(completed)
|
|
uploadId String? @map("upload_id") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
AiWorkspaceBlobEmbedding AiWorkspaceBlobEmbedding[]
|
|
|
|
@@id([workspaceId, key])
|
|
@@index([workspaceId, status, deletedAt])
|
|
@@map("blobs")
|
|
}
|
|
|
|
enum NotificationType {
|
|
Mention
|
|
Invitation
|
|
InvitationAccepted
|
|
InvitationBlocked
|
|
InvitationRejected
|
|
InvitationReviewRequest
|
|
InvitationReviewApproved
|
|
InvitationReviewDeclined
|
|
Comment
|
|
CommentMention
|
|
}
|
|
|
|
enum NotificationLevel {
|
|
// Makes a sound and appears as a heads-up notification
|
|
High
|
|
// Makes a sound
|
|
Default
|
|
// Makes no sound
|
|
Low
|
|
Min
|
|
None
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
level NotificationLevel
|
|
read Boolean @default(false)
|
|
type NotificationType
|
|
body Json @db.JsonB
|
|
|
|
user User @relation(name: "user_notifications", fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
// for user notifications list, including read and unread, ordered by createdAt
|
|
@@index([userId, createdAt, read])
|
|
@@map("notifications")
|
|
}
|
|
|
|
model UserSettings {
|
|
userId String @id @map("user_id") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
payload Json @db.JsonB
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("user_settings")
|
|
}
|
|
|
|
model Comment {
|
|
// NOTE: manually set this column type to identity in migration file
|
|
sid Int @unique @default(autoincrement()) @db.Integer
|
|
id String @id @default(uuid()) @db.VarChar
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("doc_id") @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
content Json @db.JsonB
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
|
|
// whether the comment is resolved
|
|
resolved Boolean @default(false) @map("resolved")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
replies Reply[]
|
|
|
|
@@index([workspaceId, docId, sid])
|
|
@@index([workspaceId, docId, updatedAt])
|
|
@@index([userId])
|
|
@@map("comments")
|
|
}
|
|
|
|
model Reply {
|
|
// NOTE: manually set this column type to identity in migration file
|
|
sid Int @unique @default(autoincrement()) @db.Integer
|
|
id String @id @default(uuid()) @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
commentId String @map("comment_id") @db.VarChar
|
|
// query new replies by workspaceId and docId
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("doc_id") @db.VarChar
|
|
content Json @db.JsonB
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([commentId, sid])
|
|
@@index([workspaceId, docId, updatedAt])
|
|
@@index([userId])
|
|
@@map("replies")
|
|
}
|
|
|
|
model CommentAttachment {
|
|
// NOTE: manually set this column type to identity in migration file
|
|
sid Int @unique @default(autoincrement())
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
docId String @map("doc_id") @db.VarChar
|
|
key String @db.VarChar
|
|
size Int @db.Integer
|
|
mime String @db.VarChar
|
|
name String @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
createdBy String? @map("created_by") @db.VarChar
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
// will delete creator record if creator's account is deleted
|
|
createdByUser User? @relation(name: "createdCommentAttachments", fields: [createdBy], references: [id], onDelete: SetNull)
|
|
|
|
@@id([workspaceId, docId, key])
|
|
@@map("comment_attachments")
|
|
}
|
|
|
|
model AccessToken {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
name String @db.VarChar
|
|
token String @unique @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@map("access_tokens")
|
|
}
|
|
|
|
model CalendarAccount {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
userId String @map("user_id") @db.VarChar
|
|
provider String @db.VarChar
|
|
providerAccountId String @map("provider_account_id") @db.VarChar
|
|
displayName String? @map("display_name") @db.VarChar
|
|
email String? @db.VarChar
|
|
providerPresetId String? @map("provider_preset_id") @db.VarChar
|
|
serverUrl String? @map("server_url") @db.VarChar
|
|
principalUrl String? @map("principal_url") @db.VarChar
|
|
calendarHomeUrl String? @map("calendar_home_url") @db.VarChar
|
|
username String? @db.VarChar
|
|
authType String? @map("auth_type") @db.VarChar
|
|
accessToken String? @map("access_token") @db.Text
|
|
refreshToken String? @map("refresh_token") @db.Text
|
|
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
|
|
scope String? @db.Text
|
|
status String @default("active") @db.VarChar
|
|
lastError String? @map("last_error") @db.Text
|
|
refreshIntervalMinutes Int @default(30) @map("refresh_interval_minutes")
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
subscriptions CalendarSubscription[]
|
|
|
|
@@unique([userId, provider, providerAccountId])
|
|
@@index([userId])
|
|
@@index([provider, providerAccountId])
|
|
@@map("calendar_accounts")
|
|
}
|
|
|
|
model CalendarSubscription {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
accountId String @map("account_id") @db.VarChar
|
|
provider String @db.VarChar
|
|
externalCalendarId String @map("external_calendar_id") @db.VarChar
|
|
displayName String? @map("display_name") @db.VarChar
|
|
timezone String? @db.VarChar
|
|
color String? @db.VarChar
|
|
enabled Boolean @default(true)
|
|
syncToken String? @map("sync_token") @db.Text
|
|
lastSyncAt DateTime? @map("last_sync_at") @db.Timestamptz(3)
|
|
customChannelId String? @map("custom_channel_id") @db.VarChar
|
|
customResourceId String? @map("custom_resource_id") @db.VarChar
|
|
channelExpiration DateTime? @map("channel_expiration") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
account CalendarAccount @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
workspaceItems WorkspaceCalendarItem[]
|
|
events CalendarEvent[]
|
|
|
|
@@unique([accountId, externalCalendarId])
|
|
@@index([accountId])
|
|
@@index([provider, externalCalendarId])
|
|
@@map("calendar_subscriptions")
|
|
}
|
|
|
|
model WorkspaceCalendar {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
workspaceId String @map("workspace_id") @db.VarChar
|
|
createdByUserId String @map("created_by_user_id") @db.VarChar
|
|
displayNameOverride String? @map("display_name_override") @db.VarChar
|
|
colorOverride String? @map("color_override") @db.VarChar
|
|
enabled Boolean @default(true)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
|
createdByUser User @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
|
|
items WorkspaceCalendarItem[]
|
|
|
|
@@index([workspaceId])
|
|
@@map("workspace_calendars")
|
|
}
|
|
|
|
model WorkspaceCalendarItem {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
workspaceCalendarId String @map("workspace_calendar_id") @db.VarChar
|
|
subscriptionId String @map("subscription_id") @db.VarChar
|
|
sortOrder Int? @map("sort_order")
|
|
colorOverride String? @map("color_override") @db.VarChar
|
|
enabled Boolean @default(true)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
workspaceCalendar WorkspaceCalendar @relation(fields: [workspaceCalendarId], references: [id], onDelete: Cascade)
|
|
subscription CalendarSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([workspaceCalendarId, subscriptionId])
|
|
@@index([subscriptionId])
|
|
@@map("workspace_calendar_items")
|
|
}
|
|
|
|
model CalendarEvent {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
subscriptionId String @map("subscription_id") @db.VarChar
|
|
externalEventId String @map("external_event_id") @db.VarChar
|
|
recurrenceId String? @map("recurrence_id") @db.VarChar
|
|
etag String? @db.VarChar
|
|
status String? @db.VarChar
|
|
title String? @db.VarChar
|
|
description String? @db.Text
|
|
location String? @db.VarChar
|
|
startAtUtc DateTime @map("start_at_utc") @db.Timestamptz(3)
|
|
endAtUtc DateTime @map("end_at_utc") @db.Timestamptz(3)
|
|
originalTimezone String? @map("original_timezone") @db.VarChar
|
|
allDay Boolean @default(false) @map("all_day")
|
|
providerUpdatedAt DateTime? @map("provider_updated_at") @db.Timestamptz(3)
|
|
raw Json @db.JsonB
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
subscription CalendarSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
instances CalendarEventInstance[]
|
|
|
|
@@unique([subscriptionId, externalEventId, recurrenceId])
|
|
@@index([subscriptionId, startAtUtc])
|
|
@@index([subscriptionId, endAtUtc])
|
|
@@map("calendar_events")
|
|
}
|
|
|
|
model CalendarEventInstance {
|
|
id String @id @default(uuid()) @db.VarChar
|
|
calendarEventId String @map("calendar_event_id") @db.VarChar
|
|
recurrenceId String @map("recurrence_id") @db.VarChar
|
|
startAtUtc DateTime @map("start_at_utc") @db.Timestamptz(3)
|
|
endAtUtc DateTime @map("end_at_utc") @db.Timestamptz(3)
|
|
originalTimezone String? @map("original_timezone") @db.VarChar
|
|
allDay Boolean @default(false) @map("all_day")
|
|
providerUpdatedAt DateTime? @map("provider_updated_at") @db.Timestamptz(3)
|
|
raw Json @db.JsonB
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
calendarEvent CalendarEvent @relation(fields: [calendarEventId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([calendarEventId, recurrenceId])
|
|
@@index([calendarEventId, startAtUtc])
|
|
@@map("calendar_event_instances")
|
|
}
|