# Приложения, хранилище и взаимодействия Представление C4 Container раскрывает внутреннее устройство WealthLedger. Контейнер здесь означает приложение или хранилище, а не Docker-контейнер. Внешние системы совпадают с [System Context](context.md). ## Представление Container ```mermaid flowchart LR U["Частный инвестор
Человек"] subgraph W["WealthLedger · программная система"] F["React SPA
TypeScript, Vite, Mantine, TanStack Query"] B["Backend API
NestJS, Node.js"] D[("Локальная база
Embedded SQLite")] F -->|"HTTP JSON /api/v1; Bearer и auth cookie"| B B -->|"Prisma + better-sqlite3; доступ к локальному файлу"| D end T["T-Bank Invest API
Внешняя система"] M["MOEX ISS
Внешняя система"] N["Реестр НКР
Внешняя система"] C["T-Bank Brands CDN
Внешняя система"] 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 snapshot во время разработки. В runtime SPA вызывает API, а не генератор или OpenAPI endpoint. Форма запросов и ошибок описана в [HTTP API](http-api.md); направление генерации принято в [ADR 002](../adr/002-nest-generated-openapi-contract.md), а доверие frontend к предоставленному snapshot — в [ADR 014](../adr/014-trusted-openapi-input.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) отделяет это от хранения учётных данных. ## Сохранение и восстановление данных Резервная копия SQLite должна быть согласованной: для работающей базы используется SQLite backup API с учётом WAL. Несогласованное копирование файлов базы и WAL не сохраняет целостный снимок. Восстановление проверяется на отдельной копии. Ключ шифрования подключения хранится отдельно от базы; его потеря делает сохранённый T-Bank token нечитаемым. Откат приложения требует совместимости предыдущей версии с сохранённой схемой. Расширенная банковскими таблицами схема сохраняет пользователей, сессии и подключения. Возврат старого backup поверх новой истории удалил бы введённые банковские операции; откат приложения не равнозначен откату данных. Совместимость произвольных будущих версий этим не гарантируется. ## Стратегия решения и её основания Локальная база и 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 должны быть согласованы. `localhost` и `127.0.0.1` задают разные browser-границы для cookie и CORS. Локальный HTTP поддерживается в development-режиме backend; в остальных режимах refresh-cookie имеет Secure flag и требует защищённого соединения. Наличие локального запуска не задаёт production-топологию или гарантии SLA. Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования подключений и согласование изменений подключения между распределёнными экземплярами не входят в реализованный механизм. Эти ограничения не позволяют переносить локальную схему на несколько экземпляров без отдельного решения о состоянии, credentials и лимитах. Runtime-сценарии раскрываются у соответствующих владельцев: [восстановление сессии](../domains/authentication/overview.md#восстановление-сессии), [сохранение подключения](../integrations/tbank.md#проверка-и-сохранение-подключения) и [загрузка портфеля](../domains/portfolio/flows/portfolio-loading.md).