feat!: persist T-Bank data and synchronize reads
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.
This commit is contained in:
2026-09-26 21:29:15 +03:00
parent 0862d56c4e
commit d6e6c4ace5
116 changed files with 18321 additions and 5409 deletions
+205
View File
@@ -25,7 +25,212 @@ 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 {