Skip to content

Repository files navigation

Carpool Queue

Внутренний сервис для организации очереди водителей, которые по очереди берут заказы на поездки. Решает простую задачу: кто из группы водителей едет следующим, кто уже отказался, а кто завершил поездку — без ручного учёта в чатах и таблицах.

Разработан для доверенной группы примерно из 10 человек.

Возможности

  • Две независимые очереди водителей — для дальних (межгородних) и коротких поездок, с автоматическим порядком каждая (GET /queue); водитель может состоять в обеих
  • Создание заказа с обязательным указанием типа (дальний/короткий) и автоматическим предложением первому в очереди этого типа (POST /orders)
  • Принятие или отказ от предложенного заказа (POST /orders/{id}/respond)
  • Самоназначение на заказ вне очереди с указанием причины (POST /orders/{id}/self-assign) — водитель сразу становится исполнителем и уходит в конец очереди, как при обычном принятии
  • Завершение поездки (POST /orders/{id}/complete)
  • Отмена заказа (POST /orders/{id}/cancel) — если заказ уже был принят, водитель возвращается в начало очереди
  • История всех заказов с фильтрами по статусу, типу и пользователю (GET /orders)
  • Отображение активных предложений — кому и когда сейчас предложен заказ (GET /orders/pending)
  • Журнал действий — отдельная от истории заказов хронология событий (создание, предложение, принятие, отказ, завершение, отмена, изменения очереди) с фильтрами (GET /activity)
  • Статистика — общие счётчики по заказам и разбивка по водителям (получено/завершено/отменено), отдельная страница (GET /statistics/summary)
  • Редактируемый прайс-лист (GET/POST/PUT/DELETE /price) с поиском и логом изменений (GET /price/log) — добавлять/менять/удалять позиции может любой из группы
  • Роли — водитель/диспетчер/администратор, совмещение возможно (см. ARCHITECTURE.md, «Роли и права доступа»): водители принимают/самоназначаются на заказы, диспетчеры дополнительно назначают/переназначают заказы вне очереди (POST /orders/{id}/assign), редактируют и заменяют уже созданные заказы (PATCH /orders/{id}, POST /orders/{id}/replace) и отменяют любой заказ, администратору доступен весь функционал плюс управление пользователями/ролями/очередью
  • Страница «Пользователи» — список всех с ролями, видна любому залогиненному; страница «Админ» — добавление пользователей, управление ролями, порядок очереди (веб-аналог scripts/add_user.py/scripts/reorder_queue.py), доступна только администраторам
  • Веб-интерфейс на русском языке: дэшборд с очередью и созданием заказов + страницы истории, прайса, пользователей, статистики и админки, все взаимосвязаны навигацией
  • PWA — устанавливается на домашний экран телефона, кнопкой «Установить на экран» или вручную через браузер; страница «Прайс» ставится отдельным значком от сайта целиком; service worker кэширует только статику, данные всегда актуальны по сети (см. ARCHITECTURE.md, «PWA»)
  • Уведомление в общую беседу VK о том, кому сейчас предложен или назначен заказ (опционально, см. VK_GROUP_TOKEN/VK_PEER_ID в .env.example и ARCHITECTURE.md)
  • Вход через VK ID — серверная сессия вместо ручного выбора себя из списка, самопривязка VK-аккаунта к пользователю при первом входе (см. ARCHITECTURE.md, «Вход через VK ID»)

Стек технологий

  • Backend: Python 3.13, FastAPI, SQLModel, SQLite, Alembic
  • Frontend: HTML, CSS, vanilla JavaScript (без фреймворков и шаблонизаторов)
  • Инфраструктура: Ubuntu Server, Caddy, systemd (задеплоено на zakaz.glorden.ru, см. DEPLOY.md)

