# Changelog

Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
проект придерживается [Semantic Versioning](https://semver.org/lang/ru/).

## [2.0.2] «Kronos» — 2026-10-10

Диагностика окружения и защита от неполной конфигурации PHP.

### Исправлено
- **Понятная ошибка вместо фатальной.** `UpdateService::makeZip()` и
  `extract()` создавали `ZipArchive` без проверки наличия расширения `zip`.
  При отключённом расширении автообновление падало с
  `Error: Class "ZipArchive" not found` вместо внятного сообщения. Теперь
  обе функции проверяют `class_exists(ZipArchive::class)` — как это уже было
  сделано в `BackupService`. Поведение подтверждено запуском с выключенным zip.

### Изменено
- Требования к окружению приведены к фактическим: `doctor` сообщает о
  недостающих расширениях раньше, чем это выяснится при первом обновлении.

[2.0.2]: https://github.com/paulmann/BAS-IP/releases/tag/v2.0.2
## [2.0.1] «Kronos» — 2026-10-10

Совместимость с PHP 8.5: устранены вызовы, объявленные устаревшими в 8.5.
До этого исправления приложение на PHP 8.5 было частично неработоспособным —
обработчик ошибок превращает `E_DEPRECATED` в исключение, поэтому каждое
такое место приводило к HTTP 500.

### Исправлено
- **HTTP-слой полностью отказывал на PHP 8.5**: `curl_close()` объявлен
  устаревшим в 8.5 (с 8.0 он ничего не делает) и бросал исключение — падали
  проверка обновлений, уведомления, работа с панелью BAS-IP и Telegram.
  Вызов убран: дескриптор `CurlHandle` освобождается сборщиком мусора.
- **`/admin` отдавал HTTP 500**: обращение к массиву по ключу `null`
  (`['debtors' => …][$badgeKey]`) объявлено устаревшим — депрекейшен возникал
  на каждом пункте меню без бейджа.
- **Установка на SQLite под Windows была невозможна**: путь вида
  `C:\…\base.sqlite` считался относительным и превращался в
  `C:\app\C:\…\base.sqlite`, установщик падал на создании каталога.
  Теперь распознаются POSIX-пути, пути с буквой диска и UNC-пути.

### Проверено
- 217 тестов / 930 проверок — зелёные на PHP 8.5 (SQLite).
- Проверка обновлений реально обращается к GitHub API: релиз читается,
  ассет `.sha256` распознаётся, сравнение версий работает.

### Требования
- Расширение `zip` обязательно (распаковка обновлений); health-проверка
  сообщает `degraded`, если оно отключено.

[2.0.1]: https://github.com/paulmann/BAS-IP/releases/tag/v2.0.1
## [2.0.0] «Kronos» — 2026-10-10

Полный рефакторинг v1 (один файл `bas-ip.php`) в самостоятельное приложение.
Код v1 сохранён в ветке `main` и в истории git.

### Добавлено

**Платформа**
- Собственное микро-ядро без фреймворков: DI-контейнер, роутер с группами и
  middleware, request/response, шаблонизатор, логгер с каналами и request-id,
  файловый кэш, rate-limiter, события.
- Три СУБД из коробки: MySQL 8 / MariaDB 10.6+, PostgreSQL 13+, SQLite 3 —
  общий миграционный движок и диалектный слой запросов.
- Конфигурация: `config/config.php` (дефолты) + `.bas-ip.config.local.php`
  (локальные значения и секреты; файл в `.gitignore` — настройки не теряются
  при обновлении из git) + `.env`; шифрованное хранилище секретов
  (`secret:set`, `${secret:NAME}`).
- Веб-установщик с проверкой требований, мастером БД (кнопка «Проверить
  соединение» — до установки, с человеческой диагностикой и готовым
  `CREATE DATABASE`), проверкой панели (реальный handshake BAS-IP),
  автогенерацией пароля администратора, сбором телефона, автологином после
  установки и подробным машиночитаемым отчётом об ошибках (копирование/
  скачивание, пароли маскированы — удобно для анализа ИИ); самоблокировка
  через `public/install.lock`.
- Консоль `bin/bas-ip`: 71 команда (миграции, users, billing, gate, guests,
  residents import/export, backups, queue, scheduler, doctor, updates, secrets).
- Автообновление из GitHub-репозитория: каналы stable/beta, проверка контрольных
  сумм, бэкап файлов и БД перед установкой, режим обслуживания, откат
  (`update:rollback`, снимки версий), preserve-список (конфиг и storage не
  перезаписываются).

**Аутентификация и права**
- Вход по логину/паролю, 2FA (TOTP, совместим с Google Authenticator),
  «запомнить меня» (ротация cookie), сброс пароля по e-mail, блокировка после
  серии неудач, журнал входов, управление сессиями (список, отзыв, «выйти
  везде»), принудительная смена пароля.
- Роли `admin` / `operator` / `resident` и 40+ точечных прав
  (`residents.edit`, `billing.payment.record`, `gate.open`, …).

**Жители и данные**
- Карточка жителя: ФИО, несколько телефонов и e-mail, квартира/корпус/площадь/
  число жителей/доля, машины (госномер + марка, несколько на жителя),
  комментарии, чекбоксы «доступ», «включен», «участвует в оплатах»,
  «гостевой доступ разрешён», «уведомления».
- По умолчанию показываются только активные жители; все расчёты и массовые
  действия учитывают только активных.
- Мягкое удаление с восстановлением, поиск по всему (имя, квартира, телефон,
  e-mail, госномер), массовые операции, история изменений по каждой карточке.
- Импорт из CSV/JSON/Excel с инспекцией файла и маппингом колонок, экспорт
  жителей/оплат/журнала, образцы файлов.
- Бэкапы БД (mysqldump / pg_dump / sqlite) с ротацией и восстановлением из UI.

**Биллинг**
- Начисление: общая сумма ÷ активные жители — поровну, по площади, по числу
  жителей или по доле; расщепление до копейки без потерь.
- Счета с номерами, сроками оплаты, статусами (pending/partial/paid/overdue/void),
  оплаты с FIFO-распределением по старейшим счетам, переплата на балансе.
- Перерасчёт начисления при смене суммы или числа участников: дополнительные
  счета и сторно, уже выставленные счета не изменяются.
- Ссылка на оплату с подстановками (`{id}`, `{apartment}`, `{sum}`, `{period}`),
  должники, напоминания, автопометка просрочки (cron).

**Шлагбаум и гостевой доступ**
- Драйверы: BAS-IP Camdroid `/api/v0` (токен устройства с автообновлением),
  HTTP-реле (произвольный endpoint), dry-run эмулятор; несколько устройств.
- Камера привязана к шлагбауму: настройка в карточке устройства (режим
  snapshot/proxy/mjpeg, endpoint панели, URL потока, кэш, ширина); прямой
  поток автоматически разрешается в CSP; proxy-режим транслирует MJPEG через
  сервер (pass-through потока или синтез из кадров панели).
- Пульт жителя `/remote`: полноэкранная гигантская кнопка открытия (~95%
  экрана), живая камера выбранного шлагбаума с переключателем, cooldown с
  обратным отсчётом, полноэкранный отклик + вибрация, Wake Lock; список
  камер — `GET /panel/cameras`.
- Вход по номеру телефона в любом написании (E.164-нормализация): учётка
  жителя создаётся одной кнопкой — логин = цифры телефона, пароль
  генерируется и показывается один раз, при первом входе обязательная смена.
- Панель оператора: открытие/закрытие, статус в реальном времени, снимок камеры
  (snapshot / proxy / MJPEG), SIP вкл/выкл, перезагрузка панели.
- Каждое открывание логируется: кто, когда, тип доступа (свой / гостевой / API /
  ручной / аварийный), квартира, устройство, результат, снимок.
- Гостевой доступ: по умолчанию 12 часов (пресеты 1–72), подписанная ссылка + QR +
  короткий код без неоднозначных глифов + PIN для панели, лимиты активных
  пропусков, авто-истечение, полный журнал.
- Расписания доступа и «тихие часы».

**Уведомления**
- Каналы: log, SMTP (свой клиент, без зависимостей), Telegram-бот, SMS,
  WhatsApp, webhook; очередь с ретраями и экспоненциальной задержкой.
- Шаблоны с подстановками и модификаторами (`{amount|money}`, `{due_date|date:d.m.Y}`),
  автогенерация писем об оплате, массовые рассылки по начислению, объявления.

**API и интеграции**
- REST `/api/v1`: шлагбаум (status/open/close/events/snapshot), гостевые
  пропуска, жители, оплаты, статистика, уведомления; токены с областями доступа
  и сроком действия; интерактивная документация `/api/v1/docs`.
- Совместимость с v1: `bas_ip.php?cmd=…` и `get_last_image.php` перенаправляются
  на новый API (теперь с аутентификацией и аудитом).
- `/health`, `/health.json`, `/metrics` (Prometheus), Telegram-бот
  (open/status/guest/balance), Home Assistant, webhook-и.

**Интерфейс**
- Tailwind CSS + дизайн-токены shadcn/ui, light/dark тема: переключатель,
  сохранение выбора, автоопределение по системе при первом входе.
- Mobile-first, полноценный десктоп, PWA-манифест, командная палитра,
  скелетоны загрузки, тосты, диалоги, графики, QR прямо в браузере.
- Все ассеты локальные (шрифты Inter/JetBrains Mono — woff2 latin+cyrillic,
  иконки Lucide — единый SVG-спрайт, qrcode-generator) — ни одного CDN.
- Встроенная справка `/help`: 36 разделов с поиском.

**Безопасность**
- CSP с nonce, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`,
  `Permissions-Policy`, HSTS, `Cache-Control: no-store` для динамики.
- CSRF на все состояния-меняющие запросы, rate-limit (логин, API, открытие
  ворот, гостевые ссылки, сброс пароля), hardened-сессии (HttpOnly, SameSite,
  fingerprint, idle timeout, absolute lifetime), IP-allowlist для админки/API.
- Шифрование секретов at-rest (AES-256-GCM), маскирование чувствительных полей
  в логах, аудит-лог с экспортом, журнал входов и сессий.

### Изменено
- Документ-рут перенесён в `public/`; корень проекта закрыт `.htaccess`.
- v1-эндпоинты требуют аутентификацию и пишут аудит (в v1 токен лежал в файле
  `.api_token` рядом со скриптом).

### Исправлено (в ходе финализации 2.0.0)
- Утечка персональных данных в рассылках: `notifyCharge` гидратировал жителя из
  join-строки, где `invoices.*` затенял `residents.id` — письма начислений
  уходили соседям. Получатели загружаются отдельным батч-запросом
  (+ регрессионный тест).
- Гостевое открытие шлагбаума падало с 500: `VerifyCsrf` не понимал
  `csrf_except => true` (bool) и падал в `str_ends_with(bool)`.
- API-токены с точечными областями получали 403: `scopes` маршрута
  интерпретировались как «все обязательны» вместо «любая из».
- Security-заголовки (`X-Frame-Options`, `X-Request-Id`, `no-store`) не
  попадали на ответы, сформированные вне `run()` (error-страницы, тесты,
  вложенные вызовы `handle()`).
- `App::handle()` не был ре-ентерабелен: мемоизация Guard и request-scoped
  ключи контейнера протекали между запросами (тесты, очереди).
- Веб-установщик падал на «Не удалось подключиться к базе данных»: контейнер
  строил `Database` до записи локального конфига (добавлен `Database::reload()`).
- CLI тихо игнорировал неизвестные опции (`--sum` вместо `--total` приводило к
  «успешному» запуску с дефолтами) — теперь ошибка с подсказкой.
- `--period=2026-10` не распознавался (понимал только `10.2026`) и молча
  превращался в прошлый месяц; добавлены ISO-формат, русские названия месяцев
  и нормализация метки периода в счетах/письмах.
- Дубли «750 ₽ RUB» в письмах и повисшие строки «Оплатить:» без ссылки.
- Устаревшая сборка `public/assets/css/app.css` (не хватало ~2/3 используемых
  утилит и стилей tooltip); спрайт иконок расходился с манифестом (11 имён
  рендерились пустыми); отсутствовали скрипты сборки `tools/build-*.mjs`.
- Страницы, объявлявшие раскладку ключом `'layout'` в данных контроллера
  (вход, 2FA, восстановление пароля, API-доки), рендерились **без layout и
  стилей**: `View::render` не потреблял этот ключ. Добавлен единый
  data-driven механизм + передача `$title` в раскладку — заголовки вкладок
  появились на всех страницах.
- Веб-установщик с MySQL: имя базы превращалось в абсолютный путь
  (`absolutePath` применялся ко всем драйверам вместо только SQLite) —
  установка через мастер на MySQL была невозможна.
- MariaDB: collation `utf8mb4_0900_ai_ci` (дефолт MySQL 8) автоматически
  понижается до `utf8mb4_unicode_ci` (сессия и DDL) — миграции больше не
  падают с «Unknown collation».
- Режим обслуживания запирал жителей во дворе: allowlist содержал
  несуществующий маршрут; теперь все gate-маршруты доступны (права
  по-прежнему проверяются), + регрессионный тест.
- В репозитории были закоммичены секреты и артефакты: `.bas-ip.config.local.php`,
  боевой API-токен v1, бэкапы/экспорты/логи, rate-limit-кэш — всё изъято из git.
- `View::asset()` брал filemtime несуществующего пути — cache-busting `?v=`
  не работал.
- Пустой `$title` давал « · App» в заголовке страниц аутентификации.
- Разнобой минимальной версии PHP (8.2/8.3) — едино: минимум 8.2, рекомендуется 8.5.

[2.0.0]: https://github.com/paulmann/BAS-IP/releases/tag/v2.0.0
