Document exact balances, corrections, archive behavior and safe migration and rollback. Update product navigation and UI catalog.
152 lines
13 KiB
Markdown
152 lines
13 KiB
Markdown
# Приложения, хранилище и взаимодействия
|
|
|
|
Представление C4 Container раскрывает внутреннее устройство WealthLedger.
|
|
Контейнер здесь означает приложение или хранилище, а не Docker-контейнер.
|
|
Внешние системы совпадают с [System Context](context.md).
|
|
|
|
## Представление Container
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
U["Частный инвестор<br/>Человек"]
|
|
subgraph W["WealthLedger · программная система"]
|
|
F["React SPA<br/>TypeScript, Vite, Mantine, TanStack Query"]
|
|
B["Backend API<br/>NestJS, Node.js"]
|
|
D[("Локальная база<br/>Embedded SQLite")]
|
|
F -->|"HTTP JSON /api/v1; Bearer и auth cookie"| B
|
|
B -->|"Prisma + better-sqlite3; доступ к локальному файлу"| D
|
|
end
|
|
T["T-Bank Invest API<br/>Внешняя система"]
|
|
M["MOEX ISS<br/>Внешняя система"]
|
|
N["Реестр НКР<br/>Внешняя система"]
|
|
C["T-Bank Brands CDN<br/>Внешняя система"]
|
|
U -->|"Просматривает данные и управляет подключением"| F
|
|
B -->|"gRPC over TLS; token пользователя"| T
|
|
B -->|"HTTPS; публичные данные"| M
|
|
B -->|"HTTPS; публичный HTML"| N
|
|
F -->|"HTTPS; изображения без авторизации"| C
|
|
```
|
|
|
|
SPA выполняется в браузере пользователя. Backend — отдельное локальное
|
|
приложение; SQLite используется внутри его процесса через библиотеку и файл
|
|
базы. Между API и SQLite нет сетевого database server. Граница системы на
|
|
схеме показывает принадлежность частей, а не их размещение на одном узле.
|
|
|
|
## Ответственность частей
|
|
|
|
| Часть | Ответственность |
|
|
| --- | --- |
|
|
| React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений |
|
|
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и серверные кэши |
|
|
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений и ручной банковской истории |
|
|
| Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности |
|
|
|
|
Frontend организован слоями `app → pages → features → entities → shared`:
|
|
каркас связывает маршруты, страницы собирают сценарии, а общие средства
|
|
обслуживают транспорт и представление. Backend остаётся модульным NestJS API,
|
|
где локальная аутентификация отделена от подключения брокера и инвестиционных
|
|
сценариев. Это внутренняя организация приложений, не набор микросервисов.
|
|
|
|
Финансовые HTTP-запросы браузера идут через backend. Пользователь вводит token
|
|
T-Bank в форме и отправляет его для сохранения, но API не возвращает token
|
|
в ответах; SPA не использует его для прямых брокерских запросов. Прямая
|
|
загрузка публичного изображения из CDN не проходит через эту финансовую
|
|
границу.
|
|
|
|
Типизированный frontend-клиент производится из OpenAPI backend во время
|
|
разработки. В runtime SPA вызывает API, а не генератор или OpenAPI endpoint.
|
|
Форма запросов и ошибок описана в [HTTP API](http-api.md); направление
|
|
генерации принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md).
|
|
|
|
## Хранимые данные и границы доступа
|
|
|
|
SQLite содержит пользователя с нормализованным email и hash пароля, его
|
|
сессии с hash текущего refresh token, сроком действия и признаком отзыва,
|
|
а также одно зашифрованное подключение T-Bank с временем подключения.
|
|
Пароли хешируются bcrypt; token брокера шифруется AES-256-GCM. Plaintext
|
|
credentials не являются сохраняемым представлением этих данных.
|
|
|
|
Ручной банковский учёт хранит карты, пользовательские категории и операции
|
|
в отдельных таблицах, связанных с User. Начальный остаток и суммы записаны
|
|
целыми bigint-копейками, календарные даты — днями без времени. Перевод хранится
|
|
одной операцией с двумя картами; возврат связан с исходной покупкой. Баланс
|
|
вычисляется из неудалённой истории и отдельно не сохраняется.
|
|
|
|
Банковские запросы проверяют локального владельца каждой связи и не используют
|
|
подключение брокера. Транзакции, ограничения уникальности и версии согласуют
|
|
переводы, возвраты и конкурирующие исправления. Fingerprint исходного ввода
|
|
и отметка удаления защищают от повторного создания после потерянного ответа
|
|
или перезапуска. Архив обратим и сохраняет историю. Подробности —
|
|
[банковские карты](../domains/cards/overview.md).
|
|
|
|
Сессии и подключение связаны с владельцем. Access token подтверждает
|
|
локального пользователя, но `accountId` из URL сам по себе не даёт права
|
|
читать брокерский счёт. Backend получает текущее подключение пользователя
|
|
и его generation перед использованием финансового кэша. Проверка доступности
|
|
счёта у брокера зависит от сценария: журнал выполняет её до чтения страницы
|
|
кэша; свежий портфель одного счёта может быть возвращён без нового запроса
|
|
списка счетов. При обновлении портфеля доступ проверяется у брокера.
|
|
|
|
Ротация сессии атомарно заменяет hash текущего refresh token: два
|
|
конкурентных запроса не могут оба успешно выполнить одну ротацию.
|
|
Вызов брокера и запись в локальную базу не образуют общую транзакцию:
|
|
при сохранении подключения сначала проверяется token, затем записывается
|
|
шифротекст. Подробности находятся в
|
|
[аутентификации](../domains/authentication/overview.md) и
|
|
[интеграции T-Bank](../integrations/tbank.md).
|
|
|
|
Брокерские счета, сводки, портфель, страницы журнала и будущие события
|
|
не являются долговременной историей в SQLite. Они получаются для текущего
|
|
подключения и могут временно кэшироваться. Перезапуск backend не
|
|
восстанавливает прежние финансовые снимки и cursor continuations из базы;
|
|
[модель свежести](cross-cutting/data-freshness.md) отделяет это от хранения
|
|
учётных данных.
|
|
|
|
## Стратегия решения и её основания
|
|
|
|
Локальная база и Prisma дают типизированный доступ к локальному состоянию
|
|
без отдельного сервера базы; исходные причины и пределы выбора сохранены
|
|
в [ADR 001](../adr/001-prisma-sqlite-local-mvp.md). Централизованная
|
|
[серверная граница T-Bank](../adr/003-tbank-server-side-grpc-boundary.md)
|
|
сосредоточивает credentials, лимиты и повторы вне браузера, а
|
|
[шифрование подключения](../adr/004-encrypted-per-user-tbank-token.md)
|
|
задаёт способ хранения брокерского секрета.
|
|
|
|
[Политика сессий](../adr/006-auth-session-policy.md) разделяет короткоживущий
|
|
access token и вращаемый refresh token. [Политика query-кэша](../adr/007-react-query-fetch-policy.md)
|
|
сохраняет доступные данные при обновлении и не запускает refetch при возврате
|
|
фокуса. [Ограниченные повторы T-Bank](../adr/005-tbank-upstream-resilience.md)
|
|
и [структурированные журналы](../adr/008-pino-backend-logging.md) обслуживают
|
|
внешнюю границу. ADR сохраняют контекст принятия решения; текущие детали
|
|
уточняются в тематических описаниях.
|
|
|
|
## Качество и ограничения
|
|
|
|
Точность представления денег требует сохранять валюту и decimal-значение;
|
|
неизвестная сумма не заменяется нулём. Свежесть наблюдения не равна
|
|
актуальности рынка в реальном времени. Временные ошибки внешнего поставщика
|
|
обрабатываются ограниченными повторами и локальными состояниями интерфейса,
|
|
без обещания успешного ответа при недоступном upstream.
|
|
|
|
Авторизация доступа, недопущение token в публичный ответ и атомарность
|
|
ротации сессии — отдельные свойства; наличие типизированного клиента
|
|
не доказывает их. Известные расхождения регистрации и деталей валидации
|
|
отмечены в [аутентификации](../domains/authentication/overview.md#регистрация-и-вход)
|
|
и [HTTP API](http-api.md#error-contract).
|
|
|
|
Подтверждённая топология — локальная SPA и локальный API с файлом SQLite.
|
|
Origin браузера, адрес API и cookie policy должны быть согласованы; условия
|
|
и диагностика описаны в [локальном окружении](../development/local-workspace.md).
|
|
Наличие локального запуска не задаёт production-топологию или гарантии SLA.
|
|
|
|
Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования
|
|
подключений и согласование изменений подключения между распределёнными
|
|
экземплярами не входят в реализованный механизм. Эти ограничения не
|
|
позволяют переносить локальную схему на несколько экземпляров без отдельного
|
|
решения о состоянии, credentials и лимитах.
|
|
|
|
Runtime-сценарии раскрываются у соответствующих владельцев:
|
|
[восстановление сессии](../domains/authentication/overview.md#восстановление-сессии),
|
|
[сохранение подключения](../integrations/tbank.md#проверка-и-сохранение-подключения)
|
|
и [загрузка портфеля](../domains/portfolio/flows/portfolio-loading.md).
|