MVP для создания, назначения и выполнения выездных заявок. Оператор управляет заявками, а команда видит только свою работу и меняет ее статус.
- входит по телефону, паролю и демонстрационному 2FA-коду;
- видит все заявки и список выездных команд;
- создает и редактирует заявки;
- назначает или меняет исполнителя;
- без перезагрузки страницы получает изменения статуса заявки.
- видит только заявки, назначенные ее учетной записи;
- не может получить чужую заявку подменой UUID в запросе;
- переводит свою заявку
new -> in_progress -> completed; - без перезагрузки страницы получает новую назначенную заявку.
React, TypeScript, Vite, Node.js, Express, TypeORM, PostgreSQL, Redis, RabbitMQ и Socket.IO. Приложения запускаются через npm, инфраструктура — через Docker Compose.
- Node.js 22;
- npm;
- Docker Desktop с Docker Compose.
Порты: 5173, 3000, 5432, 6379, 5672, 15672.
docker compose up -d
docker compose psRabbitMQ Management UI http://localhost:15672:
- логин:
fieldops; - пароль:
fieldops.
cd backend
npm ciСкопируйте пример локальной конфигурации:
Copy-Item .env.example .envЗатем примените миграции и добавьте тестовых пользователей:
npm run db:setupКоманда применяет миграции TypeORM и добавляет тестовые данные.
В каталоге backend:
npm run devBackend http://localhost:3000.
Проверка состояния:
GET http://localhost:3000/health
ответ:
{
"status": "ok",
"database": "connected",
"redis": "connected",
"rabbitmq": "connected"
}В отдельном терминале:
cd frontend
npm ci
npm run devОткройте адрес, который напечатает Vite, обычно http://localhost:5173.
Vite проксирует /api и /socket.io на backend, поэтому для локальной разработки отдельная настройка CORS не нужна.
- Backend проверяет телефон и хеш пароля.
- Шестизначный код сохраняется в Redis на 5 минут и печатается в терминале:
[2FA] +10000000001: 123456. - После проверки кода создается Redis-сессия на 24 часа.
- Браузер получает
HttpOnlycookie с идентификатором сессии.
- Оператор входит в систему.
- Оператор создает заявку в статусе
new. - Оператор назначает пользователя с ролью
team. - Backend сохраняет изменение в PostgreSQL.
- Publisher отправляет событие
order.assignedв RabbitMQ. - Consumer получает событие и подтверждает его обработку через
ack. - Socket.IO отправляет событие только в комнату назначенного пользователя.
- Frontend команды повторно запрашивает свой список заявок.
- Команда переводит заявку в
in_progress, затем вcompleted. - После каждого изменения backend публикует
order.statusChanged. - Consumer передает событие в Socket.IO-комнату роли
operator. - Frontend оператора повторно запрашивает заявки, и таблица обновляется без ручной перезагрузки.
RabbitMQ не подключается к браузеру. Он отвечает за асинхронную передачу события внутри backend, а Socket.IO — за push-уведомление от backend к уже подключенному браузеру.
Каждый защищенный HTTP-запрос проходит через middleware authenticate:
- middleware читает идентификатор сессии из cookie;
- получает сессию из Redis;
- загружает текущего пользователя и его роль из PostgreSQL;
- записывает пользователя в
request.currentUser; - следующий middleware или route handler проверяет роль и выполняет операцию.
Операторские операции дополнительно защищены проверкой роли operator. При чтении или изменении статуса командой запрос к PostgreSQL сразу фильтруется одновременно по UUID заявки и UUID текущего исполнителя. Для чужой заявки возвращается 404, чтобы не раскрывать факт ее существования.
Ограничения существуют не только в интерфейсе: ключевые проверки выполняются backend-ом.
Role (id, name, code)—operatorилиteam.User (uuid, fullName, phone, passwordHash, role)— пользователь системы.Order (uuid, executor, executionDate, address, status, description)— заявка; исполнитель необязателен.
Одна роль связана с несколькими пользователями, один team-пользователь — с несколькими заявками. Статусы: new, in_progress, completed.
Аутентификация:
POST /api/auth/login,POST /api/auth/verify-2fa;GET /api/auth/me,POST /api/auth/logout.
Заявки:
GET /api/orders,GET /api/orders/:uuid— оператор видит все, команда только свои;POST /api/orders,PATCH /api/orders/:uuid— только оператор;PATCH /api/orders/:uuid/status— только назначенная команда.
Команды: GET /api/teams — только оператор.
WorkOrderHub/
backend/
src/
auth/
database/
entities/
messaging/
middleware/
routes/
services/
verification/
websocket/
frontend/
src/
docker-compose.yml
В каталоге backend:
| Команда | Назначение |
|---|---|
npm run dev |
Запуск с автоматическим перезапуском при изменении исходников |
npm run typecheck |
Проверка TypeScript без генерации JavaScript |
npm run build |
Компиляция в backend/dist |
npm start |
Запуск ранее собранного backend/dist/server.js |
npm run db:migrate |
Применить непримененные миграции |
npm run db:migrate:revert |
Откатить последнюю миграцию |
npm run db:seed |
Создать или обновить тестовые роли и пользователей |
npm run db:setup |
Последовательно выполнить migration и seed |
npm run verify:realtime |
Проверить основной REST/RabbitMQ/Socket.IO сценарий |
Для npm run verify:realtime должны работать инфраструктура и backend. Скрипт проверяет авторизацию, адресные уведомления и смену статусов, затем удаляет тестовые данные.
В каталоге frontend:
| Команда | Назначение |
|---|---|
npm run dev |
Запуск Vite dev server |
npm run typecheck |
Проверка TypeScript |
npm run build |
TypeScript-проверка и production-сборка в frontend/dist |
npm run preview |
Локальный просмотр production-сборки |
Основные значения уже имеют локальные defaults. Файл .env не должен попадать в Git.
| Переменная | Значение по умолчанию | Назначение |
|---|---|---|
PORT |
3000 |
Порт backend |
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_NAME |
fieldops |
Имя базы данных |
DB_USER |
fieldops |
Пользователь PostgreSQL |
DB_PASSWORD |
fieldops |
Пароль PostgreSQL |
DB_LOGGING |
false |
Логирование SQL TypeORM |
REDIS_URL |
redis://localhost:6379 |
Подключение к Redis |
RABBITMQ_URL |
amqp://fieldops:fieldops@localhost:5672 |
Подключение к RabbitMQ |
RABBITMQ_ORDER_EVENTS_QUEUE |
fieldops.order-events |
Имя очереди событий заявок |
TWO_FACTOR_TTL_SECONDS |
300 |
Срок жизни 2FA challenge |
SESSION_TTL_SECONDS |
86400 |
Срок жизни сессии |
SESSION_COOKIE_NAME |
fieldops_session |
Имя session cookie |
NODE_ENV |
development |
В production включает флаг Secure у cookie |
Frontend обычно не требует .env. Если Socket.IO находится не на origin frontend и не проксируется Vite, адрес можно задать через VITE_SOCKET_URL.
- 2FA выводится в консоль; регистрации, восстановления пароля и OAuth нет.
- Backend работает одним процессом; RabbitMQ использует одну очередь и не переподключается автоматически.
- Нет rate limiting, CSRF-токенов и аудита.
- Team — пользователь с ролью
team, отдельной сущности команды нет. - Статусы меняются только вперед; realtime-событие запускает повторный REST-запрос.
- Ручное тестирование UI и пограничных случаев еще требуется.
PostgreSQL и RabbitMQ не объединены общей транзакцией. Если запись в БД прошла, а публикация события — нет, realtime-уведомление потеряется. Transactional Outbox для MVP намеренно не реализован.
Остановить контейнеры, сохранив данные в volumes:
docker compose downУдаление volumes уничтожит локальную базу и состояния инфраструктуры, поэтому используйте такой вариант только если действительно нужен полный сброс:
docker compose down -v