ledgerone_backend/prisma/schema.prisma

478 lines
14 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
fullName String?
phone String?
companyName String?
addressLine1 String?
addressLine2 String?
city String?
state String?
postalCode String?
country String?
role String @default("user")
emailVerified Boolean @default(false)
twoFactorEnabled Boolean @default(false)
twoFactorSecret String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
rules Rule[]
exports ExportLog[]
exportDownloadTokens ExportDownloadToken[]
auditLogs AuditLog[]
abuseEvents AbuseEvent[]
googleConnection GoogleConnection?
apiKeys ApiKey[]
emailVerificationToken EmailVerificationToken?
passwordResetTokens PasswordResetToken[]
refreshTokens RefreshToken[]
sessions Session[]
socialAccounts SocialAccount[]
subscription Subscription?
taxReturns TaxReturn[]
csvImportMappings CsvImportMapping[]
createdHouseholds Household[] @relation("HouseholdCreator")
householdMemberships HouseholdMember[]
sentHouseholdInvites HouseholdInvite[] @relation("HouseholdInviteInviter")
acceptedHouseholdInvites HouseholdInvite[] @relation("HouseholdInviteAccepter")
ownedAccounts Account[] @relation("AccountOwnerUser")
createdHouseholdGoals HouseholdGoal[] @relation("HouseholdGoalCreator")
}
model Household {
id String @id @default(uuid())
name String
createdByUserId String
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy User @relation("HouseholdCreator", fields: [createdByUserId], references: [id], onDelete: Cascade)
members HouseholdMember[]
invites HouseholdInvite[]
accounts Account[]
goals HouseholdGoal[]
@@index([createdByUserId, createdAt])
}
model HouseholdGoal {
id String @id @default(uuid())
householdId String
createdByUserId String
name String
description String?
targetAmount Decimal
currentAmount Decimal @default(0)
isoCurrencyCode String @default("USD")
targetDate DateTime?
priority String @default("medium")
status String @default("active")
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
createdBy User @relation("HouseholdGoalCreator", fields: [createdByUserId], references: [id], onDelete: Cascade)
@@index([householdId, status])
@@index([createdByUserId, createdAt])
@@index([targetDate])
}
model HouseholdMember {
id String @id @default(uuid())
householdId String
userId String
role String @default("member")
status String @default("active")
joinedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([householdId, userId])
@@index([userId, status])
@@index([householdId, role])
}
model HouseholdInvite {
id String @id @default(uuid())
householdId String
invitedById String
email String
role String @default("member")
tokenHash String @unique
status String @default("pending")
expiresAt DateTime
acceptedAt DateTime?
acceptedById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
household Household @relation(fields: [householdId], references: [id], onDelete: Cascade)
invitedBy User @relation("HouseholdInviteInviter", fields: [invitedById], references: [id], onDelete: Cascade)
acceptedBy User? @relation("HouseholdInviteAccepter", fields: [acceptedById], references: [id], onDelete: SetNull)
@@index([householdId, status])
@@index([email, status])
@@index([expiresAt])
}
model SocialAccount {
id String @id @default(uuid())
userId String
provider String
providerAccountId String
email String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@index([userId])
}
model Account {
id String @id @default(uuid())
userId String
householdId String?
ownerUserId String?
ownershipType String @default("mine")
institutionName String
accountType String
mask String?
plaidAccessToken String?
plaidItemId String?
plaidAccountId String? @unique
tellerAccessToken String?
tellerEnrollmentId String?
tellerAccountId String? @unique
currentBalance Decimal?
availableBalance Decimal?
isoCurrencyCode String?
lastBalanceSync DateTime?
lastTransactionSync DateTime?
lastSyncAttemptAt DateTime?
syncStatus String @default("idle")
lastSyncError String?
syncConsecutiveFailures Int @default(0)
plaidWebhookCode String?
plaidWebhookAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
household Household? @relation(fields: [householdId], references: [id], onDelete: SetNull)
ownerUser User? @relation("AccountOwnerUser", fields: [ownerUserId], references: [id], onDelete: SetNull)
transactionsRaw TransactionRaw[]
@@index([householdId, ownershipType])
@@index([ownerUserId])
@@index([plaidItemId])
@@index([tellerEnrollmentId])
}
model PlaidWebhookEvent {
id String @id @default(uuid())
itemId String?
webhookType String
webhookCode String
payload Json
status String @default("received")
error String?
receivedAt DateTime @default(now())
processedAt DateTime?
@@index([itemId, receivedAt])
@@index([webhookType, webhookCode])
}
model TransactionRaw {
id String @id @default(uuid())
accountId String
bankTransactionId String @unique
date DateTime
amount Decimal
description String
rawPayload Json
ingestedAt DateTime @default(now())
source String
account Account @relation(fields: [accountId], references: [id])
derived TransactionDerived?
ruleExecutions RuleExecution[]
}
model TransactionDerived {
id String @id @default(uuid())
rawTransactionId String @unique
userCategory String?
userNotes String?
attribution String @default("mine")
splitMode String @default("none")
splitMinePercent Decimal?
splitYoursPercent Decimal?
isHidden Boolean @default(false)
modifiedAt DateTime @default(now())
modifiedBy String
raw TransactionRaw @relation(fields: [rawTransactionId], references: [id])
}
model CsvImportMapping {
id String @id @default(uuid())
userId String
headerSignature String
name String?
mapping Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lastUsedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, headerSignature])
@@index([userId, lastUsedAt])
}
model Rule {
id String @id @default(uuid())
userId String
name String
priority Int
conditions Json
actions Json
isActive Boolean @default(true)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
executions RuleExecution[]
}
model RuleExecution {
id String @id @default(uuid())
ruleId String
transactionId String
executedAt DateTime @default(now())
result Json
rule Rule @relation(fields: [ruleId], references: [id])
transaction TransactionRaw @relation(fields: [transactionId], references: [id])
}
model ExportLog {
id String @id @default(uuid())
userId String
format String @default("csv")
destination String @default("download")
filters Json
rowCount Int
fileName String?
mimeType String?
fileHash String?
ipAddress String?
userAgent String?
metadata Json @default("{}")
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
@@index([userId, createdAt])
@@index([format, createdAt])
}
model ExportDownloadToken {
id String @id @default(uuid())
userId String
tokenHash String @unique
format String
filters Json
storageProvider String?
storageKey String?
fileName String?
mimeType String?
rowCount Int?
fileHash String?
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, createdAt])
@@index([expiresAt])
}
model AuditLog {
id String @id @default(uuid())
userId String
action String
metadata Json
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
model AbuseEvent {
id String @id @default(uuid())
userId String?
eventType String
riskPoints Int
severity String
ipAddress String?
userAgent String?
metadata Json @default("{}")
createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([userId, createdAt])
@@index([eventType, createdAt])
}
model GoogleConnection {
id String @id @default(uuid())
userId String @unique
googleEmail String
refreshToken String
accessToken String?
spreadsheetId String?
driveMirrorEnabled Boolean @default(false)
driveMirrorStatus String @default("not_started")
driveMirrorSpreadsheetUrl String?
driveMirrorLastSyncedAt DateTime?
isConnected Boolean @default(true)
connectedAt DateTime @default(now())
lastSyncedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model ApiKey {
id String @id @default(uuid())
userId String
name String
prefix String
keyHash String @unique
scopes String[] @default(["transactions:read"])
lastUsedAt DateTime?
revokedAt DateTime?
expiresAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, createdAt])
@@index([prefix])
}
model EmailVerificationToken {
id String @id @default(uuid())
userId String @unique
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model PasswordResetToken {
id String @id @default(uuid())
userId String
token String @unique
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Session {
id String @id @default(uuid())
userId String
ipHash String
userAgentHash String
createdAt DateTime @default(now())
lastSeenAt DateTime @default(now())
expiresAt DateTime
revokedAt DateTime?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
refreshTokens RefreshToken[]
@@index([userId, revokedAt])
}
model RefreshToken {
id String @id @default(uuid())
userId String
sessionId String?
tokenHash String @unique
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
session Session? @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId])
}
model Subscription {
id String @id @default(uuid())
userId String @unique
plan String @default("free")
stripeCustomerId String?
stripeSubId String?
currentPeriodEnd DateTime?
cancelAtPeriodEnd Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model TaxReturn {
id String @id @default(uuid())
userId String
taxYear Int
filingType String
jurisdictions Json
status String @default("draft")
summary Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
documents TaxDocument[]
}
model TaxDocument {
id String @id @default(uuid())
taxReturnId String
docType String
metadata Json @default("{}")
createdAt DateTime @default(now())
taxReturn TaxReturn @relation(fields: [taxReturnId], references: [id], onDelete: Cascade)
}