- Replace emoji icons with Lucide across admin pages; add vendor/lucide.min.js and renderIcons() helper\n- Reorder sidebar logically (Dashboard, Journal, Students, Groups, Files, Links, Trash + admin sections)\n- Groups: open photo chronology only via the Фото button; covers no longer clickable\n- Journal: show group badge over card photo, compact AI-status icon beside description, and date range in empty-state message\n- Update README (AI worker, Lucide, worker.js)
WhatIDo
Учётная система для образовательного центра: журнал посещений, проектные работы воспитанников, галерея групп, откреплённые файлы и публичные страницы-витрины (share-ссылки).
Возможности
- Журнал записей — отметки о занятиях с фото и прикреплёнными файлами (проектные работы), мягкое удаление и корзина; в режиме карточек у записи показывается бейдж группы поверх фото и иконка статуса AI-проверки
- AI-проверка (воркер) — фоновый авто-чек текста записей (
worker.js); статус каждой записи (очередь / проверка / проверено / пропущено / ошибка) отображается компактной иконкой в журнале с всплывающей подсказкой - Файлы — централизованный раздел со всеми загруженными файлами, фильтры (имя воспитанника, группа, даты, поиск) и вкладка «Откреплённые»
- Группы — учебные группы, расписание (день недели, время), фотохроника группы
- Воспитанники — справочник с привязкой к группам
- Share-ссылки — публичные страницы-витрины с выбором группы / воспитанника / диапазона дат
- Дашборд — статистика, активные группы, активность за 14 дней, последние записи, топ воспитанников
- Резервное копирование — экспорт/импорт полного дампа (БД + файлы) в
tar.gz - Настройки — тексты футера, анти-спам интервал
- Публикация через Tailscale — приложение открывается по постоянному адресу
https://whatido.<tailnet>.ts.netбез проброса портов, внешнего IP и reverse-proxy
Технологии
- Node.js + Express
- PostgreSQL (pg)
- Multer (загрузка файлов), Tar (бэкапы)
- Lucide (иконки UI)
- Docker / Docker Compose
- Tailscale (Serve / Funnel) — публикация по HTTPS
Быстрый старт
Требования
- Linux-хост с Docker и плагином
docker compose - Free порта
443на хосте (его займёт tailscale для Funnel) - Аккаунт Tailscale (для публикации по ссылке)
Запуск
# 1) создайте .env из примера и задайте свои пароли
cp .env.example .env
$EDITOR .env
# 2) соберите и поднимите стек
docker compose up -d --build
После старта (без публикации через tailscale):
- HTTP
http://localhost:3003— редирект на HTTPS - HTTPS
https://localhost:3443— приложение (самоподписанный сертификат, примите предупреждение браузера) - PostgreSQL — доступен только внутри docker-сети (наружу не публикуется)
Управление:
docker compose ps # статус
docker compose logs -f app # логи приложения
docker compose down # остановка (данные сохраняются)
Приложение не запустится без
ADMIN_PASSWORD(защита от пароля по умолчанию).DB_PASSWORDзадаёт пароль пользователяappв PostgreSQL.
Конфигурация
Переменные окружения (.env):
| Переменная | По умолчанию | Назначение |
|---|---|---|
ADMIN_PASSWORD |
— (обязательно) | Пароль администратора (X-Admin-Token). Без него сервер не стартует |
DB_PASSWORD |
— (обязательно) | Пароль пользователя app в PostgreSQL |
Пример .env (в репозитории — .env.example):
ADMIN_PASSWORD=сложный-пароль
DB_PASSWORD=случайная-длинная-строка
DB_PASSWORD подставляется в docker-compose.yml в POSTGRES_PASSWORD и DATABASE_URL. Если БД уже была инициализирована ранее, значение DB_PASSWORD должно совпадать с фактическим паролем пользователя app в БД (иначе приложение не подключится).
Имя узла Tailscale задаётся в docker-compose.yml (tailscale.hostname, по умолчанию whatido).
Публичный доступ через Tailscale
Стек не требует внешнего IP и проброса портов: контейнер tailscale запускается с network_mode: host, входит в вашу tailnet-сеть и через Serve открывает приложение внутри tailnet, а через Funnel — в публичном интернете.
Цепочка:
Интернет / tailnet → https://whatido.<tailnet>.ts.net (TLS от Tailscale)
→ localhost:443 контейнера tailscale
→ https://127.0.0.1:3443 (приложение, самоподписанный cert)
Контейнер доверяет самоподписанному сертификату приложения через SSL_CERT_FILE=/etc/tailscale/app-certs/cert.pem (файл монтируется из ./certs/cert.pem).
1. Вход в tailnet при первом запуске
При первом старте контейнер автоматически выполняет tailscale up и печатает ссылку для авторизации — откройте её в браузере и добавьте устройство в аккаунт. Ввести устройство вручную там не нужно: tailscale status покажет статус:
docker exec -it whatido-tailscale-1 tailscale status
Если авторизация по какой-то причине не прошла, выполните вход вручную:
docker exec -it whatido-tailscale-1 tailscale up --hostname=whatido
# откроется ссылка вида https://login.tailscale.com/a/... — войдите в браузер
2. Включите функции и найдите адрес
После авторизации узел получит имя вида whatido и адрес:
docker exec -it whatido-tailscale-1 tailscale status
# что-то вроде: whatido.taile47725.ts.net 100.x.x.x receiver online
Чтобы публиковать сайт, для tailnet должны быть включены:
- HTTPS Certificates — автоматически выдается при первом Serve/Funnel
- Serve (доступ из tailnet) и Funnel (доступ из интернета) — включаются в админ-консоли Tailscale, например по прямой ссылке на узел:
https://login.tailscale.com/f/serve?node=<node-id>иhttps://login.tailscale.com/funnel?node=<node-id>(id узла берётся изtailscale status)
3. Запуск Serve / Funnel
Контейнер при старте сам выполняет:
tailscale funnel --bg --yes https://127.0.0.1:3443
(funnel включает и serve-часть; флаг --bg — работа в фоне, --yes — не спрашивать подтверждения.)
Если сервис уже запущен и команды в compose не отработали (например, функции только что включили в консоли), выполните вручную:
docker exec whatido-tailscale-1 tailscale funnel --bg --yes https://127.0.0.1:3443
Проверить состояние:
docker exec whatido-tailscale-1 tailscale funnel status
# https://whatido.<tailnet>.ts.net/ (Funnel on)
4. Готово
- Из любого устройства вашей tailnet:
https://whatido.<tailnet>.ts.net/ - Из интернета (при включённом Funnel): тот же адрес
- Админка:
https://whatido.<tailnet>.ts.net/admin
Важные замечания
-
Порт 443 на хосте должен быть свободен — tailscale слушает его напрямую (поэтому у сервиса
network_mode: host, а приложение опубликовано на127.0.0.1:3003/3443). -
Сертификат приложения: контейнер приложения генерирует self-signed
cert.pemпри сборке образа (это не секрет — публичный сертификат). Он монтируется в tailscale через./certs/cert.pem. Если образ приложения пересобирали впервые на новом хосте — скопируйте сертификат и перезапустите tailscale:docker compose up -d --build app docker cp $(docker compose ps -q app):/app/certs/cert.pem certs/cert.pem docker compose up -d tailscale -
/lib/modulesмонтируется в контейнер tailscale, чтобы на LC/дистрибутивах без авто-загрузки модулей корректно инициализировался netfilter (иначеtailscaledпадает сTable does not existи узел мигает online/offline). -
Ограничения сети: если у провайдера нет глобального IPv6 и соединения к ПК-ядрам Tailscale нестабильные (CGNAT), отвечать наружу узел может с перебоями — классический признак:
tailscale statusпоказываетonline, а из интернета URL не открывается. Внутри tailnet сайт работает всегда.
Альтернативная публикация (Caddy)
Исторически проект публиковался через reverse-proxy Caddy + Let's Encrypt (файлы Caddyfile.example, закомментированный сервис в старых версиях compose). Если нужно классическое публичное HTTPS на собственном домене с проброшенными портами 80/443 — этот вариант остаётся возможным: раскомментируйте/восстановите сервис caddy, укажите домен в Caddyfile и уберите сетевые блокировки. По умолчанию сейчас рекомендуется Tailscale-схема выше.
Структура данных
groups— группы, расписание (day_of_week,time_start,time_end)students— воспитанникиentries— записи журнала (фотоphoto_path, описание, мягкое удалениеdeleted_at)project_files— файлы записей (token,path, привязкаentry_id, отметкаdetached_at)group_photos— фотохроника группshare_links— публичные ссылки-витриныsettings— пары ключ/значение (анти-спам интервал, футер)
Схема инициализируется при первом запуске из db/init.sql; миграции существующей БД — в db/migration.sql.
Хранилище файлов
Загруженные фото и файлы хранятся в каталоге uploads/ на хосте и монтируются в контейнер (./uploads:/app/uploads). Это даёт прямой доступ к данным из-под хост-системы. Данные БД хранятся в именованном томе pgdata.
Бэкапы
В админке (Настройки → Бэкап) можно:
- Скачать полный бэкап —
tar.gz, содержащийdata.json(все таблицы) иuploads/ - Восстановить из файла бэкапа
Также доступны скрипты на хосте:
./scripts/backup.sh # дамп БД + фото в backups/whatido-backup-<дата>.tar.gz
./scripts/restore.sh # восстановление из архива
Безопасность
- Пароль администратора обязателен (
ADMIN_PASSWORD); фолбэка наadminнет. - CORS отключён — кросс-доменные запросы к API запрещены.
- Rate limiting по IP на публичные роуты:
POST /api/entries— 10 запросов / 15 мин, загрузка файлов и share-ссылки — 300 / 15 мин. - Загрузки ограничены: 30 МБ суммарно на запись, 10 МБ на файл; заблокированы опасные расширения (
.html,.js,.svg,.xml,.exeи др.); SVG не отдаётся inline. - Restore проходит полную валидацию данных бэкапа; удаление файлов ограничено каталогом
uploads/. - Заголовки:
helmet—X-Frame-Options,nosniff, HSTS,Referrer-Policy. - Порт БД 5432 наружу не публикуется (доступ только внутри docker-сети).
- TLS: снаружи HTTPS терминируется Tailscale (сертификат Let's Encrypt для
*.ts.net); между tailscale и приложением используется самоподписанный сертификат приложения.
Основные API
| Метод | Путь | Назначение |
|---|---|---|
GET |
/api/entries |
Записи журнала (с фильтрами) |
POST |
/api/entries |
Создать запись (photo + files) |
PUT/DELETE |
/api/entries/:id |
Обновить / мягко удалить |
GET |
/api/files |
Все файлы (фильтры) |
GET |
/api/files/detached |
Откреплённые файлы |
POST |
/api/files/:id/detach |
Открепить файл от записи |
GET |
/api/files/:token |
Скачать/показать файл по токену (публично) |
GET |
/api/groups |
Список групп |
POST/PUT/DELETE |
/api/groups/:id(?) |
CRUD групп |
GET/POST |
/api/groups/:id/photos |
Фотохроника группы |
GET/POST/PUT/DELETE |
/api/share/..., /api/links |
Публичные ссылки |
GET |
/api/backup |
Скачать бэкап |
POST |
/api/restore |
Восстановить из бэкапа |
GET |
/api/dashboard, /api/stats |
Статистика |
Защищённые админ-маршруты требуют заголовок X-Admin-Token с ADMIN_PASSWORD.
Структура проекта
├── docker-compose.yml # сервисы: app + db + tailscale
├── .env.example # шаблон переменных окружения
├── Dockerfile # сборка образа (Node 20, генерация TLS-сертификата)
├── server.js # Express-приложение
├── worker.js # фоновый worker AI-проверки записей
├── certs/ # cert.pem приложения (монтируется в tailscale, в git не хранится)
├── db/
│ ├── init.sql # схема при первом запуске
│ └── migration.sql # миграции существующей БД
├── public/ # статика (HTML/CSS/JS админки и витрин)
├── scripts/ # вспомогательные скрипты
├── uploads/ # загруженные файлы (bind-монт, вне git)
└── backups/ # локальные бэкапы