Telegram-бот + сайт статистики велоклубу: учасники надсилають кілометраж у чат, бот рахує рейтинги, тижневі та річні підсумки й внутрішню валюту Torcoins; сайт показує поточний тиждень, архів, річні рейтинги та профілі учасників.
Це новий проєкт з чистою архітектурою. Старий PHP-проєкт з тією ж назвою використовувався лише як референс бізнес-логіки під час проєктування - код з нього не переносився.
Laravel 11 (PHP) + Vue 3 + Inertia.js + Tailwind CSS v4 + MySQL. Повне обґрунтування — STACK_DECISION.md. Опис архітектури — PROJECT_PLAN.md. Інструкції по деплою на VPS — DEPLOYMENT.md.
Вимоги: PHP 8.2+, Composer, Node.js 18+/npm, MySQL.
composer install
npm install
cp .env.example .env
php artisan key:generateВідредагувати:
DB_DATABASE=velotor_ride
DB_USERNAME=root
DB_PASSWORD=
VELOTOR_TIMEZONE=Europe/Kyiv
TELEGRAM_BOT_TOKEN= # токен від @BotFather
TELEGRAM_WEBHOOK_SECRET= # будь-який довгий випадковий рядок
TELEGRAM_CHAT_ID= # id чату клубу для тижневих звітів
Створити базу даних (MySQL має вже бути запущений):
mysql -u root -e "CREATE DATABASE velotor_ride CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"php artisan migrate --seedСидер створює: адмін-акаунт (admin@velotor.ride / password — обов'язково
змінити пароль на проді), базові налаштування, ~14 учасників і суцільний
ланцюжок тижнів за останні ~60 тижнів (з переходом через Новий рік) із
випадковими результатами — щоб інтерфейс одразу був наповнений даними.
composer run devЦя команда одночасно піднімає php artisan serve, чергу (queue:listen —
не використовується логікою проєкту, але йде в комплекті зі стандартним
скриптом Laravel), логи (pail) і Vite dev-сервер. Сайт буде доступний на
http://localhost:8000.
Щоб локально перевірити закриття тижня без очікування понеділка, просто
викликайте команду вручну: php artisan week:close.
Якщо потрібен лише сайт без hot-reload:
npm run build
php artisan serveТиждень клубу — окрема сутність weekly_periods, а не голий номер: у неї є
start_date/end_date (понеділок 00:00 → наступний понеділок 00:00 за
VELOTOR_TIMEZONE), і саме діапазон дат визначає "поточний тиждень", а не
лічильник. Пара (year, week_number) унікальна; при переході через Новий рік
week_number автоматично скидається на 1, а year — оновлюється, без
ручного втручання. Усі результати завжди прив'язані до weekly_period_id
(FK) — це структурно виключає змішування даних різних років на стику тижня 1.
Щопонеділка о 00:00 (php artisan week:close, через Laravel Scheduler)
система: рахує підсумки активного тижня, формує топ-5, надсилає звіт у
Telegram, закриває тиждень і відкриває наступний. Дія ідемпотентна: якщо
scheduler випадково запуститься двічі поспіль, повторний виклик нічого не
зробить (звіт не продублюється).
Torcoins нараховуються пропорційно дистанції: 100 км = 1 Torcoin. Перший учасник із результатом у кожному новому тижні додатково отримує 0.1 Torcoin. Баланс рахується окремо за весь час і за поточний рік.
Розпізнає повідомлення виду результат 10, результат 10 км, результат 10.5, результат 10,5, result 10, +10 км (крапка або кома як
розділювач, одиниця виміру необов'язкова після ключового слова). Усі інші
повідомлення в чаті ігноруються. Команди: /start, /help, /me, /top,
/week, /year, /alltime, /admin.
/admin відкриває кнопки для ручного додавання результату за поточний або
попередній тиждень, фіксації тижня та відкату останньої фіксації. Доступ мають
адміністратори Telegram-чату. Для роботи в приватному чаті можна вказати їхні
Telegram user ID через кому в TELEGRAM_ADMIN_IDS.
Локально Telegram не може достукатись до localhost напряму — для
розробки використовуйте тунель (ngrok/Cloudflare Tunnel) і вкажіть публічну
URL:
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \
-d "url=https://<ваш-домен-або-тунель>/telegram/webhook" \
-d "secret_token=<TELEGRAM_WEBHOOK_SECRET>"Для продакшену — див. DEPLOYMENT.md.
* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1Перевірити, що команда закриття тижня запланована:
php artisan schedule:list/admin/login (сидер створює admin@velotor.ride / password). Дозволяє:
переглядати учасників і результати, редагувати/видаляти помилковий
результат (з автоматичним перерахунком статистики тижня), запускати повний
перерахунок статистики, дивитись логи Telegram-бота, дивитись статус
поточного тижня і закривати його вручну, змінювати telegram_chat_id та
часовий пояс.
php artisan test83 тести (unit + feature) покривають: розпізнавання всіх форматів результату, захист від дублів, розрахунок Torcoins, логіку тижнів/років (включно з переходом через Новий рік), ідемпотентність закриття тижня, Telegram-вебхук (усі команди, некоректні payload, перевірку секретного токена), публічні сторінки сайту та адмінку.
- Написати боту
/startу чаті — має відповісти коротким описом. - Написати
результат 15 км— бот має відповісти підсумком (км за тиждень/усього/Torcoins) і зберегти результат. - Написати щось не по темі — бот не повинен відповідати взагалі.
/me,/top,/week,/year,/alltime— мають відповідати коротким рейтингом/статистикою./admin/bot-logs— кожне повідомлення має з'явитись у логах зі статусомok/ignored/error.
php artisan week:closeПерший запуск закриває активний тиждень (якщо він уже закінчився, або
завжди — через адмінку з примусовим закриттям), надсилає звіт у
TELEGRAM_CHAT_ID і створює наступний тиждень. Повторний виклик одразу
після цього нічого не робить — це і є ідемпотентність, покрита тестом
WeeklyCloseActionTest.