2026-10-03 17:23:14 +07:00
2026-07-12 18:05:52 +07:00
2026-10-03 14:52:40 +07:00
2026-10-03 17:12:38 +07:00
fix
2026-10-03 13:22:11 +07:00
2026-10-03 17:11:51 +07:00
2026-10-03 14:52:40 +07:00
2026-10-03 14:52:40 +07:00
2026-09-21 18:54:32 +07:00
2026-10-03 12:18:35 +07:00
2026-09-30 23:19:58 +07:00
2026-09-30 23:44:21 +07:00
2026-10-03 12:18:35 +07:00
fix
2026-10-03 12:38:11 +07:00
2026-10-01 13:03:14 +07:00
2026-09-10 11:43:56 +07:00
fix
2026-09-18 02:52:01 +07:00
fix
2026-10-03 17:23:14 +07:00
2026-06-28 17:24:14 +00:00
2026-06-28 17:24:14 +00:00
fix
2026-09-10 03:59:53 +07:00
2026-09-16 23:56:15 +07:00
2026-09-16 23:56:15 +07:00
2026-10-03 12:18:35 +07:00
2026-09-30 23:41:13 +07:00

evening_detective_server

Шалон для Go сервисов (имя репо должно быть snake case) Методы http должны быть с префиксом /api

Инициализация

make generate
go mod tidy
docker compose -f docker-compose-db.yml up -d
cp .env.example .env
mkdir migrations

Для генерации protobuf нужен protoc 36.0 и плагины тех же версий, что проверены в этом репозитории: protoc-gen-go v1.36.11, protoc-gen-go-grpc v1.6.2, protoc-gen-grpc-gateway v2.30.0 и protoc-gen-openapiv2 v2.30.0. Установить плагины можно командой make install-generate-tools; каталог $(go env GOPATH)/bin должен быть в PATH. После установки запуск make generate удаляет и пересоздаёт proto/ и обновляет cmd/evening_detective_server/main.swagger.json. Ручное редактирование сгенерированных файлов не требуется.

Запуск

make run

Создание локального пользователя с полными ролями:

make dev-user

По умолчанию: dev@example.com / dev123456. Значения можно изменить:

make dev-user DEV_USER_EMAIL=test@example.com DEV_USER_PASSWORD=test123456

Сборка

make build-builder
make build-linux

По умолчанию используется архитектура Docker-среды. Для другой архитектуры укажите, например, make build-linux GOARCH=arm64 или make build-linux GOARCH=amd64.

Миграции

goose -dir ./migrations create {migration name} sql
make migrate-up

Тестирование

make test

PDF с QR-кодами команд

Кнопка «QR команд» на странице управления игрой скачивает GET /api/games/{id}/teams/qr.pdf с авторизацией организатора, автора или администратора. Без команд кнопка скрыта; прямой запрос для пустой игры возвращает FailedPrecondition.

Модуль internal/modules/team_qr_pdf формирует A4 в книжной ориентации, 12 карточек (3 × 4) с названиями команд и пунктирными линиями для разрезания. Имя файла: YYYY-MM-DD_Название игры.pdf, дата берётся из games.start_at. Недопустимые символы имени файла заменяются на _.

Для рабочих ссылок задайте APP_BASE_URL — тот же публичный адрес приложения, который используется существующими QR-кодами фронтенда (VITE_API_URL). Пустой или некорректный адрес приводит к ошибке скачивания. PDF не сохраняется в хранилище и отдаётся с Cache-Control: no-store.

Шрифт DejaVu Sans Condensed встроен в бинарник вместе с лицензией; системные шрифты не нужны. Поддерживается кириллица, длинные названия переносятся и уменьшаются до размера 6 pt. Символы вне BMP (например, emoji) вызывают явную ошибку генерации из-за ограничения PDF-библиотеки.

Закрывашка сценария

Автор (или администратор) сохраняет закрывашку через PUT /api/scenarios/{id}/conclusion:

{"conclusion":{"text":"Разгадка дела","video":"ending_abc.mp4"}}

Видео сначала загружается через POST /api/files/upload (MP4/WebM, до 64 МБ); в video передаётся возвращённое имя файла. Текст и видео можно задавать отдельно. {"conclusion":null} удаляет закрывашку. Она возвращается в story.conclusion полного сценария, но не передаётся в историю игроков, включая MCP. Закрывашка переносится вместе с ZIP-архивом; действуют общие лимиты импорта архива (32 МБ на архив и отдельный файл).

Документация и юридические документы

  • docs/TERMS.md — Пользовательское соглашение (оферта); раздаётся сервером по постоянному URL /api/terms;
  • docs/PRIVACY.md — Политика конфиденциальности и обработки персональных данных (152-ФЗ); раздаётся сервером по постоянному URL /api/privacy;
  • docs/CONSENT.md — текст согласия на обработку ПДн для формы регистрации (URL /api/consent);
  • docs/COMPLIANCE.md — чек-лист приведения сервиса в соответствие с документами.

Юридические эндпоинты:

  • POST /api/auth/signup — регистрация; требует accept_terms: true и accept_privacy: true (факт акцепта фиксируется в таблице user_agreements);
  • DELETE /api/auth/delete-account — удаление учётной записи и персональных данных (авторизован JWT, подтверждение паролем в HTTP-заголовке X-Password).

Структура:

  • agent.cordis.yml — композиция агента (полный набор инструментов, как у пресета standard, + персона юриста и скилл-база);
  • preset.yml — отображаемое имя и описание;
  • skills/software-law/ — база знаний: SKILL.md (правила консультаций) и references/ (лицензии, авторские права, ПДн, договоры).

