generated from VLADIMIR/template
up
This commit is contained in:
+99
-4
@@ -105,7 +105,7 @@ defer os.RemoveAll(bundle.Dir)
|
||||
`Snapshot.SourceServer` содержит исходный адрес онлайн-сервера.
|
||||
`Snapshot.HasProgress` заполняется экспортёром по наличию действий, полученных
|
||||
улик и ответов. Кодек не читает БД, поэтому достоверность снимка и этого признака
|
||||
обеспечивает этап 5. `Data` не имеет полей для игрового прогресса.
|
||||
обеспечивает экспортёр этапа 5. `Data` не имеет полей для игрового прогресса.
|
||||
|
||||
`Pack` сначала собирает ZIP в приватном временном каталоге, затем копирует его
|
||||
в `destination`. Ошибка получения файлов не выдаёт частичный пакет. Ошибка
|
||||
@@ -141,13 +141,108 @@ MIME и SHA-256, некорректные связи данных и состо
|
||||
Ошибки проверяются через `errors.Is`: `ErrInvalidPackage`,
|
||||
`ErrUnsupportedVersion`, `ErrLimitExceeded`, `ErrMediaUnavailable`,
|
||||
`ErrUnsafeURL`; отмена сохраняет `context.Canceled`/`context.DeadlineExceeded`.
|
||||
Транспортных handlers на этапе 4 нет; внутренние причины ошибок нельзя напрямую
|
||||
выдавать в будущем API.
|
||||
Кодек не содержит транспортных handlers; экспортёр переводит ошибки в безопасные
|
||||
сообщения API.
|
||||
|
||||
SHA-256 защищает от повреждения, но не подтверждает автора пакета. Подпись и
|
||||
шифрование не реализованы. Кодек не блокирует игру и не делает импорт; повторные
|
||||
импорты, сохранность QR на площадке и полный перенос проверяются на этапах 5–6.
|
||||
импорты, сохранность QR на площадке и полный перенос проверяются на этапе 6.
|
||||
|
||||
Проверка: `go test ./internal/services/game_package_service -count=1` и полный
|
||||
`go test ./...`. Тесты покрывают сохранение данных, автономное чтение, загрузку
|
||||
медиа, дедупликацию, потоковую обработку, отмену, очистку, лимиты и повреждённые пакеты.
|
||||
|
||||
## Экспорт со статусом офлайн-режима: этап 5
|
||||
|
||||
Только для тестовых игр до реализации возврата результатов. Импорт, интерфейс
|
||||
передачи, отмена и возврат результатов реализуются следующими этапами.
|
||||
|
||||
### API
|
||||
|
||||
Все три маршрута требуют `Authorization: Bearer <JWT>` и одну из ролей `admin`,
|
||||
`developer`, `organizer`. `author` и `user` не имеют доступа. В `APP_MODE=venue`
|
||||
экспорт запрещён, worker не запускается и закрытый bucket не инициализируется.
|
||||
|
||||
| Метод и маршрут | Результат |
|
||||
| --- | --- |
|
||||
| `POST /api/games/{ID}/export` | Запуск фоновой подготовки; повторный запрос в `preparing`/`ready` возвращает ту же попытку. Тело: `{}`. |
|
||||
| `GET /api/games/{ID}/export` | Последняя попытка: состояние, ID, готовый пакет либо безопасная причина ошибки. |
|
||||
| `GET /api/games/{ID}/export/download` | Потоковый ZIP готовой попытки; `Content-Length`, `Content-Disposition`, `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`. |
|
||||
|
||||
Первые два маршрута возвращают `{"export":{"ID":"...","state":"preparing"}}`.
|
||||
В `ready` добавляются `packageID` из фактического манифеста, `size` (int64 в
|
||||
protobuf JSON представлен строкой), `sha256`. В `failed` — безопасный `failure`.
|
||||
Снимок, инициатор, владелец worker и ключ RustFS наружу не передаются.
|
||||
`Game.export` в обычных ответах игры содержит те же сведения для будущего UI.
|
||||
|
||||
Ошибки доступа: HTTP 401/403; отсутствие попытки — 404; начатая игра, прогресс,
|
||||
режим `venue` и ещё не готовый ZIP — HTTP 400
|
||||
(`FailedPrecondition`). Неизвестные ошибки дают безопасный HTTP 500.
|
||||
Скачивание реализовано отдельным HTTP-handler с штатной JWT-проверкой; ZIP не
|
||||
проходит через gRPC `HttpBody` или публичный `/api/files`.
|
||||
|
||||
### Снимок и статус офлайн-режима
|
||||
|
||||
Начало экспорта блокирует строку игры в короткой транзакции. Проверяются `draft`,
|
||||
отсутствие `started_at`, `ended_at`, действий, улик и ответов команд. Запланированное
|
||||
`start_at` переносится. Одним SQL-снимком сохраняются игра, общий сценарий, все
|
||||
команды и билеты, карта, план экранов, выбранный плейлист игры и default сценария.
|
||||
`game_music_states.playlist_id` имеет приоритет над `games.playlist_id`.
|
||||
|
||||
Частичный уникальный индекс допускает одну попытку `preparing`/`ready` на игру.
|
||||
Экспорт не запрещает изменения игры и связанных данных: команды, регистрация,
|
||||
билеты, прогресс, оценки, музыка, экраны и карта продолжают работать штатно.
|
||||
Почтовые workers продолжают обработку. Функций и триггеров БД нет.
|
||||
|
||||
В списке игр и на странице управления `preparing` отображается как подготовка
|
||||
офлайн-пакета, `ready` — как «Игра в офлайн-режиме». Это информационный статус,
|
||||
а не подтверждение импорта пакета на площадке. Поля `status` и `format` игры
|
||||
не меняются. При `failed` отметка офлайн-режима исчезает.
|
||||
|
||||
ZIP собирается из сохранённого снимка. Изменения после начала экспорта не
|
||||
попадают в этот пакет, повторное скачивание отдаёт прежнюю версию. Общие сценарии
|
||||
и плейлисты остаются редактируемыми; недоступное или удалённое медиа приводит
|
||||
к ошибке сборки. Отдельный интерфейс запуска/скачивания остаётся этапом 7.
|
||||
|
||||
### Worker и хранение
|
||||
|
||||
Очередь и снимки находятся в PostgreSQL (`game_exports`), отдельного брокера нет.
|
||||
Worker проверяет задания каждые 5 секунд. После захвата попытки аренда длится
|
||||
2 минуты, heartbeat обновляет её каждые 30 секунд. Общая подготовка, включая
|
||||
ожидание в очереди, ограничена 30 минутами с момента запуска. Потеря владения
|
||||
отменяет подготовку; `ready` публикуется только после полной загрузки ZIP и
|
||||
повторной проверки ID попытки, владельца, аренды и срока подготовки в БД.
|
||||
|
||||
ZIP собирается во временном каталоге `0700` с файлами `0600` и потоково
|
||||
загружается в отдельный закрытый bucket `<S3_BUCKET>-game-exports` в текущем
|
||||
RustFS с текущими учётными данными. Новые переменные окружения и изменения
|
||||
production-конфигурации не нужны. Временные файлы удаляются после работы;
|
||||
maintenance очищает оставшиеся каталоги терминальных попыток и неполные объекты
|
||||
после перезапуска. Данные для очистки сохраняются даже после удаления игры,
|
||||
независимо от её текущего наличия.
|
||||
|
||||
Ошибка сборки или истёкшая аренда переводит попытку в `failed`. Повторный POST создаёт новую попытку и новый снимок. Worker старой
|
||||
попытки не может опубликовать результат или изменить новую попытку. Неоднозначный
|
||||
ответ БД после публикации не приводит к удалению готового ZIP: удаление допустимо
|
||||
только после подтверждённого `failed`. Очистка повторяется до окончания возможной
|
||||
работы старого worker.
|
||||
|
||||
Готовый пакет хранится до реализации возврата результатов. Повторное скачивание
|
||||
отдаёт те же байты; закрытие соединения не меняет `ready`.
|
||||
|
||||
### Проверка
|
||||
|
||||
`TEST_DATABASE_URL` включает PostgreSQL-проверки в изолированных схемах через
|
||||
реальные миграции и публичные методы репозиториев/сервисов. `go test ./...`
|
||||
проверяет снимок, конкурентный запуск, возможность изменений после экспорта, истечение
|
||||
аренды/timeout, запрет устаревшей публикации, ошибку/повторный запуск, очистку,
|
||||
права и режим `venue`.
|
||||
|
||||
Для отдельного тестового RustFS дополнительно задаются `TEST_S3_ENDPOINT`,
|
||||
`TEST_S3_USER`, `TEST_S3_PASSWORD` и запускается
|
||||
`go test ./internal/services/game_export_service -run TestExportRustFS -count=1 -v`.
|
||||
Тест создаёт отдельный bucket, проверяет кодек, SHA-256, анонимный отказ, потоковую
|
||||
выдачу/обрыв и повторное создание клиента, затем удаляет свои объекты и bucket.
|
||||
Необязательный `TEST_S3_RESTART_MARKER` позволяет внешнему тестовому harness
|
||||
перезапустить изолированный RustFS: после появления marker нужно дождаться health
|
||||
и создать `<marker>.continue` в течение трёх минут. Это не production-проверка.
|
||||
|
||||
Reference in New Issue
Block a user