From 7fe5525a01eddfd2d0c7f63aad7fada2df6a9155 Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Sun, 11 Oct 2026 02:24:14 +0700 Subject: [PATCH] up --- docs/agent/TASKS.md | 13 +++++++++---- prod | 2 +- reproxy | 2 +- tests/venue/https.py | 5 +++-- venue/nginx.conf | 11 +++++++++++ venue/nginx.https.conf | 18 ++++++++++++++++++ 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/docs/agent/TASKS.md b/docs/agent/TASKS.md index 42a9bb9..ef2860c 100644 --- a/docs/agent/TASKS.md +++ b/docs/agent/TASKS.md @@ -27,13 +27,13 @@ ### ED-029 — Автономная площадка и панель организатора -- Статус: `IN_PROGRESS` — этап 3 принят пользователем без WAN; этап 4 реализован и проверен, следующий — экспорт с блокировкой игры. +- Статус: `IN_PROGRESS` — этап 3 принят пользователем без WAN; этапы 4–6 реализованы и проверены, следующий — интерфейс передачи пакета. - Репозитории: корневой, `evening_detective_server`, `evening_detective_frontend`. -- Цель: реализовать принятый пользователем план из 15 последовательных этапов: автономное окружение; локальные организаторы; прежние адреса через Cudy; пакет игры; экспорт с блокировкой; локальный импорт; интерфейс передачи; полный билетный и игровой flow; пакет результатов; возврат онлайн; интерфейс возврата; устройства; панель состояния; сбор данных Cudy; Wi-Fi и испытание площадки. +- Цель: реализовать принятый пользователем план из 15 последовательных этапов: автономное окружение; локальные организаторы; прежние адреса через Cudy; пакет игры; экспорт со статусом офлайн-режима; локальный импорт; интерфейс передачи; полный билетный и игровой flow; пакет результатов; возврат онлайн; интерфейс возврата; устройства; панель состояния; сбор данных Cudy; Wi-Fi и испытание площадки. - Критерии: проверяемый результат каждого этапа; цикл «онлайн → ноутбук → проведение → онлайн» через интерфейс с сохранением QR, билетов и связей; повторные импорты без дублей; мониторинг устройств и Wi-Fi; полный прогон без WAN, 100 клиентов и проверка покрытия. - Ограничения: только тестовые игры до завершения возврата; существующие правила игры и формат `offline` сохраняются; без несогласованных новых зависимостей и отклонений от стандартов; без персональных списков игроков, CPU/памяти Cudy и автоматического переключения серверов; настройки роутера менять только по явному запросу пользователя; секреты и пакеты вне Git; commit/push выполняет пользователь. - Проверка: Go-стандарт и `go test ./...`; PostgreSQL-интеграционные тесты для атомарности; frontend-стандарт, type-check, lint, lint:css и build; штатная генерация protobuf; ручные контрольные точки после этапов 3, 8, 11, 15. -- Прогресс: этапы 1–2 реализованы и проверены в локальном Docker-окружении. Добавлены отдельный Compose, подготовка образов заранее, постоянные тома, миграции перед стартом, healthchecks и прокси. Явный `APP_MODE=venue` отключает SMTP, фоновые письма и внешний webhook; почтовые операции аккаунта возвращают безопасную ошибку до изменения данных. Локальные аккаунты создаются штатным `dev-user.sql`, роли и правила автоматического подтверждения команд сохраняются. Этап 3 принят пользователем после проверки без WAN; формат и код пакета игры (этап 4) реализованы и проверены. Этапы 5–15 ещё не завершены. +- Прогресс: этапы 1–2 реализованы и проверены в локальном Docker-окружении. Добавлены отдельный Compose, подготовка образов заранее, постоянные тома, миграции перед стартом, healthchecks и прокси. Явный `APP_MODE=venue` отключает SMTP, фоновые письма и внешний webhook; почтовые операции аккаунта возвращают безопасную ошибку до изменения данных. Локальные аккаунты создаются штатным `dev-user.sql`, роли и правила автоматического подтверждения команд сохраняются. Этап 3 принят пользователем после проверки без WAN; формат и код пакета игры (этап 4) реализованы и проверены. Экспорт со статусом офлайн-режима (этап 5) и локальный импорт (этап 6) реализованы и проверены. Этапы 7–15 ещё не завершены. - Дополнение по запросу пользователя: структура `venue` приведена к `prod` (`backend/`, `frontend/dist/`, `migrations/`, `Dockerfile.backend`, `docker-compose.yml`, `deploy.sh`); имена основных Compose-сервисов совпадают. Общая генерация `make build` / `make prod-build` готовит артефакты обеих папок; frontend собирается отдельно с настройками своего окружения. Текущие файлы `prod` при этой работе не изменяются; сборка проверяется с production-выводом в `/tmp`. - Упрощение запуска: отдельные `init.sh` и `prepare.sh` удалены, всё собрано в `deploy.sh`. Единственная команда запуска — `make venue`: создаёт `.env` только при его отсутствии, при необходимости готовит зависимости/образы и автоматически создаёт организатора. Повторный готовый запуск не собирает и не скачивает образы. `make venue-update` явно пересобирает окружение с интернетом. Аккаунт организатора синхронизируется со значениями `.env` при каждом запуске. Первый запуск, повторный запуск без интернета и обновление проверяются `python3 tests/venue/deploy.py` с подставными командами; реальный готовый запуск проверен через Docker и API-smoke. - Настройки площадки: по запросу пользователя необязательный `venue/.env.example` удалён; настройки создаются автоматически через `make venue`. Убраны ссылка и проверка примера; `python3 tests/venue/deploy.py` прошёл. Существующая `.env` не изменялась. @@ -58,7 +58,12 @@ - Приёмка этапа 3: пользователь сообщил «все проверил работает» после контрольного списка LAN-портов, обновления DNS устройств и проверки Android/iPhone с отключёнными WAN и мобильными данными. Аппаратный блокер снят по подтверждению пользователя; агент самостоятельно эти проверки не повторял. - Результат этапа 4: добавлен `GamePackageService` для потоковой сборки и автономного чтения ZIP версии 1 из подготовленного снимка. Пакет сохраняет исходные ID, пароли QR, хеши/статусы билетов, email капитанов, сценарий с ответами, план экранов, карту команд/стола, плейлисты и все медиа. Внешние HTTP/HTTPS-медиа скачиваются с проверками адресов DNS/соединения/перенаправлений; локальные файлы доступны через адаптер существующего хранилища. Совпадающие файлы хранятся один раз. Архив и распакованный объём ограничены 1 ГиБ, файл — 64 МиБ, JSON — 8 МиБ, количество записей — 4096. Проверяются версия, связи, размеры, SHA-256, MIME, опасные пути, дубли и отсутствующие/посторонние файлы; временные каталоги и файлы имеют права 0700/0600 и очищаются на ошибке. БД, API, protobuf, зависимости и текущий архив сценария не изменены. Контракт и владение временными файлами описаны в `evening_detective_server/docs/GAME_PACKAGE.md`. - Проверки этапа 4: `gofmt`, `go test ./internal/services/game_package_service -count=1`, полный `go test ./...` и `git diff --check` прошли. Табличные тесты проверяют сохранение данных/секретов QR/билетов, оба назначения плейлистов, автономное чтение, внешние/локальные URL, дедупликацию, запрещённые адреса и redirects, повреждённые пакеты, масштабированные лимиты ZIP/распакованного объёма и отмену во время извлечения с сохранением родительского каталога. Потоковое чтение проверено на генерируемых медиа 1 и 16 МиБ с ограниченным размером чтения. Ручное ревью всего нового кода по Go-стандарту и независимое ревью проведены; замечания исправлены. Реальный пакет на 1 ГиБ, скачивание с внешнего production-хостинга и перенос игры между БД не проверялись; UI/экспорт/импорт реализуются на следующих этапах. Commit/push не выполнялись. -- Следующий шаг: этап 5 — согласованный снимок игры из БД, экспорт пакета и блокировка дальнейших изменений на исходном сервере. Экспортёр учитывает выбранный плейлист в `game_music_states` и наличие игрового прогресса. Затем этап 6 — атомарный локальный импорт, сохранение QR и обработка конфликтов ID/повторных пакетов. Общая задача не завершена и не переносится в архив. +- Результат этапа 5: сохранён согласованный SQL-снимок и одна активная попытка экспорта на игру. Снимок учитывает выбранную музыку в `game_music_states`, команды, билеты, сценарий, план экранов и карту. Пакет собирается в фоне, потоково хранится в закрытом RustFS; PostgreSQL-очередь, heartbeat 30 секунд, аренда 2 минуты, timeout 30 минут и защита от устаревшего worker сохранены. API и оба protobuf-клиента сгенерированы; права трёх ролей и запрет экспорта в `venue` проверены. Повторное скачивание отдаёт те же байты, обрыв не меняет состояние. +- Изменение решения (2026-10-11): по запросу пользователя жёсткая блокировка полностью удалена; функции/триггеры БД не используются. Все обычные операции игры, команды, билеты, прогресс, музыка, карта, экраны и почтовые задания продолжают штатную работу. В списке игр и на странице управления показываются подготовка офлайн-пакета (`preparing`) и «Игра в офлайн-режиме» (`ready`). Это информационная отметка, без изменения игрового `status` или `format`; при ошибке она исчезает. Онлайн-изменения после запуска не попадают в сохранённый пакет; локальный импорт реализован на этапе 6; возврат ещё не реализован. +- Проверка этапа 5: ранее прошли реальный тестовый RustFS-цикл с перезапуском, проверкой ZIP кодеком, приватностью объекта, потоковой выдачей/обрывом и повторным скачиванием одинаковых байтов. После упрощения прошёл полный `go test ./...` с PostgreSQL: снимок, редактирование в `preparing`/`ready`, конкурентный запуск, аренда, устаревшая публикация, ошибка/повторный запуск и завершение/очистка после удаления игры. Frontend `type-check`, `lint`, `lint:css` и Vite build в `/tmp` прошли; `gofmt` и diff проверены. Проверка интерфейса в браузере и повторный реальный RustFS-цикл в этом уточнении не выполнялись. API описан в backend `docs/GAME_PACKAGE.md`. Production-развёртывание, commit и push не выполняются. +- Результат этапа 6: добавлен потоковый `POST /api/games/import` (сырой ZIP, JWT, роли admin/developer/organizer, только venue) и соответствующий protobuf-контракт. Кодек проверяет пакет до записи; игра, команды, билеты, сценарий, плейлисты, треки, план экранов, карта и метаданные публикуются одной транзакцией после загрузки медиа в локальный RustFS. ID игры/команд/билетов, пароли QR, хеши и состояния билетов сохранены. Сценарий/плейлисты/треки получают отдельные локальные ID; сценарий принадлежит импортирующему пользователю, исходные пользовательские ссылки не связываются с локальными аккаунтами. Исходный снимок и соответствия ID сохраняются в закрытом реестре импорта. Занятые ID/хеши и другой пакет той же игры отклоняются; повтор того же ZIP ничего не меняет, в том числе после локального редактирования или удаления игры. Реестр, heartbeat 30 секунд, аренда 2 минуты и timeout 30 минут защищают публикацию; очистка ошибок повторяется после перезапуска и не удаляет медиа при неопределённом ответе commit. Для маршрута импорта venue-прокси настроены лимит 1 ГиБ и потоковая передача; production-конфигурация не менялась. +- Проверки этапа 6: штатные `make generate` и `npm run proto:gen`; полный обычный `go test ./...`; PostgreSQL-тесты импорта, экспорта и API; `go test -race` для imports repo/service и app; frontend type-check, lint, lint:css и Vite build в `/tmp`; `python3 tests/venue/deploy.py`, `python3 tests/venue/https.py --docker` и diff-проверки прошли. Тесты покрывают связи исходных ID, локальные копии, QR/билеты, пустые наборы данных, последовательности, повтор/конкурентность, конфликты до/после загрузки, изменённые байты под тем же package ID, rollback, отмену, аренду/timeout, ошибки очистки и неопределённый commit. Реальный HTTP/RustFS smoke прошёл в отдельной internal Docker-сети без WAN, с отдельными PostgreSQL/RustFS, чтением медиа, перезапуском хранилища и повторным импортом; тестовые контейнеры и данные удалены. Nginx проверен на обоих HTTPS-доменах, включая POST импорта. Ручное ревью изменений по Go/frontend-стандартам выполнено. Пакет на 1 ГиБ, реальные телефоны и обновление работающей площадки не проверялись; новая миграция `20261011120000_create_game_imports.sql` требуется при обновлении. Commit/push не выполнялись. +- Следующий шаг: этап 7 — интерфейс запуска экспорта, скачивания и загрузки пакета на площадку. Полный билетный и игровой flow — этап 8; возврат результатов — последующие этапы. До реализации возврата использовать только тестовые игры. Общая задача не завершена и не переносится в архив. ### ED-024 — Общая дизайн-система фронтенда diff --git a/prod b/prod index 3dcd84c..5ea5af1 160000 --- a/prod +++ b/prod @@ -1 +1 @@ -Subproject commit 3dcd84cd68b86ecb9ec819e5c407efb19411413a +Subproject commit 5ea5af1fcae875648d92600f6c7b5ee104008295 diff --git a/reproxy b/reproxy index 0fbb698..ac1e355 160000 --- a/reproxy +++ b/reproxy @@ -1 +1 @@ -Subproject commit 0fbb6988b0ab5b216254caa07f171362c45b931e +Subproject commit ac1e3550dbf807fad6d7e6395fa3ee5972e1c916 diff --git a/tests/venue/https.py b/tests/venue/https.py index f73c890..a7a617f 100644 --- a/tests/venue/https.py +++ b/tests/venue/https.py @@ -192,14 +192,14 @@ with tempfile.TemporaryDirectory(prefix="venue-https-") as directory: # Test-only CA is explicit; public HTTPS verification is never disabled. context = ssl.create_default_context(cafile=str(root / "ca.pem")) - def request(domain, path, tls=True): + def request(domain, path, tls=True, method="GET", body=None): port = int(run("docker", "port", proxy, "443/tcp" if tls else "80/tcp").split(":")[-1]) connection = http.client.HTTPConnection("127.0.0.1", port, timeout=10) if tls: connection.sock = context.wrap_socket(socket.create_connection(("127.0.0.1", port), timeout=10), server_hostname=domain) try: - connection.request("GET", path, headers={"Host": domain}) + connection.request(method, path, body=body, headers={"Host": domain}) response = connection.getresponse() return response.status, response.getheader("Location"), response.read().decode() finally: @@ -224,6 +224,7 @@ with tempfile.TemporaryDirectory(prefix="venue-https-") as directory: assert request(domain, "/api/test/ping?value=1", tls=False)[:2] == (308, "https://" + domain + "/api/test/ping?value=1") assert request(domain, "/api/test/ping?value=1") == (200, None, "api:/api/test/ping?value=1") assert request(domain, "/api/files/material.pdf") == (200, None, "api:/api/files/material.pdf") + assert request(domain, "/api/games/import", method="POST", body=b"test ZIP") == (200, None, "api:/api/games/import") assert request(ui, "/team-story/1?password=test") == (200, None, "spa:/team-story/1?password=test") assert request(api, "/docs/") == (200, None, "api:/docs/") run("docker", "restart", proxy) diff --git a/venue/nginx.conf b/venue/nginx.conf index 0a1febb..0af22d7 100644 --- a/venue/nginx.conf +++ b/venue/nginx.conf @@ -3,6 +3,17 @@ server { server_name _; client_max_body_size 257m; + location = /api/games/import { + client_max_body_size 1g; + client_body_timeout 1800s; + proxy_read_timeout 1800s; + proxy_send_timeout 1800s; + proxy_request_buffering off; + proxy_pass http://evening_detective_server:8090; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + location /api/ { proxy_pass http://evening_detective_server:8090; proxy_set_header Host $host; diff --git a/venue/nginx.https.conf b/venue/nginx.https.conf index 6416417..1f8b8ad 100644 --- a/venue/nginx.https.conf +++ b/venue/nginx.https.conf @@ -24,6 +24,15 @@ server { listen 443 ssl; server_name evening-detective.crabs-games.art; + location = /api/games/import { + client_max_body_size 1g; + client_body_timeout 1800s; + proxy_read_timeout 1800s; + proxy_send_timeout 1800s; + proxy_request_buffering off; + proxy_pass http://evening_detective_server:8090; + } + location /api/ { proxy_pass http://evening_detective_server:8090; } @@ -37,6 +46,15 @@ server { listen 443 ssl; server_name evening-detective-api.crabs-games.art; + location = /api/games/import { + client_max_body_size 1g; + client_body_timeout 1800s; + proxy_read_timeout 1800s; + proxy_send_timeout 1800s; + proxy_request_buffering off; + proxy_pass http://evening_detective_server:8090; + } + location / { proxy_pass http://evening_detective_server:8090; }