Imported from thevladbog/quokkaq (
apps/backend/AGENTS.md). Install upstream withnpx skills add thevladbog/quokkaq --skill backend. Copyright stays with the author.
QuokkaQ Go Backend — контекст для агента
Продукт
QuokkaQ — система управления очередями для нескольких подразделений: талоны, услуги, окна, смены, бронирование/предзапись, приглашения пользователей, киоск, табло, staff/supervisor, админка. Мультитенантность по units.
Стек
- Go 1.26.2, модуль
quokkaq-go-backend - HTTP: Chi v5, CORS, JWT (
golang-jwt/jwt) - БД: PostgreSQL + GORM
- Real-time: Gorilla WebSocket (
internal/ws/) — комнаты по подразделениям - Фоновые задачи: Asynq + Redis (
internal/jobs/) - Файлы: AWS SDK v2 → MinIO/S3
- Почта: gomail v2, шаблоны в сервисах
- API docs: OpenAPI 3 (Scalar
/swagger/, файлы вdocs/)
Архитектура
handlers → services → repository → models (GORM)
↘ ws hub, Asynq workers
- Точка входа:
cmd/api/main.go - Миграции БД: версионированные шаги в
pkg/database/postgres.goчерезRunMigration("v…", …). Не менять уже существующие миграции (тело уже применённых версий в БД не перезапускается) — только добавлять новые версии с новым ключомvX.Y.Z_…и нужной логикой/DDL. - Публичное демо: данные —
internal/demoseed, CLI —cmd/seed-demo; порядок на чистой БД: миграции →cmd/seed-plans(пакетinternal/subscriptionplanseed) →seed-demo. После правок миграций/моделей, от которых зависит сид, из корня монорепо:export DATABASE_URL=postgresql://…(пустая PostgreSQL 16+) →pnpm nx run backend:test-demoseed-smoke. Стек и деплой:../../deploy/demo/README.md,docs/DEMO_DEPLOYMENT.md. - Конфиг:
internal/config/, примеры env —.env.example - Типичные переменные:
DATABASE_URL,PORT(по умолчанию 3001),APP_BASE_URL(URL фронта), AWS/MinIO, SMTP, Redis,JWT_SECRET,CORS_ALLOWED_ORIGINS(через запятую),RUN_AUTO_MIGRATE(false— отключить AutoMigrate при старте).
Доменные области (по internal/services/)
auth, users, units, tickets, services, counters, shifts, slots, bookings, pre-registrations, invitations, templates, mail, storage, TTS, job enqueue.
Талон: documentsData и поля с киоска (PII)
- Разрешение и валидация снимков при создании талона:
internal/services/ticket_documents_data.go; проверка полей услуги/киоска —internal/services/service_kiosk_config.go. Cron очистки истёкших:ClearExpiredTicketDocuments, планировщик вcmd/api/main.go. - Урезание ответов HTTP по
tickets.user_data.readи публичномуX-Visitor-Token— вinternal/handlers/ticket_handler.go(напримерapplyTicketUserDataForHTTP, staff-редакции). Подробнее: runbook «Ticket documentsData».
Digital Signage (табло, внешние фиды, плейлисты)
- Модели/таблицы:
internal/models/signage.go— плейлисты, расписания,ExternalFeed(в т.ч.last_error,last_fetch_at,consecutive_failuresпосле миграции), объявления на экран. - Сервис/поллинг:
internal/services/signage_service.go—PollDueFeeds/PollFeedByID. Типweatherиспользует Open-Meteo (параметрыlat/lonвconfigJSON, см.pollWeather);rss— парсерgofeed,custom_url— JSON по HTTP. Для сетевых вызовов встроены повторные попытки (см.httpGetJSON/pollCustomURL). - Периодика Asynq:
internal/jobs/feed_poller.go, постановкаEnqueueSignageFeedPollизcmd/api/main.go(интервальные enqueue в общем цикле, как у других periodic jobs). - Публичные пути (без сессии, для экрана): объявления и данные фидов — теги и маршруты в
internal/handlers/signage_handler.go(имена путей и префиксыpublic-/public-screen-в OpenAPI). - Очередь
servedToday: в обход HTTP и WebSocketUnitETASnapshotзаполняется вinternal/services/eta_service.goтой же логикой дня, чтоGetUnitQueueSummary(функцияservedTodayForUnit+ timezone юнита).
Позиция продукта и границы (Digital Signage)
- QuokkaQ в части табло: один публичный экран = один
unitс очередью, не сеть DSP на тысячи дисплеев. Расширения vNext (календарные границы слотов, сроки слайдов,GET .../signage-health, режимы объявленийbanner/fullscreen) согласованы с таймзоной юнита (ActivePlaylistи валидация дат). - Вне near-term roadmap (только по запросу B2B): группы экранов, теги/smart-плейлисты, proof of play, shuffle, тяжёлый offline (PWA/Tauri) — фиксировать в коммерции, не планировать как обязательный baseline.
Статистика: аномалии и staffing
- Asynq: периодическая задача
anomaly:checkставится изcmd/api/main.go, тип и постановка —internal/jobs/types.go,internal/jobs/client.go, обработчик —internal/jobs/worker.go(handleAnomalyCheck). Нужен Redis (REDIS_URLи т.п.), иначе очередь недоступна. - БД: сохранённые сигналы — таблица
anomaly_alerts(миграция вpkg/database/postgres.go), репозиторийinternal/repository/anomaly_alert_repository.go. - API для UI:
GET /units/{unitId}/statistics/anomaly-alerts—internal/handlers/statistics_handler.go; логика детекции/уведомлений —internal/services/prediction_service.go.
Локальная разработка
- Из корня монорепо:
pnpm nx run backend:serve—go run ./cmd/apiчерезscripts/run-backend-dev.js(освобождение порта, корректный код выхода для Nx при Ctrl+C). Hot reload нет: после правок.goперезапустите процесс. Без Nx:node scripts/run-backend-dev.jsилиgo run ./cmd/apiизapps/backend. docker-compose.yml: postgres, redis, minio, backend — API :3001- После старта: Scalar
http://localhost:3001/swagger/, спека OpenAPI 3:http://localhost:3001/docs/openapi.json(и исторический путь/docs/swagger.json). - Новые эндпоинты: model → repository → service → handler → регистрация в
main.go→ аннотации swag (Swagger 2) → пайплайн доков изapps/backend:swag init -g cmd/api/main.go -o ./docs→go run ./cmd/swagger-to-openapi3(конвертация в OpenAPI 3 через kin-openapi + структурные патчи). Или через Nx из корня:pnpm nx run backend:openapi. - Pull request: корневой CI —
pnpm nx run backend:openapi:check+git diffпоdocs/*при затронутом backend; отдельный workflow вapps/backend/.github/— тот же порядок; Gosec —.github/workflows/gosec.ymlи.gosec.json. - Монорепо quokkaq: после обновления
docs/openapi.json, если эндпоинт попадает под Orval на фронте, из корня выполнитьpnpm nx run frontend:orvalи закоммитить изменения вapps/frontend/lib/api/generated/(см.apps/frontend/orval.config.ts). Для читаемых имён в клиенте можно задать@IDв swag-комментариях к handler.
Tenant integration API и публичный виджет
- Интеграционный REST — префикс
/integrations/v1(ключи, scope, пути — канон в OpenAPI/Scalar на вашем инстансе, напримерGET /docs/openapi.json). INTEGRATION_API_RL_REDIS: приtrueи доступном Redis (REDIS_URLилиREDIS_HOST/REDIS_PORT) для/integrations/v1используется sliding-window лимит в Redis; иначе — in-memory token bucket.GET /companies/meвключаетplanCapabilities: флаги тарифа для UI (Developer API, webhooks, публичный виджет и др.) — имена полей и типы только из OpenAPI.- Публичный виджет: JWT подписывается секретом
PUBLIC_WIDGET_JWT_SECRET; allowlist origin —company.settings.publicQueueWidgetAllowedOrigins. Краткая операторская документация: EN, RU.
Фронтенд (соседний репозиторий)
../quokkaq-frontend— Next.js; ожидает REST наNEXT_PUBLIC_API_URLи WebSocket наNEXT_PUBLIC_WS_URL.
Деплой
- Ветка
prod-release, образ в Yandex Container Registry, VM — см.README.md,docs/DEPLOYMENT.md.
Документация
- Подробно:
README.md(EN),README.ru.md(RU).
Авторизация и RBAC
- Каталог прав — константы в
internal/rbac/permissions.go(dot-notation:tickets.read,access.staff_panel,support.reports, …). Новые ключи добавлять туда и в OpenAPI/клиент при необходимости. - HTTP middleware (
internal/middleware/rbac_middleware.go):RequirePlatformAdmin— только SaaS-оператор (platform_admin); в не-production приPLATFORM_ALLOW_TENANT_ADMINможет допускать глобальныйadmin(см.authorization.go).RequireTenantAdmin—platform_admin, глобальныйadmin, tenantsystem_admin, или каталогtenant.adminна юните.RequireTenantPermission(perm)— то же +TenantPermissionAllowed: каталогpermчерез tenant roles или то же право наuser_unitsв компании (internal/repository/tenant_permission_allowed.go).RequireUnitPermission— право на конкретномunitIdиз URL (JWT user или terminal).
- Tenant roles —
tenant_roles,tenant_role_units,user_tenant_roles; слияние прав вuser_units—tenantroleseed.RebuildUserUnitsFromTenantRoles/ синхронизация из хендлеров. - Глобальный
admin—userRepo.IsAdmin(только имя ролиadmin); legacy, но всё ещё используется в части хендлеров и middleware. Для полного контроля внутри тенанта предпочтительны tenant-рольsystem_adminи каталог прав. - Миграции БД — только новые версии:
RunMigration("v1.x.y_snake_case", …)вpkg/database/postgres.go; тела уже применённых миграций не менять. - Inline-проверки (survey, shift journal, statistics scope) опираются на глобальные имена ролей и/или канонические права на
user_units; tenantsystem_adminобычно покрывается слитыми правами на все юниты после TRU, а не отдельной проверкой slug в репозитории.
Зависимости и алерты
- pgx / CVE-2026-33815 (GHSA-xgrm-4fwx-7qm8): в
go.modстоитgithub.com/jackc/pgx/v5v5.9.1; по OSV исправление с v5.9.0. Локально:go run golang.org/x/vuln/cmd/govulncheck@latest ./...вapps/backend— без находок. Если GitHub Dependency review всё ещё ругается, в.github/workflows/dependency-review.ymlдля этого GHSA заданallow-ghsas(см. комментарий в workflow); при обновлении данных GitHub правило можно убрать. - Debricked (OpenText Core SCA): опциональный CI —
.github/workflows/debricked.yml; нужен секрет репозиторияDEBRICKED_TOKEN. Скан с корня монорепо (debricked scan .) подхватываетpnpm-lock.yaml,apps/backend/go.modи др. Ложные срабатывания после апгрейда зависимости настраиваются в UI Debricked (automation rules / ignore / waiver), а не только в коде.