MCP-сервер (встроен в основной сервис)

MCP-сервер (Model Context Protocol) встроен в основной бинарь и доступен как HTTP-эндпоинт /api/mcp (streamable HTTP) на REST-gateway — чтобы LLM-клиент (Claude Desktop, IDE с MCP-поддержкой и т.п.) мог играть в «Вечерний детектив». Инструменты вызывают игровые сервисы напрямую, отдельный процесс не нужен.

Ходы через make_move сохраняются с actions.via_mcp = true; через REST/gRPC — с false. Отметку задаёт сервер, клиент её не передаёт. У старых ходов значение false: их источник не отслеживался. Это отметка канала MCP, а не гарантия того, кто фактически играет.

Инструменты:

Инструмент Назначение
connect Подключиться к игре по ссылке /team-story/{id}?password=...; возвращает id команды, пароль и текущую историю
get_team_story История команды: точки, двери, улики, вопросы дела + инфо об игре (по паролю команды, без авторизации)
make_move Ход команды в точку по её коду; возвращает обновлённую историю (по паролю команды, без авторизации)
submit_answers Ответы команды на вопросы дела (массив {question_code, answer}); возвращает обновлённую историю (по паролю команды, без авторизации)

Ссылку на игру (/team-story/{id}?password=...) выдаёт команде организатор личным каналом — например, вместе с паролем. Принимаются относительные и абсолютные ссылки (в т.ч. с любым хостом): HTTP-запросы по ним не выполняются, из ссылки берутся только id команды и пароль. Игроку авторизация не нужна: команда идентифицируется паролем из ссылки.

Запуск — обычный запуск основного сервиса:

make run

MCP-эндпоинт: http://localhost:8090/api/mcp. ВНИМАНИЕ: REST-gateway слушает :8090 (gRPC — :8080), а docker-compose.yml публикует наружу только 8080 — для доступа к /api/mcp извне нужна публикация порта 8090 в compose либо проксирование пути /api/mcp через reverse-proxy (nginx/caddy). Эндпоинт публичный (как и REST /api/teams/{id}/story); секрет — пароль команды.

Подключение к MCP-клиенту (пример для Claude Desktop / аналогов):

{
  "mcpServers": {
    "evening-detective": {
      "type": "http",
      "url": "http://localhost:8090/api/mcp"
    }
  }
}

Регистрация команды из Яндекс Форм

POST /api/games/{id}/registrations — отдельный HTTP webhook, без пользовательского JWT. Капитан не должен иметь учётную запись. Создаётся обычная команда с автоматически сгенерированным паролем; капитану отправляется письмо с данными регистрации, почта капитана и количество участников видны организатору в списке команд игры. Письма автоматически не отправляются.

Перед запуском примените миграции и задайте YANDEX_FORMS_TOKEN в окружении сервера (случайный секрет, например результат openssl rand -hex 32). Пустое значение отключает ручку. Секрет настраивается только в интеграции формы, не в её публичных полях.

В Яндекс Формах откройте Интеграции → Запрос заданным методом:

  • Метод: POST.
  • URL: https://<адрес-сервера>/api/games/42/registrations, где 42 — ID игры.
  • Заголовок Authorization: Bearer <YANDEX_FORMS_TOKEN>.
  • Заголовок Content-Type: application/json.
  • Тело запроса:
{
  "name": "Следствие ведут котики",
  "email": "captain@example.org",
  "participantsCount": 5
}

Подставьте вместо примеров переменные ответов на вопросы «Название команды», «Почта капитана», «Количество участников». Для строк используйте фильтр JSON, чтобы кавычки и переносы строк корректно экранировались. Число участников передаётся целым JSON-числом без кавычек: от 1 до 2147483647. Название — 1–255 символов после удаления пробелов по краям; email — одиночный адрес длиной до 254 байт.

Яндекс передаёт заголовок X-Delivery-Id: он обязателен и используется для защиты от повторной доставки. Один ID доставки в пределах игры возвращает ту же команду, не меняя её данные и пароль. Новая отправка формы с другим ID создаёт новую команду, даже если почта и название совпадают. Для ручного теста задайте этот заголовок сами.

Успех: 200 {"id":123}. Пароль команды в ответ webhook не передаётся. Регистрация новой команды доступна только для игры в статусе draft. Повтор уже принятой доставки успешен и после старта игры. Ошибки: 400 — поля/JSON/ID доставки; 401 — токен; 409 — игра отсутствует или регистрация закрыта; 503 — интеграция выключена; 500 — внутренняя ошибка. Лимит тела — 16 КиБ, время обработки — до 4 секунд.

Сервис должен быть доступен по HTTPS и IPv6: Яндекс Формы отправляют интеграционные запросы только с IPv6. При фильтрации входящего трафика разрешите сеть 2a02:6b8:c00::/40. Подробности и настройка переменных — в официальной инструкции Яндекс Форм.

Проверка SQL и повторов на отдельной тестовой PostgreSQL:

TEST_DATABASE_URL='postgres://...' go test ./internal/repos/teams_repo -run TestRegistrationPostgres -count=1

Тест создаёт и удаляет собственную схему; требуется право CREATE SCHEMA.

S
Description
Сервер для игры "Вечерний детектив"
Readme 2.8 GiB
Languages
Go 99.6%
PLpgSQL 0.2%
Makefile 0.2%