452 lines
14 KiB
Plaintext
452 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")
|
|
}
|
|
|
|
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[]
|
|
|
|
@@index([createdByUserId, createdAt])
|
|
}
|
|
|
|
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)
|
|
}
|