Files
TrainigPlaner/docs/architecture.md
T
2026-09-30 15:05:05 +03:00

69 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура и границы первой версии
## Стек
.NET 10, ASP.NET Core, Blazor, EF Core, PostgreSQL. Один процесс приложения и отдельный контейнер БД. Без микросервисов.
## Зависимости
- Domain: простые предметные классы, без зависимостей от UI и хранилища.
- Application → Domain: прикладные сценарии и контракты хранилища.
- Infrastructure → Application: EF Core, PostgreSQL, авторизация и внешние сервисы.
- Web → Application + Infrastructure: интерфейс и регистрация зависимостей.
Infrastructure подключается в Web для сборки приложения. Компоненты работают через контракты Application.
## Программа
Клиент принадлежит тренеру. У клиента несколько сохранённых месячных программ. Программа содержит недели, недели — тренировки, тренировки — упорядоченные блоки. Блок содержит ровно одно упражнение.
Количество подходов (`SetsCount`) и отдых в минутах (`RestMinutes`) находятся у блока. У упражнения общие текстовые повторения и вес, текстовые разминка и примечание, nullable decimal процент от максимума, название, ссылка и флаг камеры. Процент вводит тренер; автоматического соответствия между повторениями и процентом нет. Сеты не поддерживаются.
Общий отдых вычисляется как сумма `SetsCount × RestMinutes` всех блоков, включая отдых после последнего подхода. Текст повторений на эту формулу не влияет. Длительность тренировки (`Duration`) вводится тренером текстом и не вычисляется из отдыха.
Миграция ExercisePrescription переносит общие вес и повторения из первого подхода, секунды делит на 60, старые сеты разделяет на отдельные блоки без перестановки упражнений. Перед применением нужна резервная копия. Откат с восстановлением индивидуальных подходов возможен только из резервной копии; автоматический Down запрещён.
Упражнение программы хранит снимок названия и ссылки из библиотеки. Изменение библиотеки не изменяет старые программы. Копирование создаёт новые идентификаторы всех элементов программы. История — список сохранённых программ клиента, а не журнал каждого нажатия клавиши.
## Конфигурация
Настраиваемые значения задаются в appsettings.json и переопределяются стандартным провайдером переменных окружения .NET. В именах переменных `__` разделяет секции. Docker Compose читает `.env` и передаёт переменные контейнеру. Сам .NET файл `.env` автоматически не читает.
Секреты не имеют рабочих значений по умолчанию и не хранятся в Git. Версии инструментов, имена маршрутов, CSS и структура данных не являются runtime-настройками.
## Этапы
1. Основа решения, правила разработки, предметные классы и конфигурация.
2. PostgreSQL, авторизация, клиенты и библиотека упражнений.
3. Редактор месячной программы и независимое копирование.
4. PDF и проверка полного сценария в Docker.
5. Адаптация под iPhone 15 — после первой версии.
## Сохранение и безопасность
UI использует Blazor Interactive Server. Каждая операция хранилища создаёт отдельный DbContext; общий контекст на долгоживущий Blazor circuit не используется. Репозитории проверяют тренера, полученного из серверного состояния авторизации. Экспорт получает идентификатор тренера из авторизованного HTTP-запроса.
Сохранение небольшой месячной программы заменяет её внутренние дочерние записи одной транзакцией. Идентификаторы недель, тренировок и блоков являются внутренними и не используются в ссылках. Это сохраняет простую реализацию для первого этапа; при необходимости частичных обновлений контракт репозитория можно сохранить. Идентификатор программы стабилен, `Version` защищает от перезаписи из другой вкладки. История программ не удаляется.
Автосохранение срабатывает после редактирования с задержкой из конфигурации. Незаполненное упражнение остаётся локальным черновиком до заполнения обязательных полей. Статус сохранения и ошибка видны в интерфейсе. Переход со страницы с несохранёнными данными требует подтверждения.
ASP.NET Core Identity: хеширование паролей, блокировка повторных неудачных входов, antiforgery для форм, HTTP-only cookie и периодическая проверка security stamp. Регистрации нет. Первый тренер создаётся из конфигурации только в пустой базе. Для HTTPS за прокси задаётся адрес доверенного прокси; произвольные forwarded-заголовки не принимаются.
PDF генерируется PDFsharp/MigraDoc, шрифты с кириллицей устанавливаются в Docker-образ. Каждая неделя — одна непрерывная альбомная A4-таблица. Общие заголовки недели и столбцов повторяются на продолжении страниц; тренировки разделяются цветными строками и имеют собственные итоги. Заголовок тренировки удерживается вместе с первой строкой, итог — с последней. Размеры, цвета и пути задаются в Pdf. Ссылки являются PDF-аннотациями; значок камеры встроен как статический рисунок и не зависит от emoji-шрифтов ОС.
## Публикация Blazor в Docker
В Web-проекте явно установлен `RequiresAspNetWebAssets=true`: при отдельном Docker-слое восстановления зависимостей Razor-файлы ещё не скопированы, поэтому автоматическое обнаружение компонентов не подходит. Dockerfile дополнительно проверяет наличие `wwwroot/_framework/blazor.web.js` после публикации. Это настройка сборки, а не runtime-параметр appsettings.
Документация: https://learn.microsoft.com/aspnet/core/blazor/project-structure?view=aspnetcore-10.0
## Группы библиотеки
У `LibraryExercise` необязательная ссылка `GroupId` на `ExerciseGroup`, обе записи принадлежат тренеру. Упражнения внутри разных групп независимы. Добавление из другой группы создаёт новую запись с копией названия и ссылки. Перетаскивание меняет только `GroupId`, не переписывая остальные поля. `null` означает «Без группы».
Удаление группы каскадно удаляет только её упражнения библиотеки. Снимки упражнений в программах не имеют внешнего ключа на библиотеку, поэтому история не меняется. Все операции проверяют тренера и у источника, и у целевой группы. Миграция ExerciseGroups добавляет таблицу групп и nullable GroupId: прежние упражнения остаются без группы.
Поиск фильтрует по всем словам без учёта порядка, регистра и «ё/е»; точные совпадения и совпадения с начала имени выше совпадений внутри имени. В списке показана группа, чтобы различать независимые упражнения с одинаковыми названиями.
Компонент ExercisePicker использует всплывающий список поверх страницы: ширина соответствует полю, направление раскрытия и высота зависят от доступного места окна. Нативный popover закрывается по Escape и клику снаружи. Двойной клик и Enter вызывают явный OnApply, одиночный клик меняет выбор; флаг применения предотвращает повторное добавление во время операции.