# Aspans · Обходы Сервис обходов охраняемых объектов: периметры и маршруты на карте, график дежурств, мобильный экран сотрудника с офлайн-очередью, отчёты с фото, видео и геометками, вебхуки для интеграций. Стек: React 18 + TypeScript + MUI (Vite, PWA), NestJS 11 + TypeORM, PostgreSQL 17 + PostGIS, SeaweedFS (S3 API) для медиа, tileserver-gl для своей карты Казахстана. Всё поднимается через docker compose. ## Запуск ```bash cp .env.example .env ./scripts/build-tiles.sh docker compose up -d --build ``` Откройте http://localhost:8090 и войдите владельцем платформы — учётной записью паспорта Aspans из `PLATFORM_ADMIN_USERNAMES` (по умолчанию `admin`, с его паролем из паспорта). Компанию можно зарегистрировать там же («Организации») или самостоятельно на странице входа → «Зарегистрировать». Перед реальным использованием смените в `.env` `JWT_SECRET`, `POSTGRES_PASSWORD`, `S3_SECRET_KEY`. ## Паспорт Aspans, Aspans ID и Kafka Люди и пароли живут в паспорте Aspans, «Обходы» — платформа `patrol` в нём. Организации, роли и доступ к объектам — свои, внутри «Обходов». 1. В паспорте через интерфейс «Платформы и группы» создать платформу `patrol` (без домена) и её группу `patrol_user`; выложить метод `v1/users/findUsers`. 2. В `.env`: `PASSPORT_URL`, `PASSPORT_PLATFORM_TOKEN` (= PLATFORM_SALT паспорта), `ASPANS_ID_BASE_URL`. Локально паспорт — контейнер в docker-сети `aspans` (см. `docker-compose.override.yml`), на сервере — `https://passport.aspans.com`. 3. Синхронизация через Kafka (как у symfony): в проекте kafka `make service NAME=platform-patrol` и `./bin/service.sh grant-shared platform-patrol aspans.`, пароль — в `KAFKA_PASSWORD`, затем `docker compose --profile kafka up -d users_sync`. Как это работает: - **Вход** — логином и паролем паспорта или через Aspans ID (SSO). Если человек уже вошёл в другой сервис Aspans, «Обходы» входят тихо (`prompt=none`). Пускают только тех, кто заведён в «Обходах» и состоит в организации с ролью; владельцы платформы — по `PLATFORM_ADMIN_USERNAMES`. - **Новый сотрудник** заводится в паспорте (`createOrGetUserSimple`, группа `patrol_user`). **Существующий** — ищется в паспорте (`findUsers`, контакты замаскированы) и привязывается к платформе. Один человек — одна организация в «Обходах». - **Профиль** (ФИО, email, телефон) правится в обе стороны через `aspans.users.changed`: правки из «Обходов» уходят событием `UserProfileUpsertedEvent` (очередь `users_sync_pending`), правки из паспорта применяются по версии (LWW). Пароль меняется сразу в паспорте. - **Удаление**: убрали с платформы в паспорте (`UserRemovedFromPlatformEvent`) — доступ закрывается, сессии гаснут, человек пропадает из сотрудников; запись остаётся для отчётов. «Убрать из организации» в «Обходах» отвязывает человека от платформы в паспорте. `build-tiles.sh` один раз скачивает OSM-данные Казахстана и собирает `tiles/kazakhstan.mbtiles` (planetiler, нужно ~4 ГБ RAM, несколько минут). Без этого файла сервис работает, но карта будет пустой. Повторный запуск обновляет карту, после него — `docker compose restart tiles`. ## Сервер Без своего домена сервис открывается по `https://.sslip.io`: sslip.io указывает на IP из имени, Caddy сам получает сертификат Let's Encrypt. HTTPS обязателен для телефонов охранников: без него браузер не даёт геолокацию и не включает офлайн-режим PWA. 1. Проверить сервер (только читает): `ssh user@IP 'bash -s' < scripts/server-check.sh`. Нужно: Docker с `docker compose` v2, свободные 80 и 443 (и открытые в файрволе), от 2 ГБ RAM (сборка фронта), от 10 ГБ свободного места плюс место под фото и видео. 2. Склонировать репозиторий, `cp .env.production.example .env`, заполнить. 3. Карту на сервере **не собирать**: скопировать готовый файл — `scp tiles/kazakhstan.mbtiles user@IP:<папка проекта>/tiles/` (~0,5 ГБ). Сборка planetiler требует ~4 ГБ RAM и качает исходные данные. 4. `docker compose up -d --build` (`COMPOSE_FILE` из `.env` сам подключит `docker-compose.prod.yml`). 5. В Aspans ID у клиента `patrol` разрешить redirect_uri `https://.sslip.io/auth/sso/callback`. Наружу открыты только 80 и 443 (Caddy). База слушает 127.0.0.1, api, tiles и s3 доступны только внутри docker. ## Порты | Что | Адрес | |---|---| | Веб и API | http://localhost:8090 (`/api/…`, карта — `/tiles/…`) | | PostgreSQL | localhost:5470 | ## Разработка без Docker для фронта ```bash npm install docker compose up -d db s3 tiles api web npm run dev:web ``` Vite на http://localhost:5173 проксирует `/api` и `/tiles` на :8090. ## Устройство ``` apps/api NestJS: auth, organizations, users/roles, sites, routes, schedules, rounds, media, reports, webhooks apps/web React: панель управления и экран сотрудника /guard (PWA, офлайн-очередь в IndexedDB) packages/shared права, статусы, флаги отметок, события вебхуков — общие для фронта и API docker/ Dockerfile'ы и nginx scripts/ сборка тайлов карты ``` - Обходы создаются из графиков на 7 дней вперёд (cron раз в час) для дней, где назначены дежурные. Начать обход может любой дежурный этого дня. - Незакрытые к концу окна обходы становятся «Пропущен» или «Не полностью». Отметки, снятые офлайн до конца окна и пришедшие позже, пересчитывают статус. - GPS только записывается: отметку «я тут» сотрудник ставит на карте, координаты телефона сохраняются рядом. Расхождения видны в отчёте флагами и ничего не блокируют. - Медиа грузятся кусками по 8 МБ через API в SeaweedFS и докачиваются после обрыва связи. - Вебхуки: `POST` с JSON, подпись `X-Patrol-Signature: sha256=`; события `round.completed`, `round.partial`, `round.missed`, `visit.issue`. - Реквизиты по БИН — из pk.uchet.kz: публичный поиск без токена даёт название, адрес и руководителя. С токеном из личного кабинета pk.uchet.kz (`UCHET_KZ_TOKEN` в `.env`) добавляются телефон и email. Выключить обращения к uchet.kz: `UCHET_KZ_DISABLED=true`.