- Go 52.2%
- TypeScript 28.3%
- HTML 15%
- SCSS 4.3%
- Dockerfile 0.1%
| backend | ||
| docs | ||
| frontend | ||
| .gitignore | ||
| .woodpecker.yml | ||
| angie.conf.example | ||
| Dockerfile | ||
| Dockerfile.ci | ||
| LICENSE | ||
| README.md | ||
| test.js | ||
Система управления программой конференции
Мгновенная синхронизация, загрузка презентаций, управление докладчиками и порядком выступлений — всё в одном веб-приложении.
Особенности
-
Ролевая модель — глобальные роли (global_admin, security_admin, viewer) + локальные роли на площадку (venue_admin, operator, secretary, moderator). Полная матрица прав ниже.
-
JWT + Argon2id — безопасная аутентификация. Пароли не хранятся в открытом виде. Refresh token rotation, отзыв сессий.
-
Секции/залы — иерархическое дерево секций с поддержкой перетаскивания (CDK drag-and-drop), вложенности, сворачивания.
-
Статус
speaking— трёхстатусный цикл (pending → speaking → done). Фиксация времени начала и окончания выступления. Автоматический расчёт таймлайна. -
Файлы презентаций — загрузка через веб-интерфейс, автоматическое сохранение на сервере. Поддержка чанковой загрузки через chunker-upload с fallback на прямую загрузку. Автоматическая конвертация в PDF и ZIP-архив через conference-convert (опционально). Чипсы форматов прямо в таблице.
-
Массовый импорт — JSON или XLSX/ODS/CSV, авто-маппинг колонок, проверка дублей, авто-создание залов и секций.
-
Протокол conference-display — WebSocket-команды для внешнего сервиса отображения презентаций на экранах в зале. Предзагрузка следующего доклада (
queue_next) для нулевого лага. -
Drag-and-drop — интуитивное перетаскивание докладов и секций (CDK). Ограничено по правам:
venue_admin— всё, остальные — только reorder внутри своей секции. -
Тёмная тема — переключение между светлой, тёмной и системной. Выбор сохраняется в localStorage.
-
Pull-to-refresh — на мобильных устройствах (<768px) жест смахивания вниз запрашивает свежие данные.
-
Мгновенные обновления для организаторов через WebSocket; зрители получают обновления с накоплением (30-секундный debounce).
-
OS-уведомления — через Notification API + Service Worker.
-
Service Worker на TypeScript — кэширование статики, нотификации.
-
Легкое развёртывание — одиночный бинарник Go + SQLite, готовый Docker-образ. Фронтенд вшит в бинарник через
go:embed. -
Гибкий эндпоинт — флаг
--prefixпозволяет повесить приложение на подпуть. -
Аудит действий — логирование всех изменений участников, секций, залов, ролей с привязкой к пользователю.
Быстрый запуск в Docker
docker run -p 5000:3000 \
-e CONFERENCE_NAME="Моя конференция" \
-v ${PWD}/data/:/data \
-v ${PWD}/uploads:/uploads \
git.ymnuktech.ru/ymnuk/reception-conference:latest
При первом запуске автоматически создаётся администратор admin с паролем admin.
Параметры Docker-контейнера
| Переменная окружения | Описание | По умолчанию |
|---|---|---|
CONFERENCE_NAME |
Название конференции | Конференция |
DB_PATH |
Путь к файлу SQLite | conference.db |
PORT |
Порт HTTP | 3000 |
PREFIX |
Префикс эндпоинта | (пусто) |
BASE_PATH |
Адрес для прослушивания (:port или host:port) |
:3000 |
CHUNKER_UPLOAD_URL |
URL сервиса chunker-upload | (пусто — прямой upload) |
CHUNKER_APP_ID |
App ID для chunker-upload | conference |
CHUNKER_TOKEN |
Токен доступа к chunker-upload | (пусто) |
CHUNKER_WEBHOOK_TOKEN |
Токен для проверки webhook от chunker | (пусто — без проверки) |
CHUNKER_BASE_PATH |
Базовый путь в chunker-upload | conference |
CONVERTER_URL |
URL сервиса conference-convert | (пусто — отключён) |
CONVERTER_TOKEN |
Токен для push-уведомлений в конвертер | (пусто) |
DISPLAY_BASE_URL |
Базовый URL сервиса conference-display | (пусто — отключён) |
LOG_LEVEL |
Уровень логирования: debug, info, off |
info |
Важно: пробросьте тома:
-v /host/path/data:/data— база данных.-v /host/path/uploads:/uploads— загруженные презентации.
Самостоятельная сборка и запуск
Требования
- Go 1.26.4+
- Node.js 24+ и npm
Шаги
-
Клонировать репозиторий
git clone <repository-url> cd reception-conference -
Собрать фронтенд
cd frontend npm install npx ng build --prod --output-path=../backend/cmd/server/static cd .. -
Собрать бэкенд
cd backend go mod download go build -o conference-server ./cmd/server -
Запустить сервер
./conference-server --db=./data.db --port=8080 -
Открыть в браузере —
http://localhost:8080. Логин:admin, пароль:admin.
Для разработки фронтенд запускается отдельно (
ng serve), бэкенд с флагом--devдля CORS. Прокси настроен вfrontend/proxy.conf.json.
Параметры командной строки
| Аргумент | Переменная окружения | По умолчанию | Описание |
|---|---|---|---|
--db |
DB_PATH |
conference.db |
Путь к SQLite |
--port |
PORT |
3000 |
Порт HTTP |
--base-path |
BASE_PATH |
:3000 |
Адрес прослушивания |
--prefix |
PREFIX |
(пусто) | Префикс эндпоинта |
--name |
CONFERENCE_NAME |
Конференция |
Название конференции |
--dev |
— | false |
Режим разработки (CORS) |
--chunker-url |
CHUNKER_UPLOAD_URL |
(пусто) | URL chunker-upload |
--chunker-app-id |
CHUNKER_APP_ID |
conference |
App ID для chunker |
--chunker-token |
CHUNKER_TOKEN |
(пусто) | Токен доступа к chunker |
--chunker-webhook-token |
CHUNKER_WEBHOOK_TOKEN |
(пусто) | Токен проверки webhook |
--chunker-base-path |
CHUNKER_BASE_PATH |
conference |
Базовый путь в chunker |
--converter-url |
CONVERTER_URL |
(пусто) | URL conference-convert |
--converter-token |
CONVERTER_TOKEN |
(пусто) | Токен для push в конвертер |
--display-base-url |
DISPLAY_BASE_URL |
(пусто) | URL conference-display |
--log-level |
LOG_LEVEL |
info |
Уровень логирования: debug, info, off |
Структура проекта
.
├── .woodpecker.yml # CI/CD (Woodpecker)
├── frontend/ # Angular 22
│ ├── src/
│ │ ├── app/
│ │ │ ├── app.ts/html # Корневой компонент
│ │ │ ├── services/ # WebSocket, File, Theme
│ │ │ ├── components/ # ProgramTable, SectionGroup
│ │ │ ├── dialogs/ # Login, Add, Edit, Import, Upload, Admin, etc.
│ │ │ └── directives/ # PullToRefresh
│ │ ├── service-worker.ts # Service Worker (TS)
│ │ └── index.html
│ ├── angular.json
│ └── package.json
├── backend/ # Go 1.26
│ ├── cmd/server/main.go # Точка входа (Echo)
│ ├── internal/
│ │ ├── api/ # External REST API (/api/v1)
│ │ ├── auth/ # JWT + Argon2id
│ │ ├── config/ # Парсинг флагов
│ │ ├── db/ # SQLite + AutoMigrate
│ │ ├── handlers/ # HTTP upload/download
│ │ ├── models/ # GORM-модели
│ │ ├── ws/ # WebSocket хаб (12 файлов)
│ │ ├── webhook/ # Приём webhook от chunker
│ │ └── integration/ # Интеграционные тесты
│ ├── go.mod
│ └── go.sum
├── docs/
│ ├── ROADMAP.md # Трекер задач
│ ├── ARCHITECTURE.md # Архитектура
│ ├── DISPLAY_PROTOCOL.md # Протокол conference-display
│ └── IMPORT.md # Формат импорта
├── Dockerfile
└── README.md
Как это работает
Аутентификация
- При открытии страницы устанавливается WebSocket-соединение (гость = viewer).
- Пользователь вводит логин/пароль → сервер возвращает JWT (15 мин) + refresh token (24 ч).
- JWT отправляется через WS
auth, сервер поднимает роль. - Refresh token rotation: каждый refresh выдаёт новую пару, старый refresh отзывается.
- Пароли хэшируются Argon2id. При первом запуске seed:
admin/admin(global_admin + security_admin).
Управление
- Площадки (venues) — создание/редактирование/удаление (только global_admin). Каждая площадка имеет дерево секций.
- Секции — иерархические, с временем начала, длительностью, перерывами. Drag-and-drop между собой.
- Участники — имя, тема, статус (pending/speaking/done), секция, файл, длительность.
- Статус
speaking— при переключении фиксируетсяStartReadingAt; при завершении —EndReadingAt. Это позволяет построить точный таймлайн. - Файлы — загрузка, конвертация (PDF/ZIP), скачивание. Форматы показываются иконками в таблице.
- Импорт — массовое добавление участников через JSON или XLSX/ODS/CSV.
- Сервис-токены — для внешних сервисов (conference-display). Привязаны к одному залу.
Интеграции
| Сервис | Назначение | Протокол |
|---|---|---|
| chunker-upload | Чанковая загрузка больших файлов | REST API + webhook |
| conference-convert | Конвертация в PDF/ZIP | Push-уведомление + webhook |
| conference-display | Отображение слайдов на экранах зала | WebSocket (ServiceToken) |
Все интеграции опциональны. Без них приложение работает штатно.
Ролевая модель и матрица прав
Глобальные роли
| Роль | Описание |
|---|---|
global_admin |
Полный доступ ко всем площадкам, управление залами, токенами. Создаётся при seed. |
security_admin |
Управление пользователями (создание, блокировка, сброс пароля, назначение глобальных ролей). |
viewer |
Начальная роль гостя. Только просмотр активных участников. |
Локальные роли (назначаются на площадку)
Каждый пользователь может иметь разные роли на разных площадках.
| Роль | Полномочия |
|---|---|
venue_admin |
Полный CRUD участников и секций, перемещение между секциями, drag-and-drop секций. |
operator |
Смена статуса, перестановка порядка, загрузка/замена файлов. |
secretary |
Смена статуса, перестановка порядка, редактирование имени/темы. |
moderator |
Смена статуса, перестановка порядка. |
Матрица действий
| Действие | global_admin | venue_admin | operator | secretary | moderator | viewer |
|---|---|---|---|---|---|---|
| Управление площадками | ✅ | — | — | — | — | — |
| CRUD секций | ✅ | ✅ (свои) | — | — | — | — |
| Drag-and-drop секций | ✅ | ✅ (свои) | — | — | — | — |
| Добавление/удаление участника | ✅ | ✅ (свои) | — | — | — | — |
| Редактирование имени/темы | ✅ | ✅ (свои) | — | ✅ | — | — |
| Смена статуса | ✅ | ✅ (свои) | ✅ | ✅ | ✅ | — |
| Перестановка внутри секции | ✅ | ✅ (свои) | ✅ | ✅ | ✅ | — |
| Перемещение между секциями | ✅ | ✅ (свои) | — | — | — | — |
| Загрузка/замена файла | ✅ | ✅ (свои) | ✅ | — | — | — |
| Скачивание файла | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Сервис-токены | ✅ | — | — | — | — | — |
| Управление пользователями | ✅ | — | — | — | — | — |
| Просмотр участников | ✅ | ✅ (свои) | ✅ (свои) | ✅ (свои) | ✅ (свои) | ✅ (активные) |
Статусы участников
| Статус | Описание |
|---|---|
pending |
Начальный статус. Файл не загружен. |
ready |
Файл загружен, готов к выступлению. |
speaking |
Выступает сейчас. Фиксируется StartReadingAt. |
done |
Выступление завершено. Фиксируется EndReadingAt. |
Цикл: pending → ready → speaking → done. Из done можно вернуть в ready или pending.
Исключённые участники
Скрыты от зрителей, не участвуют в сортировке, не редактируются и не удаляются. Возвращаются кнопкой «Восстановить» (venue_admin/global_admin).
Массовый импорт
Доступен из двух табов:
JSON:
[
{"name": "Иван Петров", "topic": "Квантовые вычисления"},
{"name": "Мария Сидорова", "topic": "Нейросети", "duration": 20, "venueName": "Зал A"}
]
Файл (XLSX/ODS/CSV с авто-маппингом колонок):
| Столбец | Обязательный | Описание |
|---|---|---|
| Докладчик | да | Имя |
| Тема | да | Название доклада |
| Зал | нет | Название зала (пусто → «Остальное») |
| Длительность | нет | Минуты (пусто → 15) |
Дубли (name + topic) пропускаются. При импорте авто-создаются залы и секция «Импортировано …».
Вебхуки
Система принимает два вебхука от внешних сервисов:
POST /webhook/file — от chunker-upload
Приходит после завершения чанковой загрузки файла.
{
"upload_id": "uuid",
"app_id": "conference",
"base_path": "conference",
"file_name": "presentation.pptx",
"file_size": 4194304,
"status": "completed"
}
Что делает бэкенд:
- Проверяет
Authorization: Bearer <chunker_webhook_token>(опционально, если токен задан). - Находит
FileUploadпоupload_idв БД. - Скачивает готовый файл из chunker-upload через
GET /{base_path}/file/{upload_id}. - Сохраняет локально в
/uploads/{participantID}_{filename}. - Обновляет
Participant.FilePathи статус →ready. - Если настроен converter — отправляет файл на конвертацию в PDF/ZIP.
Push POST /api/convert → conference-convert
При загрузке файла (через прямой upload или chunker) reception отправляет файл на конвертацию:
POST {converter_url}/api/convert
Content-Type: multipart/form-data
Authorization: Bearer {CONVERTER_TOKEN}
Поля формы:
file— загруженный файл (оригинальное имя)talk_id— UUID участникаcallback_url— URL для ответа:{PUBLIC_URL}/api/v1/webhook/converted[?secret={CONVERTER_TOKEN}]
Retry: 3 попытки с интервалом 1, 3, 5 сек. При timeout или не 201/202 — повтор.
POST /api/v1/webhook/converted — callback от conference-convert
Принимает результат конвертации. Аутентификация: если задан CONVERTER_TOKEN, проверяется ?secret=<токен> query-параметр.
Успех (event: "conversion.completed")
{
"event": "conversion.completed",
"task_id": "conv_abc123",
"talk_id": "019f1c99-...",
"status": "success",
"result": {
"origin_url": "https://...",
"pdf_url": "/api/result/conv_abc123/pdf",
"zip_url": "/api/result/conv_abc123/zip",
"format": "mixed",
"total_slides": 15,
"size_bytes": 2048576,
"converted_at": "2026-06-30T10:00:00Z"
}
}
Что делает бэкенд:
- Retry guard: если
task_idуже существует (sentinel или готовые файлы) —200 OK, пропуск. - Создаёт sentinel-запись
ConvertedFile{Format: "_processing_", TaskID: task_id}— блокирует retry. - Возвращает
200 OKнемедленно. - В горутине: скачивает
pdf_urlиzip_url, сохраняет как{participantID}_converted_pdf.pdf/{participantID}_converted_zip.zip, удаляет sentinel, создаёт полноценные записиConvertedFile. - Вызывает
OnChange→ WS broadcastupdateс актуальнымAvailableFormats.
Ошибка (event: "conversion.failed")
{
"event": "conversion.failed",
"task_id": "conv_abc123",
"talk_id": "019f1c99-...",
"status": "error",
"error": {
"code": "LIBREOFFICE_TIMEOUT",
"message": "Conversion timeout",
"details": ""
}
}
Логируется и возвращается 200 OK.
Оба вебхука опциональны — без них приложение работает штатно, просто без конвертации форматов.
Тестирование
Backend (Go) — 115 тестов
cd backend
go test ./... -v
Пакеты:
ws— 40 тестов (роли, секции CRUD, перемещение, таймлайн, аудит, venue, display, permissions)api— 15 тестов (ServiceToken, participants, PATCH статус, file GET, converter webhook)auth— 15 тестов (логин, refresh, JWT, блокировка, argon2id)handlers— 17 тестов (upload, download, uploadInit, валидация, замена файла, лимит)models— 14 тестов (CRUD всех моделей)webhook— 9 тестов (приём webhook, проверка токена, скачивание)integration— 3 теста (полный цикл через chunker-upload + unauthorized + healthcheck)config— 2 теста (парсинг флагов)
Frontend (Angular / Vitest) — 77 тестов, 9 файлов
cd frontend
npm test
Разработка
# Фронтенд (порт 4200)
cd frontend && ng serve
# Бэкенд (порт 8080, с CORS для dev)
cd backend && go run ./cmd/server --port=8080 --dev
Прокси настроен в frontend/proxy.conf.json.
CI/CD
.woodpecker.yml — Woodpecker CI. При создании тега:
- Сборка фронтенда.
- Сборка Go-бинарника (со встроенной статикой).
- Публикация релиза с бинарниками для Linux и Windows.
- Сборка Docker-образа.
Известные ограничения
- SQLite не рассчитана на высокую конкурентную запись (>10 одновременных редакторов).
- Нет поддержки нескольких конференций одновременно.
- Загрузка файлов ограничена 100MB.
- Notification API требует HTTPS (кроме localhost), на iOS Safari не поддерживается.
- Конвертация файлов требует внешнего сервиса
conference-convert.
Лицензия
MIT. Подробнее в файле LICENSE.