Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 10m45s
Conventional commits / commits (pull_request) Successful in 29s
Backend security / security (pull_request) Successful in 8s
Backend verify / verify (pull_request) Failing after 10m37s
Store account membership, portfolio, operations, forecasts and market sources in SQLite. Coalesce scoped refreshes, fence connection revisions, preserve complete financial snapshots and stabilize operation period state. BREAKING CHANGE: investment reads return data/meta resource envelopes and locally persisted snapshots; consumers must use the matching OpenAPI contract and explicit synchronization endpoints.
306 lines
8.3 KiB
Plaintext
306 lines
8.3 KiB
Plaintext
generator client {
|
|
provider = "prisma-client"
|
|
output = "../generated/prisma"
|
|
moduleFormat = "cjs"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "sqlite"
|
|
}
|
|
|
|
model User {
|
|
id String @id
|
|
email String @unique
|
|
passwordHash String
|
|
bankCards BankCard[]
|
|
expenseCategories ExpenseCategory[]
|
|
bankCardOperations BankCardOperation[]
|
|
sessions Session[]
|
|
tbankConnection TbankConnection?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model TbankConnection {
|
|
userId String @unique
|
|
encryptedToken String
|
|
connectedAt DateTime
|
|
revision String @default(uuid())
|
|
invalidToken Boolean @default(false)
|
|
membershipAt DateTime?
|
|
membershipRevision Int @default(0)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
accounts TbankAccount[]
|
|
forecasts TbankForecast[]
|
|
work TbankSyncWork[]
|
|
|
|
@@unique([userId, revision])
|
|
}
|
|
|
|
model TbankAccount {
|
|
userId String
|
|
revision String
|
|
accountId String
|
|
type String
|
|
name String
|
|
openedOn String?
|
|
upstreamOrder Int
|
|
operationsRevision Int @default(0)
|
|
historyFrom DateTime?
|
|
historyBoundary String @default("unknown")
|
|
connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict)
|
|
portfolio TbankPortfolio?
|
|
operations TbankOperation[]
|
|
coverage TbankOperationCoverage[]
|
|
|
|
@@id([userId, revision, accountId])
|
|
}
|
|
|
|
model TbankPortfolio {
|
|
userId String
|
|
revision String
|
|
accountId String
|
|
payload Json
|
|
mappingVersion Int @default(1)
|
|
fetchedAt DateTime
|
|
dataRevision Int @default(1)
|
|
account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict)
|
|
|
|
@@id([userId, revision, accountId])
|
|
}
|
|
|
|
model TbankOperation {
|
|
userId String
|
|
revision String
|
|
accountId String
|
|
brokerOperationId String
|
|
occurredAt DateTime
|
|
instrumentUid String?
|
|
category String
|
|
payload Json
|
|
mappingVersion Int @default(1)
|
|
account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict)
|
|
|
|
@@id([userId, revision, accountId, brokerOperationId])
|
|
@@index([userId, revision, accountId, occurredAt, brokerOperationId])
|
|
@@index([userId, revision, accountId, instrumentUid, occurredAt])
|
|
@@index([userId, revision, accountId, category, occurredAt])
|
|
}
|
|
|
|
model TbankOperationCoverage {
|
|
userId String
|
|
revision String
|
|
accountId String
|
|
instrumentPredicate String @default("")
|
|
from DateTime
|
|
to DateTime
|
|
fetchedAt DateTime
|
|
asOf DateTime
|
|
account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict)
|
|
|
|
@@id([userId, revision, accountId, instrumentPredicate, from, to])
|
|
}
|
|
|
|
model TbankForecast {
|
|
userId String
|
|
revision String
|
|
scopeKey String
|
|
membershipRevision Int
|
|
payload Json
|
|
dependencyRevisions Json
|
|
sourceTimestamps Json
|
|
calculatedAt DateTime
|
|
dataRevision Int @default(1)
|
|
connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict)
|
|
|
|
@@id([userId, revision, scopeKey])
|
|
}
|
|
|
|
model TbankInstrument {
|
|
provider String
|
|
uid String
|
|
figi String?
|
|
ticker String?
|
|
instrumentType String
|
|
metadata Json
|
|
fetchedAt DateTime
|
|
dataRevision Int @default(1)
|
|
sources TbankInstrumentSource[]
|
|
candles TbankCandle[]
|
|
candleCoverage TbankCandleCoverage[]
|
|
|
|
@@id([provider, uid])
|
|
@@index([provider, figi])
|
|
@@index([provider, ticker, instrumentType])
|
|
}
|
|
|
|
model TbankCatalogDiscovery {
|
|
provider String
|
|
instrumentType String
|
|
query String
|
|
instrumentUids Json
|
|
fetchedAt DateTime
|
|
dataRevision Int @default(1)
|
|
|
|
@@id([provider, instrumentType, query])
|
|
}
|
|
|
|
// Empty and ambiguous successful UID/FIGI/ticker lookups are distinct from failures.
|
|
model TbankInstrumentLookup {
|
|
provider String
|
|
identifierType String
|
|
identifier String
|
|
matchedUids Json
|
|
fetchedAt DateTime
|
|
dataRevision Int @default(1)
|
|
|
|
@@id([provider, identifierType, identifier])
|
|
}
|
|
|
|
// Source records contain no holdings, personal estimates or credentials errors.
|
|
model TbankInstrumentSource {
|
|
provider String
|
|
instrumentUid String
|
|
source String
|
|
kind String
|
|
rangeKey String
|
|
payload Json
|
|
fetchedAt DateTime
|
|
dataRevision Int @default(1)
|
|
instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade)
|
|
|
|
@@id([provider, instrumentUid, source, kind, rangeKey])
|
|
}
|
|
|
|
model TbankCandle {
|
|
provider String
|
|
instrumentUid String
|
|
interval String
|
|
time DateTime
|
|
close String
|
|
payload Json
|
|
isComplete Boolean
|
|
fetchedAt DateTime
|
|
instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade)
|
|
|
|
@@id([provider, instrumentUid, interval, time])
|
|
}
|
|
|
|
model TbankCandleCoverage {
|
|
provider String
|
|
instrumentUid String
|
|
interval String
|
|
from DateTime
|
|
to DateTime
|
|
isComplete Boolean
|
|
fetchedAt DateTime
|
|
dataRevision Int @default(1)
|
|
instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade)
|
|
|
|
@@id([provider, instrumentUid, interval, from, to])
|
|
}
|
|
|
|
model TbankSyncWork {
|
|
id String @id
|
|
userId String
|
|
revision String
|
|
scopeKey String
|
|
intent Json
|
|
state String
|
|
attemptId String
|
|
processRunId String
|
|
priority Int
|
|
progress Json
|
|
errorCode String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict)
|
|
staged TbankSyncStage[]
|
|
|
|
@@unique([userId, revision, scopeKey])
|
|
@@index([userId, revision, state, priority])
|
|
@@unique([id, attemptId])
|
|
}
|
|
|
|
model TbankSyncStage {
|
|
workId String
|
|
attemptId String
|
|
sourceKey String
|
|
itemKey String
|
|
payload Json
|
|
work TbankSyncWork @relation(fields: [workId, attemptId], references: [id, attemptId], onDelete: Cascade, onUpdate: Restrict)
|
|
|
|
@@id([workId, attemptId, sourceKey, itemKey])
|
|
}
|
|
|
|
model Session {
|
|
id String @id
|
|
userId String
|
|
refreshTokenHash String
|
|
expiresAt DateTime
|
|
revokedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model BankCard {
|
|
id String @id
|
|
userId String
|
|
name String
|
|
bankName String?
|
|
lastFour String?
|
|
paymentSystem String?
|
|
openingBalanceKopecks BigInt
|
|
openingDate String
|
|
version Int @default(1)
|
|
archivedAt DateTime?
|
|
createFingerprint String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
|
|
operations BankCardOperation[] @relation("SourceCard")
|
|
incomingOperations BankCardOperation[] @relation("TargetCard")
|
|
@@index([userId])
|
|
}
|
|
model ExpenseCategory {
|
|
id String @id
|
|
userId String
|
|
name String
|
|
nameKey String
|
|
seedKey String?
|
|
version Int @default(1)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
|
|
operations BankCardOperation[]
|
|
@@unique([userId, nameKey])
|
|
@@unique([userId, seedKey])
|
|
}
|
|
model BankCardOperation {
|
|
id String @id
|
|
userId String
|
|
kind String
|
|
cardId String
|
|
targetCardId String?
|
|
categoryId String?
|
|
originalExpenseId String?
|
|
amountKopecks BigInt
|
|
date String
|
|
description String?
|
|
version Int @default(1)
|
|
deletedAt DateTime?
|
|
createFingerprint String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
|
|
card BankCard @relation("SourceCard", fields: [cardId], references: [id], onDelete: Restrict)
|
|
targetCard BankCard? @relation("TargetCard", fields: [targetCardId], references: [id], onDelete: Restrict)
|
|
category ExpenseCategory? @relation(fields: [categoryId], references: [id], onDelete: Restrict)
|
|
originalExpense BankCardOperation? @relation("RefundExpense", fields: [originalExpenseId], references: [id], onDelete: Restrict, onUpdate: NoAction)
|
|
refunds BankCardOperation[] @relation("RefundExpense")
|
|
@@index([userId, cardId, date])
|
|
@@index([userId, targetCardId, date])
|
|
@@index([originalExpenseId])
|
|
}
|