An open API service indexing awesome lists of open source software.

https://github.com/mrnko/velotor-ride

Telegram bot for tracking cycling club statistics, ride distances, and weekly leaderboards. Built with pure PHP and Telegram Bot API for the «VeloTor» community.
https://github.com/mrnko/velotor-ride

php telegram telegrambot

Last synced: 8 days ago
JSON representation

Telegram bot for tracking cycling club statistics, ride distances, and weekly leaderboards. Built with pure PHP and Telegram Bot API for the «VeloTor» community.

Awesome Lists containing this project

README

          

# Velotor Ride

Telegram-бот + сайт статистики велоклубу: учасники надсилають кілометраж у
чат, бот рахує рейтинги, тижневі та річні підсумки й внутрішню валюту
Torcoins; сайт показує поточний тиждень, архів, річні рейтинги та профілі
учасників.

Це новий проєкт з чистою архітектурою. Старий PHP-проєкт з тією ж назвою
використовувався лише як референс бізнес-логіки під час проєктування - код з
нього не переносився.

## Стек

Laravel 11 (PHP) + Vue 3 + Inertia.js + Tailwind CSS v4 + MySQL. Повне
обґрунтування — [STACK_DECISION.md](STACK_DECISION.md). Опис архітектури —
[PROJECT_PLAN.md](PROJECT_PLAN.md). Інструкції по деплою на VPS —
[DEPLOYMENT.md](DEPLOYMENT.md).

## Встановлення (локально)

Вимоги: PHP 8.2+, Composer, Node.js 18+/npm, MySQL.

```bash
composer install
npm install

cp .env.example .env
php artisan key:generate
```

### Налаштування `.env`

Відредагувати:

```
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 має вже бути запущений):

```bash
mysql -u root -e "CREATE DATABASE velotor_ride CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
```

### Міграції та демо-дані

```bash
php artisan migrate --seed
```

Сидер створює: адмін-акаунт (`admin@velotor.ride` / `password` — **обов'язково
змінити пароль на проді**), базові налаштування, ~14 учасників і суцільний
ланцюжок тижнів за останні ~60 тижнів (з переходом через Новий рік) із
випадковими результатами — щоб інтерфейс одразу був наповнений даними.

### Запуск локально

```bash
composer run dev
```

Ця команда одночасно піднімає `php artisan serve`, чергу (`queue:listen` —
не використовується логікою проєкту, але йде в комплекті зі стандартним
скриптом Laravel), логи (`pail`) і Vite dev-сервер. Сайт буде доступний на
`http://localhost:8000`.

Щоб локально перевірити закриття тижня без очікування понеділка, просто
викликайте команду вручну: `php artisan week:close`.

Якщо потрібен лише сайт без hot-reload:

```bash
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

Torcoins нараховуються пропорційно дистанції: 100 км = 1 Torcoin. Перший
учасник із результатом у кожному новому тижні додатково отримує 0.1 Torcoin.
Баланс рахується окремо за весь час і за поточний рік.

## Telegram-бот

Розпізнає повідомлення виду `результат 10`, `результат 10 км`, `результат
10.5`, `результат 10,5`, `result 10`, `+10 км` (крапка або кома як
розділювач, одиниця виміру необов'язкова після ключового слова). Усі інші
повідомлення в чаті ігноруються. Команди: `/start`, `/help`, `/me`, `/top`,
`/week`, `/year`, `/alltime`.

### Налаштування вебхука

Локально Telegram не може достукатись до `localhost` напряму — для
розробки використовуйте тунель (ngrok/Cloudflare Tunnel) і вкажіть публічну
URL:

```bash
curl -X POST "https://api.telegram.org/bot/setWebhook" \
-d "url=https://<ваш-домен-або-тунель>/telegram/webhook" \
-d "secret_token="
```

Для продакшену — див. [DEPLOYMENT.md](DEPLOYMENT.md).

### Налаштування cron

```bash
* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1
```

Перевірити, що команда закриття тижня запланована:

```bash
php artisan schedule:list
```

## Адмінка

`/admin/login` (сидер створює `admin@velotor.ride` / `password`). Дозволяє:
переглядати учасників і результати, редагувати/видаляти помилковий
результат (з автоматичним перерахунком статистики тижня), запускати повний
перерахунок статистики, дивитись логи Telegram-бота, дивитись статус
поточного тижня і закривати його вручну, змінювати `telegram_chat_id` та
часовий пояс.

## Тести

```bash
php artisan test
```

83 тести (unit + feature) покривають: розпізнавання всіх форматів
результату, захист від дублів, розрахунок Torcoins, логіку тижнів/років
(включно з переходом через Новий рік), ідемпотентність закриття тижня,
Telegram-вебхук (усі команди, некоректні payload, перевірку секретного
токена), публічні сторінки сайту та адмінку.

## Як перевірити, що бот працює

1. Написати боту `/start` у чаті — має відповісти коротким описом.
2. Написати `результат 15 км` — бот має відповісти підсумком (км за
тиждень/усього/Torcoins) і зберегти результат.
3. Написати щось не по темі — бот не повинен відповідати взагалі.
4. `/me`, `/top`, `/week`, `/year`, `/alltime` — мають відповідати коротким
рейтингом/статистикою.
5. `/admin/bot-logs` — кожне повідомлення має з'явитись у логах зі
статусом `ok`/`ignored`/`error`.

## Як перевірити закриття тижня

```bash
php artisan week:close
```

Перший запуск закриває активний тиждень (якщо він уже закінчився, або
завжди — через адмінку з примусовим закриттям), надсилає звіт у
`TELEGRAM_CHAT_ID` і створює наступний тиждень. Повторний виклик одразу
після цього нічого не робить — це і є ідемпотентність, покрита тестом
`WeeklyCloseActionTest`.