Как это работает (бизнес-логика)

  1. При создании заказа обязательно указывается тип — дальний или короткий. Дальше вся логика ниже работает только в рамках очереди этого типа: заказ дальней очереди никогда не затрагивает позиции в короткой, и наоборот. Один и тот же водитель может состоять в обеих очередях одновременно, с независимыми позициями в каждой.
  2. Кто-то создаёт заказ — он сразу автоматически предлагается первому в очереди нужного типа.
  3. Первый в очереди либо принимает заказ (сразу уходит в конец этой же очереди, чтобы новый заказ не пришёл ему повторно, пока он занят), либо отказывается (остаётся на своей позиции, заказ автоматически уходит следующему по кругу).
  4. Любой другой водитель этой же очереди может самоназначиться на ещё не назначенный заказ вне очереди — например, если он уже находится в городе подачи. Указание причины обязательно, она сохраняется в истории заказа и журнале действий; сам водитель точно так же уходит в конец очереди, как при обычном принятии.
  5. После завершения поездки водитель отмечает заказ как завершённый.
  6. Таймаутов на ответ нет — водители сами договариваются в личном чате, сколько ждать.

Подробности и обоснование этих решений — в ARCHITECTURE.md.

Структура проекта

carpool-queue/
├── app/
│   ├── main.py              # точка входа FastAPI, регистрация роутов
│   ├── config.py             # настройки приложения
│   ├── database.py           # подключение к БД (SQLite)
│   ├── notifications.py      # уведомление в VK о предложенном заказе
│   ├── auth.py                # текущий пользователь из сессии
│   ├── vk_oauth.py            # механика VK ID OAuth (PKCE), см. ARCHITECTURE.md
│   └── models/
│       ├── user.py           # модель User
│       ├── queue.py          # модель QueuePosition, enum QueueType
│       ├── order.py          # модели Order и OrderOffer
│       ├── price.py          # модели PriceItem и PriceLogEntry
│       └── activity.py       # модель ActivityLog (журнал действий)
├── alembic/                  # миграции БД
│   └── versions/
├── static/
│   ├── index.html             # дэшборд (обе очереди + создание заказа)
│   ├── history.html           # история заказов
│   ├── activity.html          # журнал действий
│   ├── price.html             # прайс-лист (редактируемый)
│   ├── statistics.html        # статистика
│   ├── users.html             # список пользователей и их ролей (видна всем)
│   ├── admin.html             # добавление пользователей, роли, порядок очереди (только is_admin)
│   ├── link-account.html      # экран самопривязки VK-аккаунта при первом входе
│   ├── login-denied.html      # отказ во входе (VK-аккаунт не сопоставлен)
│   ├── sw.js                  # service worker (PWA), отдаётся с /sw.js
│   ├── fav/                   # favicon-иконки + PWA-манифесты
│   │   ├── site.webmanifest   # манифест сайта целиком («Очередь»)
│   │   └── price.webmanifest  # отдельный манифест страницы «Прайс»
│   ├── css/style.css
│   └── js/
│       ├── nav.js             # общая навигация на всех страницах + регистрация service worker'а
│       ├── session.js         # общий виджет "кто я" / вход через VK / выход
│       ├── install.js         # кнопка "Установить на экран" (PWA)
│       ├── dashboard.js
│       ├── history.js
│       ├── activity.js
│       ├── price.js
│       ├── statistics.js
│       ├── users.js
│       ├── admin.js
│       └── link-account.js
├── scripts/
│   ├── seed.py                # наполнение БД тестовыми данными
│   ├── add_user.py            # добавление реального пользователя (с ролями) в очередь(и)
│   ├── reorder_queue.py       # ручная перестановка порядка в одной из очередей
│   ├── grant_role.py          # точечная выдача/снятие одной роли по username
│   └── backup_db.sh           # ежедневный бэкап БД на проде (см. DEPLOY.md)
├── tests/                     # автотесты (pytest)
├── requirements.txt
├── alembic.ini
├── .env.example
├── PROGRESS.md                # журнал выполнения этапов
├── ARCHITECTURE.md            # архитектурные решения и технический долг
├── DEPLOY.md                  # установка и запуск на VPS
├── CHANGELOG.md               # журнал изменений (Keep a Changelog)
├── STYLEGUIDE.md               # соглашения по стилю кода
└── README.md

