This commit is contained in:
DmitriyFadin
2026-09-30 15:05:05 +03:00
commit ca18fc196d
122 changed files with 8794 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
# Архитектура и границы первой версии
## Стек
.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, одиночный клик меняет выбор; флаг применения предотвращает повторное добавление во время операции.
+112
View File
@@ -0,0 +1,112 @@
# Проверка первой версии — 24 сентября 2026
## Выполнено
- Решение .NET 10 собирается без ошибок и предупреждений.
- Пять тестов Application проходят: независимое копирование подходов, сохранение нагрузок при объединении/разделении и перемещении, запрет потери подходов при разном их количестве, добавление круга, проверка отрицательного веса и схемы видеоссылки.
- Docker-образ собирается; публикация отдельно проверяет наличие скрипта Blazor.
- Два контейнера запускаются в общей сети; PostgreSQL проходит healthcheck, миграция применяется, первый тренер создаётся из окружения.
- В Chrome проверены вход, создание клиента и программы, ручное добавление упражнения, разные веса 40/45/50 и 12/14/16 по подходам, сохранение и повторное открытие.
- Проверены объединение/разделение сета, изменение порядка, перетаскивание, библиотека и отмена удаления блока.
- Проверена независимая копия программы: изменение её веса не затрагивает оригинал.
- Проверены две вкладки: устаревшая версия не перезаписывает более новую.
- Неавторизованный запрос PDF перенаправляется на вход.
- PDF скачан из приложения и отрендерен: кириллица, значки камеры, веса и шесть кликабельных ссылок присутствуют.
- Отдельная длинная тренировка экспортирована на шесть страниц: все 18 упражнений и 54 ссылки сохранены; переносы страниц просмотрены.
## Границы проверки
Проверка проведена локально в Docker на macOS и в настольном Chrome. Публичный TLS-прокси, нагрузка нескольких тренеров и мобильный интерфейс не проверялись. Данные проверки явно помечены как демонстрационные. Результаты и скрипты ручной браузерной проверки находятся в игнорируемом `artifacts/qa`; это не данные реальных клиентов.
## Общие параметры упражнений — 25 сентября 2026
- Убраны индивидуальные подходы и сеты. Добавлены общие повторения, текстовый вес, разминка, примечание, ручной процент, отдых в минутах и ручное время тренировки.
- 8 тестов: независимое копирование полей, валидация, порядок блоков, общий отдых с паузой после последнего подхода.
- Перед миграцией создана локальная резервная копия в игнорируемом каталоге backups. Миграция проверена на отдельной копии PostgreSQL: 19 упражнений из 15 блоков перенесены в одиночные блоки; проверены порядок, названия, ссылки, камера, число подходов, первые вес/повторения и перевод секунд в минуты.
- Браузерные проверки выполнялись на отдельном временном экземпляре приложения и копии БД: ввод новых полей, сохранение и перезагрузка, независимая копия блока, текстовый вес, дробный отдых, итоги 6 и 10,5 минуты, отсутствие управления сетами, экспорт HTTP 200.
- PDF проверен по тексту и структуре без рендеринга страниц: альбомная ориентация, новые колонки, кликабельные аннотации, встроенные камеры, итог отдыха и ручное время. Длинный PDF: все 18 упражнений на 3 страницах, повтор заголовков, текст в границах страниц.
- По просьбе пользователя скриншоты и визуальная проверка PDF не выполнялись.
## Единая PDF-таблица недели — 25 сентября 2026
- Каждая неделя экспортируется одной таблицей с общими заголовками и последовательными тренировками; после каждой тренировки свой итог.
- Проверена структура документа: одна таблица на неделю, только две общие строки заголовка повторяются при переносе.
- Тестовый PDF: две короткие тренировки на одной странице без повторения заголовков между ними; длинная тренировка на следующих страницах, пустая тренировка, отдельные итоги и кликабельные ссылки. Проверены альбомная ориентация, повтор общих заголовков и границы текста.
- Проверки без скриншотов и рендеринга страниц.
## Группы библиотеки и поиск — 25 сентября 2026
- 11 тестов проходят, включая точные/начальные совпадения, поиск по нескольким словам, регистр, «ё/е» и отсутствие результатов.
- Миграция ExerciseGroups соответствует модели EF Core. На отдельной копии PostgreSQL проверен переход старых упражнений к nullable GroupId без удаления исходных записей.
- На отдельном тестовом экземпляре проверены создание и переименование групп, независимое копирование упражнения между группами, drag and drop в группу и «Без группы», сохранение после перезагрузки.
- Проверены отмена удаления группы, удаление группы с упражнениями, сохранение независимой копии в другой группе и индивидуальная корзина.
- В конфигураторе проверены фильтр группы, поиск, пустой результат, блокировка добавления без выбранного упражнения; добавленное в программу упражнение остаётся после удаления библиотечной группы.
- Заодно подтверждены значения по умолчанию: время новой тренировки «1 час», процент нового упражнения 80.
- Все UI-проверки выполнены без скриншотов. Тестовые изменения делались только в отдельной базе.
## Выпадающий поиск — 26 сентября 2026
- Постоянный список заменён popover со строкой поиска; ширина соответствует полю, высота и направление раскрытия зависят от свободного места.
- На отдельной копии базы проверены: скрытое начальное состояние, фильтрация, двойной клик без повторного добавления, одиночный выбор с кнопкой «Добавить», стрелки и Enter, Escape, закрытие по клику снаружи, отсутствие результатов и сохранение после перезагрузки.
- Проверено размещение внутри уменьшенного окна и двойной клик при создании независимой копии упражнения в группе.
- Сборка прошла. Скриншоты не выполнялись.
## Удаление клиента — 29 сентября 2026
- В карточку клиента добавлена корзина с подтверждением удаления клиента и всех его программ.
- Сборка без предупреждений и ошибок, 11 тестов прошли; приложение обновлено в Docker.
- В браузере на специально созданном тестовом клиенте проверены отмена удаления с сохранением программы, подтверждение, возврат к списку и недоступность удалённых клиента и программы по прямым ссылкам. Тестовый клиент удалён. Скриншоты не выполнялись.
## Данные клиента в PDF — 29 сентября 2026
- Миграция ClientProfile добавляет Goal, TrainingSchedule и HealthStatus с пустыми значениями; существующий столбец Notes сохраняется без изменений.
- 11 тестов прошли; EF Core не обнаружил расхождений модели и миграций. Сборка Docker и применение миграции успешны.
- На специально созданном клиенте проверены порядок полей, сохранение и загрузка после обновления страницы, экспорт PDF. Тестовые клиент и программа удалены после проверки.
- Из PDF извлечены все новые значения; таблица содержит четыре строки и два столбца, находится перед неделями и встречается один раз. Проверены альбомные страницы и отсутствие приватной тестовой заметки. Скриншоты и визуальный рендер не выполнялись.
## Удаление программы — 29 сентября 2026
- В истории программ клиента добавлена корзина с подтверждением по названию программы. Удаление ограничено текущим тренером; дочерние недели и тренировки удаляются каскадно.
- Сборка без предупреждений и ошибок, 11 тестов прошли; Docker-приложение обновлено.
- В браузере без скриншотов на собственных тестовых данных проверены отмена, подтверждение, обновление счётчика, закрытие формы копирования удалённой программы, сохранение результата после перезагрузки и недоступность удалённой программы по ссылке. Клиент и другая программа сохранились. Тестовые данные удалены после проверки.
## Импорт упражнений из таблицы — 29 сентября 2026
- Сборка без предупреждений и ошибок, 16 тестов прошли. Тесты разбора буфера покрывают кавычки Excel, переносы внутри ячеек, пустые ячейки, CR/LF/CRLF, большие вставки и лимиты; проверена валидация ссылок и номера ошибочной строки.
- В Docker-приложении без скриншотов проверена вставка 250 строк одним событием буфера обмена, автоматическое расширение таблицы, сохранение после перезагрузки, импорт в новую/существующую группу и без группы.
- Ошибочная ссылка оставляет данные в модальном окне и не создаёт ни группу, ни упражнения. Проверены лишний столбец, обязательность названия новой группы, ручное исправление ячеек, вставка только в столбец ссылки, добавление/удаление строк, отмена и Escape.
- Сохранённая ссылка проверена в библиотеке. Собственные тестовые записи удалены после проверки. Настоящее приложение Excel не автоматизировалось: использовалось событие paste с табличным текстом формата Excel.
## Очистка поиска после добавления — 29 сентября 2026
- После успешного добавления сбрасываются текст поиска и активный пункт, список закрывается; фильтр группы сохраняется. Сброс явно запрашивается родителем, поэтому обычный ввод и перерисовка не стирают следующий запрос.
- Сборка успешна; приложение обновлено в Docker. В браузере без скриншотов проверены добавление кнопкой, двойным кликом и Enter, очистка поиска и выбора, закрытие списка и сохранение следующего введённого запроса. Собственные тестовые данные удалены.
## Плашка отмены удаления — 29 сентября 2026
- Плашка видна только в тренировке, где удалён блок, и исчезает через Planner:UndoDeleteDurationSeconds (по умолчанию 10 секунд). Повторное удаление заменяет предыдущую отмену и перезапускает таймер.
- Таймер отменяется при восстановлении блока, удалении исходной тренировки/недели, смене программы и закрытии редактора.
- Сборка успешна, 16 тестов прошли. В браузере без скриншотов на собственных тестовых данных проверены переключение тренировок, восстановление блока, автоматическое исчезновение и отсутствие повторного появления. Тестовые данные удалены; Docker-приложение обновлено.
## PDF без названия программы — 29 сентября 2026
- Название программы удалено из содержимого и метаданных PDF.
- Сборка успешна, Docker-приложение обновлено. В тестовом экспорте проверено отсутствие названия на всех четырёх страницах и в метаданных, наличие данных клиента. Собственные тестовые записи удалены. Скриншоты не выполнялись.
## Отступ после данных клиента в PDF — 29 сентября 2026
- Добавлены пять пустых строк только между таблицей данных клиента и первой неделей. Настройка: Pdf:ClientDetailsGapLines, переопределение через .env.
- Сборка и Docker-обновление успешны. Координаты текста тестового PDF подтверждают смещение первой недели вниз при неизменных следующих неделях и количестве страниц. Скриншоты не выполнялись; собственные тестовые записи удалены.
## Ширина таблицы и ресурс камеры — 29 сентября 2026
- Ширина таблицы PDF увеличена с 25,5 до 27,5 см настройками столбцов. Приложенный PNG без изменений включён как EmbeddedResource в Infrastructure; размер камеры 18 pt настраивается через Pdf:CameraSizePoints.
- Иконка выровнена по центру абзаца и вертикально по центру ячейки. Сборка и Docker-обновление успешны.
- Из PDF, экспортированного Docker-приложением, извлечена иконка: пиксели совпадают с исходным PNG. По координатам проверены ширина таблицы, границы страницы и центрирование (горизонтальное отклонение 0 pt, вертикальное менее 1 pt).
- Скриншоты не выполнялись. Собственные тестовые данные удалены после экспорта.
## Полные ссылки и центрирование в PDF — 29 сентября 2026
- Вместо «Открыть видео» выводится полный URL с невидимыми возможностями переноса; адрес гиперссылки остаётся исходным.
- Все столбцы после «Примечания», включая заголовки и итоговые значения, выровнены по горизонтальному и вертикальному центру.
- Сборка и Docker-обновление успешны. В тестовом PDF проверены полный длинный URL внутри ячейки, неизменный адрес перехода в аннотациях PDF и центрирование числовых ячеек по координатам (отклонение менее 1 pt). Скриншоты не выполнялись; собственные тестовые данные удалены.