# Заявки в УК через MAX Мини-приложение и бот в MAX для жителя многоквартирного дома. Житель описывает проблему, а в ответ сразу получает в чат номер заявки, ответственную организацию и срок по нормативу. Смена статуса заявки диспетчером приходит жителю сообщением в MAX. Хакатон «Умный город», трек управления МКД. Репозиторий: https://github.com/b-on-g/max. Кто что может и все сценарии по шагам: [docs/ROLES.md](docs/ROLES.md). Разбор соответствия заданию глазами жюри: [docs/REVIEW.md](docs/REVIEW.md). ## Основной сценарий 1. Житель открывает бота по ссылке или QR-коду с подъезда, дом подставляется сам. 2. В мини-приложении выбирает, где проблема (в доме, во дворе, в городе) и что случилось, указывает подъезд, место, описание и по желанию фото. Если похожая заявка уже есть, приложение предлагает поддержать её вместо новой. 3. Заявка уходит ответственной организации автоматически: категория знает, кто отвечает (УК, АДС, РСО, муниципальная служба, участковый) и какой срок по нормативу. Бот присылает в чат: «Заявка № N зарегистрирована. Ответственный: АДС УК. Реакция по нормативу: до 16.09 12:30. Устранение: до 19.09 12:00. Основание: ПП РФ № 416, п. 13». 4. Организация получает заявку через API или диспетчер меняет статус в приложении, житель получает уведомление и видит историю. Соседи видят заявку в разделе «Дом» и могут её поддержать, поддержанные заявки видны организации с числом голосов. ## Состав и архитектура | Компонент | Путь | Роль | | --- | --- | --- | | Модель | `house/`, `category/`, `ticket/`, `uk/`, `owner/`, `status/` | Схема данных Гипербазы и правила: кто ответственный, какой срок | | Тестовые данные | `seed/` | Два дома и семь категорий с нормативами | | Бот | `bot/` | Node-процесс: мастер Гипербазы, `POST /auth`, уведомления, `/start` | | Проверка initData | `bot/check/` | HMAC-SHA256 по алгоритму MAX, тест рядом | | Мини-приложение | `app/` | Разделы «Заявки», «Дом» (заявки соседей и новости), «Профиль», «Диспетчер» для персонала УК, форма и карточка заявки | | API организаций | `bot/org/` | `GET /org/tickets` и `POST /org/status` по ключу организации, описание в `openapi.yaml` | | Новости дома | `post/` | Отключения и объявления УК, публикует диспетчер | | Навигация | `nav/` | Нижняя панель вкладок, как в мобильных приложениях | | Мост MAX | `bridge/` | Обёртка над `window.WebApp`: initData, платформа, ready | | Тема | `theme/` | Токены дизайн-системы MAX из `@maxhub/max-ui`, переложенные на переменные $mol | Стек: [$mol](https://mol.hyoo.ru) и MAM, [Гипербаза](https://github.com/giper-dev/gd) как локальная CRDT-база с синхронизацией, [`@maxhub/max-bot-api`](https://github.com/max-messenger/max-bot-api-client-ts) как официальный клиент Bot API MAX. Данные лежат в одном ленде УК, который создаёт бот при первом запуске. Ключ бота владеет лендом. Жителю после проверки initData выдаётся право `post`: он дописывает свои заявки, а статус читается только от ключей УК. Ленд зашифрован, читать его могут только привязанные жители и УК. ### Протокол мини-приложение ↔ бот ``` POST /auth { init_data: WebApp.initData, pass: <публичный ключ жителя> } 200 { land, house | null, user: { id, name } } 401 подпись MAX не прошла проверку или auth_date старше часа 422 тело не JSON или pass не похож на ключ ``` Токен бота видит только бот, поэтому подпись initData проверяется там. Дальше мини-приложение синхронизирует ленд напрямую с ботом по WebSocket. Адрес бота приложение берёт так: на GitHub Pages это константа `bot_prod` в `app/app.view.tree`, в локальном Docker свой origin, потому что nginx перед статикой проксирует `/auth`, `/org`, файлы и WebSocket на бота. Для отладки адрес передаётся параметром `#!bot=host:port` без схемы и слэшей, `$mol_state_arg` режет аргументы по `/`. ## Запуск ```sh cp .env.example .env # вписать BOT_TOKEN docker compose up --build ``` Мини-приложение: http://localhost:8080/, там же `/auth` и синхронизация через nginx. Бот напрямую: http://localhost:9090/. Остановка `docker compose down`, повторный запуск той же командой. Данные и ключ бота живут в томах `baza` и `state`, `docker compose down -v` стирает их вместе с лендом. ### Переменные окружения | Переменная | Назначение | | --- | --- | | `BOT_TOKEN` | Токен бота из MAX. Обязателен для уведомлений и проверки подписи | | `APP_URL` | Публичный HTTPS-адрес мини-приложения, его открывают кнопки в чате | | `BAZA_AUTH` | Приватный ключ бота. Пусто: ключ создаётся при первом запуске и хранится в томе `state` | | `DEV_SKIP_VALIDATION` | `1` отключает проверку подписи для проверки без MAX. В проде `0` | | `BOT_NAME` | Имя бота для ссылок и QR вида `https://max.ru/<имя>?start=house_<код>`. Пусто: берётся из Bot API по токену | | `STAFF_SECRET` | Секрет приглашения: ссылка `https://max.ru/<бот>?start=staff_<секрет>` делает открывшего админом. Дальше админ заводит дома и добавляет сотрудников в приложении по коду из профиля | | `UK_STAFF` | Запасной путь: ID пользователей MAX через запятую, которым сразу открыт раздел «Диспетчер». Свой ID бот сообщает по команде `/id` | | `ORG_KEYS` | Ключи организаций для API вида `uk:ключ,ads:ключ,municipal:ключ` | ### Порты | Порт | Сервис | | --- | --- | | 8080 | Мини-приложение, nginx | | 9090 | Бот: `POST /auth` и WebSocket мастера Гипербазы | ### Зависимости Версии зафиксированы в `bot/run/package.json`: `@maxhub/max-bot-api` 0.3.1, `jsdom` 29.1.1, `autoinstall` 0.3.1. Остальное тянет MAM из git по `.meta.tree` при сборке образа. ### Прод Мини-приложение живёт на GitHub Pages: https://b-on-g.github.io/max/, этот адрес и указан в боте MAX. На VPS работает только бот: поверх базового compose кладётся `docker-compose.prod.yml`, который не поднимает nginx, а бота выставляет на `127.0.0.1:8081`, и `caddy-docker-proxy` выпускает сертификат на `MAX_DOMAIN` по лейблу. Сейчас это `https://cmyser-ru-max.91.188.212.151.ip.giper.dev/`, он же прописан в `bot_prod` приложения для сборки на Pages. ```sh docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build ``` ## Внешние сервисы - Bot API MAX: long polling за обновлениями и отправка сообщений. Входящих соединений от MAX не нужно. - MAX Bridge в мини-приложении: `initData`, `platform`, `start_param`. Интеграций с ГИС ЖКХ и системами УК нет. Дома, категории и нормативы это тестовые данные, см. `seed/seed.ts`. Нормативы взяты из ПП РФ № 416, № 354 и № 170, перед пилотом их сверяет юрист УК. ## Проверка сценария Без MAX, `DEV_SKIP_VALIDATION=1`. Параметр `#!user=7` в адресе открывает приложение от имени тестового пользователя с этим ID, так проверяется раздел диспетчера, если ID есть в `UK_STAFF`: 1. Открыть http://localhost:8080/. Приложение представится тестовым жителем, экран «Мои заявки» пуст, внизу вкладки «Заявки», «Дом», «Профиль». 2. Нажать «Новая заявка», выбрать категорию, указать место, отправить. Дом берётся из профиля, где его можно сменить; ссылка с кодом дома выбирает его сама. 3. Откроется карточка: статус «Отправлена, ждём регистрации» сменится на «Зарегистрирована», появятся ответственный, сроки, основание и строка в истории. 4. В списке заявка показана с номером и сроком устранения. Без токена бот только регистрирует заявку, сообщение в чат не отправляется. 5. Вкладка «Дом» показывает заявки всех жителей этого дома с поиском по категории, месту и тексту, а на вкладке «Новости» отключения и объявления. В карточке заявки кнопка «Тоже беспокоит» добавляет голос соседа. 6. Открыть приложение как диспетчер: по ссылке-приглашению с `STAFF_SECRET`, локально `#!start=staff_<секрет>`, либо `#!user=7` при `UK_STAFF=7`. Раздел «Диспетчер» показывает все заявки со сменой статуса, QR-коды домов, приглашение коллег и форму новости. Добавить коллегу можно и по коду сотрудника из его профиля, доверие идёт цепочкой от ключа бота. 7. API организации: ```sh curl -s http://localhost:9090/org/tickets -H 'Authorization: Bearer <ключ из ORG_KEYS>' curl -s -X POST http://localhost:9090/org/status -H 'Authorization: Bearer <ключ>' \ -H 'content-type: application/json' -d '{"ticket":"","status":"done","note":"Лифт запущен"}' ``` Ответ содержит заявку с новым статусом, житель получает уведомление в чат. В MAX, `DEV_SKIP_VALIDATION=0`, `APP_URL` смотрит на публичный адрес: 1. Написать боту `/start`, нажать «Подать заявку». 2. Подать заявку, получить сообщение с номером, ответственным и сроками. 3. Повторная подача даёт следующий номер, перезапуск бота не дублирует сообщения: отметки об отправке хранятся в базе. Ожидаемое поведение при ошибках: неверная подпись даёт 401 с текстом, приложение показывает его и предлагает открыть заявку заново из MAX. ## Известные ограничения - Персонал УК задаётся списком ID в `UK_STAFF`, роли внутри УК не различаются. - Несколько домов у одного жителя не поддерживаются, дом один, но можно сменить в профиле. - Все привязанные жители УК видят заявки друг друга. Для дома это скорее плюс, но фото и описания стоит писать без личных данных. - Экран диспетчера, QR-коды на подъезды и фото к заявке идут после основного сценария. - Проверка подписи выполнена по открытым реализациям алгоритма MAX, сверить с документацией dev.max.ru перед сдачей. ## Разработка ```sh cd /path/to/mam && npm start DEV_SKIP_VALIDATION=1 node bog/max/bot/run/-/node.js port=9097 open 'http://localhost:9080/bog/max/app/-/index.html#!bot=localhost:9097' node bog/max/bot/run/-/node.test.js node bog/max/app/-/node.test.js ``` Бот пишет данные в `.baza` текущего каталога и ключ в `~/.local/share/mol_state_local`, для отладки удобно запускать его из отдельной папки с `XDG_DATA_HOME`. Скриншоты всех экранов на 1280 и 400 через headless Chrome, нужны дев-сервер и бот: ```sh node bog/max/probe/-/node.js dir=/tmp/max-shots bot=localhost:9097 ticket=<ссылка заявки> ``` Деплой мини-приложения на GitHub Pages идёт из `.github/workflows/deploy.yml` при пуше в `main`.