7.6 KiB
production
Модуль платформы сервисного центра (Севастополь): приём в ремонт — клиенты
(физ/юр), заказы, статусы, гарантия, публичный трекинг, фото/комментарии
по заказу. Часть модульной архитектуры — see core (service-center repo)
для реестра модулей и общей staff-идентичности.
Этот сервис не имеет своего логина. Staff JWT выпускает core; production
только верифицирует токен тем же JWT_SECRET (HS256, общий секрет).
Домены
- clients — физ/юр клиенты
- orders — заказы в ремонт: статус (
new → diagnosing → in_repair → waiting_parts → ready → completed, либоcancelled), гарантия (warranty_until+original_order_id— связь повторного ремонта с исходным), назначенный мастер, оценка/итоговая цена - order_events — таймлайн заказа: смена статуса (всегда публична),
комментарии и фото (публичность —
is_public, по умолчанию комментарии приватные, фото публичные)
API
Staff-эндпоинты (Authorization: Bearer <JWT из core>):
POST /api/clients
GET /api/clients?q=
GET /api/clients/:id — + история заказов
POST /api/orders
GET /api/orders?status=&assigned_master_id=&client_id= — Kanban-лента (плоский список, группировка на фронте)
GET /api/orders/:id — + полный таймлайн
PATCH /api/orders/:id — мастер/гарантия/цены, без записи в таймлайн
PATCH /api/orders/:id/status — пишет событие в таймлайн
POST /api/orders/:id/comments — {body, is_public}
POST /api/orders/:id/photos — multipart, {file, is_public}
Публичный (без авторизации, токен = ключ доступа):
GET /api/track/:token — статус, устройство, гарантия, только публичные события
GET /api/files/:key (staff-authenticated) отдаёт файл целиком (не стримом —
SendStream/SetBodyStream в fasthttp читает тело уже после возврата
хендлера, так что defer obj.Close() гонялся бы с чтением; файлы ≤20MB,
буферизация в память безопасна). <img src> не может послать Bearer-токен,
поэтому фронтенд грузит фото через fetch и URL.createObjectURL.
GET /api/orders/:id/invoice.pdf — Счёт на оплату
GET /api/orders/:id/act.pdf — Акт выполненных работ
Оба — staff-authenticated, PDF генерируется на лету через internal/pdfgen
(github.com/go-pdf/fpdf, кириллица — встроенные через go:embed шрифты
DejaVu Sans, не читаются с диска в рантайме). Реквизиты бизнеса — из
BUSINESS_* env (все опциональны — юрлицо ещё не зарегистрировано, пустые
поля рендерятся как «_______», а не падают/пустая строка). Различают
физ/юр клиента (ИНН/КПП/адрес только для type=company). Номер документа
детерминированно из первых 8 hex символов order id (нет БД-последовательности).
<a href> не может послать Bearer-токен — фронтенд фетчит PDF как blob и
открывает в новой вкладке (api.orders.openDocument, тот же приём, что и
для фото). Лимиты длины на все поля, которые попадают в PDF (client/order
Create/Update) — иначе MultiCell заворачивает произвольно длинный текст в
произвольно длинный документ (реальный DoS-вектор, найден security-reviewer).
Фронтенд (web/)
React + Vite, UI перенесён из Glass CRM (Kanban на @dnd-kit, карточки,
модалки) и адаптирован под fetch вместо supabase.from(...). Два бэкенда:
core (только /api/auth/login) и production (всё остальное) — оба URL через
VITE_CORE_URL/VITE_PRODUCTION_URL (compile-time, Vite их запекает в бандл).
cd web && cp .env.example .env && npm install && npm run dev
Страницы: /login, /kanban, /clients, /track/:token (публичная).
JWT хранится в localStorage, auth — React Context (AuthContext.jsx), не
голый хук — иначе каждый вызывающий компонент завёл бы свою копию состояния.
Регистрация в core
# на core, как owner:
curl -X POST $CORE_URL/api/modules -H "Authorization: Bearer $OWNER_JWT" \
-d '{"name":"production","base_url":"http://production:3000","health_path":"/health"}'
# → сохранить token в MODULE_TOKEN здесь
После старта сервис сам шлёт heartbeat в core каждые 30с
(internal/coreclient). Без CORE_URL/MODULE_NAME/MODULE_TOKEN просто
не регистрируется — сервис работает автономно, core не жёсткая зависимость.
Разработка
cp .env.example .env # JWT_SECRET должен совпадать с core/.env!
docker compose up -d --build
curl http://127.0.0.1:18091/health
backend подключён к внешней сети platform_net (создаётся один раз:
docker network create platform_net) — так production достаёт core по
алиасу core, а core, если понадобится, достанет production по production.
Порт наружу — 127.0.0.1:18091, как и у core, через хостовый Caddy.
Пока не сделано
- Заправка картриджей (Фаза 3) — отдельный модуль или расширение этого
- Публичный трекинг не показывает сами фото (только факт «добавлено») —
/api/files/:keyтребует staff JWT, публичного скоупа под токен трекинга нет - Kanban во фронтенде без realtime — поллинг раз в 20с (нет WebSocket в Фазе 1)
- Документы: реальных реквизитов бизнеса ещё нет (юрлицо не зарегистрировано,
BUSINESS_*пустые), нет ролевого разграничения — любой авторизованный staff может сгенерировать документ по любому заказу (существующая во всём API норма, не регрессия этой фичи —auth.RequireRoleесть в коде, но нигде не подключён; если нужно разделение ролей — отдельная задача на весь API)