Установка и запуск

Первоначальная настройка (один раз)

cd C:\Users\Oleg\Desktop\carpool-queue
python -m venv .venv
.venv\Scripts\activate.bat
pip install -r requirements.txt
copy .env.example .env
alembic upgrade head

Последняя команда создаст файл carpool.db со всеми нужными таблицами.

Запуск (Windows, cmd.exe)

cd C:\Users\Oleg\Desktop\carpool-queue
chcp 65001
.venv\Scripts\activate.bat
uvicorn app.main:app --reload

Веб-интерфейс (дэшборд): http://127.0.0.1:8000

Проверка работоспособности: http://127.0.0.1:8000/health должен вернуть:

{"status": "ok", "message": "Carpool queue service is running"}
Запуск на Linux/macOS
source .venv/bin/activate
uvicorn app.main:app --reload

Тестовые данные

Чтобы наполнить очередь тестовыми пользователями:

python -m scripts.seed

Добавление реального пользователя в очередь

python -m scripts.add_user "Имя Фамилия" username

Добавляет пользователя с ролью «водитель» и ставит его в конец обеих очередей (дальней и короткой).

Только в одну из очередей:

python -m scripts.add_user "Имя Фамилия" username --queue-type=long
python -m scripts.add_user "Имя Фамилия" username --queue-type=short

Для диспетчера, который создаёт и назначает заказы, но сам не участвует в очереди водителей (роль «диспетчер», без очереди):

python -m scripts.add_user "Имя Фамилия" username --no-queue

Всё это можно сделать и через веб-интерфейс — страница «Админ» (только для is_admin) дублирует функциональность обоих скриптов ниже, включая выбор ролей и очередей в форме. CLI остаётся как аварийный доступ по SSH.

Роли

Три роли — is_driver/is_dispatcher/is_admin на User, совмещение возможно (см. ARCHITECTURE.md, «Роли и права доступа»). Точечно выдать/снять одну роль (например, первому администратору после деплоя):

python -m scripts.grant_role username driver
python -m scripts.grant_role username dispatcher
python -m scripts.grant_role username admin
python -m scripts.grant_role username admin --remove

Перестановка порядка в очереди

Для редкой ручной правки порядка (не для повседневного использования — обычная логика очереди отрабатывает это сама). Первый аргумент — тип очереди (long или short), дальше список должен содержать ровно тех же людей, что сейчас в этой очереди, в нужном порядке; вторая очередь не затрагивается:

python -m scripts.reorder_queue long username1 username2 username3

Тесты

Точечные автотесты на самую хрупкую логику — арифметику позиций в очереди (tests/test_queue_logic.py) и журнал действий (tests/test_activity_log.py), на изолированной временной БД:

pytest tests/

Если менялись модели БД

alembic revision --autogenerate -m "описание изменения"
alembic upgrade head

API

