634 lines
19 KiB
Plaintext
634 lines
19 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")
|
|
notificationPreferences NotificationPreference?
|
|
notifications Notification[]
|
|
pushSubscriptions PushSubscription[]
|
|
billPayees BillPayee[]
|
|
bills Bill[]
|
|
billPayments BillPayment[]
|
|
creditScoreEntries CreditScoreEntry[]
|
|
transactionComments TransactionComment[]
|
|
}
|
|
|
|
model NotificationPreference {
|
|
id String @id @default(uuid())
|
|
userId String @unique
|
|
emailEnabled Boolean @default(true)
|
|
pushEnabled Boolean @default(false)
|
|
minSeverity String @default("info")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
type String
|
|
severity String @default("info")
|
|
title String
|
|
body String
|
|
metadata Json @default("{}")
|
|
channels String[] @default([])
|
|
readAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([userId, readAt])
|
|
@@index([type, createdAt])
|
|
}
|
|
|
|
model PushSubscription {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
endpoint String @unique
|
|
p256dh String
|
|
auth String
|
|
userAgent String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
lastUsedAt DateTime?
|
|
revokedAt DateTime?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, revokedAt])
|
|
}
|
|
|
|
model BillPayee {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
name String
|
|
nickname String?
|
|
category String?
|
|
website String?
|
|
accountNumberLast4 String?
|
|
metadata Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
bills Bill[]
|
|
|
|
@@index([userId, name])
|
|
}
|
|
|
|
model Bill {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
payeeId String?
|
|
name String
|
|
amount Decimal
|
|
currency String @default("USD")
|
|
dueDate DateTime
|
|
status String @default("pending")
|
|
recurrence String @default("none")
|
|
autopay Boolean @default(false)
|
|
reminderDays Int @default(3)
|
|
notes String?
|
|
metadata Json @default("{}")
|
|
paidAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
payee BillPayee? @relation(fields: [payeeId], references: [id], onDelete: SetNull)
|
|
payments BillPayment[]
|
|
|
|
@@index([userId, dueDate])
|
|
@@index([userId, status])
|
|
@@index([payeeId])
|
|
}
|
|
|
|
model BillPayment {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
billId String
|
|
amount Decimal
|
|
paidAt DateTime @default(now())
|
|
method String @default("manual")
|
|
confirmationNumber String?
|
|
notes String?
|
|
metadata Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
bill Bill @relation(fields: [billId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, paidAt])
|
|
@@index([billId])
|
|
}
|
|
|
|
model CreditScoreEntry {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
score Int
|
|
bureau String @default("unknown")
|
|
source String @default("manual")
|
|
model String @default("vantage_score_3")
|
|
scoreDate DateTime
|
|
factors Json @default("{}")
|
|
metadata Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, scoreDate])
|
|
@@index([userId, bureau])
|
|
}
|
|
|
|
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[]
|
|
comments TransactionComment[]
|
|
}
|
|
|
|
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 TransactionComment {
|
|
id String @id @default(uuid())
|
|
rawTransactionId String
|
|
userId String
|
|
body String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
raw TransactionRaw @relation(fields: [rawTransactionId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([rawTransactionId, createdAt])
|
|
@@index([userId, createdAt])
|
|
}
|
|
|
|
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
|
|
nonceHash String @default("")
|
|
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)
|
|
}
|