Метод Путь Описание
GET / веб-интерфейс (дэшборд)
GET /health проверка работоспособности сервиса
GET /users все пользователи с ролями (is_driver/is_dispatcher/is_admin), включая диспетчеров без очереди (требует сессию)
GET /queue список пользователей в текущем порядке очереди; query-параметр queue_type (long/short) — без него отдаёт обе очереди сразу (требует сессию)
POST /orders создать новый заказ (требует сессию — queue_type обязателен — long/short; route, comment — опциональны); создатель фиксируется как created_by
POST /orders/{order_id}/respond принять/отклонить предложенный заказ (требует роль водителя; 403, если заказ предложен не вам)
POST /orders/{order_id}/self-assign самоназначиться на pending-заказ вне очереди (требует роль водителя, обязательная reason) — сразу становится исполнителем и уходит в конец очереди
POST /orders/{order_id}/assign диспетчер/админ напрямую назначает pending-заказ водителю или переназначает уже assigned (тело — driver_user_id); целевой должен быть водителем в очереди этого типа; прежний исполнитель (при переназначении) возвращается вторым в очередь, новый — в конец
PATCH /orders/{order_id} диспетчер/админ правит route/comment уже созданного заказа «на месте» (из pending/assigned, иначе 400); queue_type не меняет — для этого /replace
POST /orders/{order_id}/replace диспетчер/админ заменяет заказ новым: старый отменяется, новый создаётся с телом как у POST /orders (из pending/assigned старого, иначе 400) — используется для смены queue_type или правки уже предложенного/назначенного заказа; новый заказ хранит replaces_order_id на старый
POST /orders/{order_id}/complete отметить заказ завершённым (требует роль водителя; заказ должен быть в статусе assigned и назначен вам, иначе 403)
POST /orders/{order_id}/cancel отменить заказ (требует сессию; из pending/assigned); если был assigned — водитель возвращается в начало очереди. Диспетчер/админ может отменить любой заказ; остальным — pending может отменить только создатель, assigned — создатель или исполнитель, иначе 403
GET /orders история заказов; query-параметры status, user_id, queue_type, limit (по умолчанию 100, максимум 500); сортировка по created_at (новые сверху) (требует сессию)
GET /orders/pending заказы в статусе pending вместе с данными активного предложения — кому сейчас предложен заказ и когда (требует сессию)
GET /price весь прайс-лист (требует сессию)
POST /price добавить позицию (требует сессию — category, name, price_text)
PUT /price/{item_id} изменить позицию (требует сессию — поля, которые меняются)
DELETE /price/{item_id} удалить позицию (требует сессию)
GET /price/log лог изменений прайса, новые сверху; query-параметр limit (по умолчанию 100, максимум 500) (требует сессию)
GET /activity журнал действий, новые сверху; query-параметры user_id, order_id, event_type, limit (по умолчанию 100, максимум 500) (требует сессию)
GET /statistics/summary общая статистика (total/completed/active/cancelled) и разбивка по водителям, состоящим в очереди (received/completed/cancelled для каждого) (требует сессию)
POST /admin/users создать пользователя с ролями и (опционально) сразу в конец указанных очередей (только is_admin)
PATCH /admin/users/{user_id}/roles частично изменить роли пользователя (только is_admin)
POST /admin/users/{user_id}/queue/{queue_type} добавить существующего водителя в конец очереди (только is_admin)
DELETE /admin/users/{user_id}/queue/{queue_type} убрать водителя из очереди (только is_admin)
POST /admin/queue/{queue_type}/reorder переставить порядок очереди целиком (тело — user_ids, тот же состав, что сейчас в очереди) (только is_admin)
GET /auth/vk/login начинает вход через VK ID (редирект на VK)
GET /auth/vk/callback обратный редирект от VK; логин, экран самопривязки или отказ
GET /auth/vk/link-candidates пользователи без привязанного vk_id — для экрана самопривязки (только сразу после callback с новым vk_id)
POST /auth/vk/link завершает самопривязку (user_id) — сохраняет vk_id, сразу логинит
POST /auth/logout завершает сессию
GET /me текущий пользователь из сессии вместе с ролями, либо {"authenticated": false}

Ошибки возвращаются в стандартном формате FastAPI: {"detail": "..."} с соответствующим HTTP-статусом (404/400).

Интерактивная документация API доступна по адресу /docs (Swagger UI) при запущенном сервере — требует сессию, как и весь остальной сайт (см. «Технический долг — закрыт (Шаг 27)» в ARCHITECTURE.md).

Статус проекта

Проект в активной разработке. Backend и веб-интерфейс полностью рабочие и покрывают основной сценарий использования. Актуальный список выполненных и предстоящих шагов — в PROGRESS.md.

Документация

  • PROGRESS.md — журнал выполнения этапов
  • ARCHITECTURE.md — архитектурные решения, логика очереди, технический долг, формат работы
  • DEPLOY.md — установка и запуск на VPS (Ubuntu + Caddy + systemd + HTTPS)
  • CHANGELOG.md — журнал изменений (формат Keep a Changelog)
  • STYLEGUIDE.md — соглашения по стилю кода

About

A lightweight queue management system for carpool and ride-sharing groups

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages