Skip to content

Releases: geekcodev/laravel-max-client

v1.2.1

Choose a tag to compare

@geekcodev geekcodev released this 04 Oct 09:44
938a098

v1.2.1

Пакет переведён в полосу 0000_00 — начало общего порядка миграций приложения и всех пакетов. Позиция выведена из графа
внешних ключей, а не из того, что пакет ставится первым.

Новое

Полоса 0000_00. Полоса в имени миграции — это позиция владельца в общем порядке, а не отдельный именованный
диапазон. Laravel сортирует миграции приложения и всех пакетов по имени файла и применяет их сверху вниз, поэтому
внешний ключ допустим только на таблицу, чья миграция имеет меньшее имя. Раскладка стека i2tech:

0000_00 — laravel-max-client: max_users, max_chats, max_chat_users
0000_01 — приложение, системные таблицы (users и прочие)
0000_02 — filament-max-chat
0000_03 — filament-max-broadcasts
0000_04 — приложение, доменные таблицы

Пакет занимает начало порядка, потому что на его таблицы ссылаются все: users.max_user_id указывает на
max_users.user_id (системные миграции приложения, 0000_01), max_chat_messages.max_chat_id — на max_chats
(плагин, 0000_02), max_broadcasts.created_by — на users (0000_01). Если бы max_users создавалась позже
users, внешний ключ в users упирался бы в ещё не существующую таблицу.

Документ с раскладкой полос и графом FK продублирован в docblock первой миграции пакета: тот, кто чинит порядок, сначала
читает файл, который для этого и существует.

Изменение (BC)

Переименованы все три миграции пакета:

было стало
0001_01_01_000001_create_max_users_table 0000_00_000001_create_max_users_table
0001_01_01_000002_create_max_chats_table 0000_00_000002_create_max_chats_table
0001_01_01_000003_create_max_chat_users_table 0000_00_000003_create_max_chat_users_table

Объявления таблиц не менялись. Для обновляющейся установки переименование означает, что все три миграции числятся
невыполненными при уже существующих таблицах, поэтому в up() каждой добавлен Schema::hasTable() с выходом по
return: таблицы не пересоздаются, миграции просто отмечаются выполненными. Без этого guard смена имени ломает ровно те
установки, ради которых делается, — они падают на повторном Schema::create.

Guard останавливает только пересоздание таблицы. Колонки, свёрнутые в create миграций 9570064
(max_users.phone_verified_at, метаданные чатов, индекс по chat_id), он не возвращает — их доставляет
max:upgrade: команда добавляет phone_verified_at, если его нет, и пересобирает max_chats только из реально
существующих колонок. Поэтому порядок migrate, затем max:upgrade обязателен, а migrate в одиночку оставляет схему
неполной.

Порядок обновления не изменился: php artisan migrate, затем php artisan max:upgrade. Вторая команда на чистой базе
остаётся no-op.

В таблице migrations строки со старыми именами остаются как неиспользуемые: на migrate они не влияют, но при желании
их можно удалить вручную.

Если до v1.1.4 миграции пакета публиковались в приложение, копии лежат под старыми именами и перекрывают файлы пакета:
Migrator::getMigrationFiles() собирает файлы через keyBy() по имени, и путь приложения идёт позже пути пакета. Копии
нужно удалить — порядок описан в README.md, раздел «Если миграции пакета публиковались раньше».

Затронутые сценарии

  • Чистая установка: php artisan migrate создаёт max_users, max_chats, max_chat_users до того, как приложение
    создаст users и начнёт ссылаться на max_users.
  • Обновление с v1.2.0: три миграции применяются повторно, каждая выходит по Schema::hasTable(), данные и схема не
    меняются.
  • Обновление с v1.1.x: сначала php artisan migrate, затем php artisan max:upgrade — порядок обязателен, между шагами
    схема неполна.
  • Обновление с v1.2.1: ничего не делает, второй прогон — no-op.

Качество

  • tests/Feature/MaxMigrationsTest.php: полоса 0000_00 (guard по исходникам всех файлов каталога), знаковость
    идентификаторов, отсутствие ad-hoc миграций. На SQLite нарушение порядка не воспроизводится — таблица с внешним ключом
    на несуществующую таблицу создаётся молча, — поэтому проверка идёт по именам файлов, а не по результату
    migrate.
  • tests/Feature/MaxSchemaUpgradeTest.php: сценарии обновления старой формы реестра, слияние дублей, идемпотентность,
    откат. Исторические имена миграций в сценариях старых установок оставлены как есть — они воспроизводят реальное
    состояние базы хоста.
  • PHPUnit, PHPStan, PHP CS Fixer — без ошибок.
  • Документация: README.md — раздел «Полоса миграций и порядок установки» и переработанный раздел «Если миграции пакета
    публиковались раньше»; AGENTS.md — правило миграций в структуре репозитория и gotchas 19–20 (порядок миграций и
    переименование выполненной миграции).

v1.2.0

Choose a tag to compare

@geekcodev geekcodev released this 01 Oct 11:15
183bacd

v1.2.0 — Реестр чатов: одна строка на чат

Релиз адаптера geekcodev/laravel-max-client поверх ядра geekcodev/max-php-client: ^1.1.8. Предыдущий тег — v1.1.4.
Версия MINOR, ломающих изменений два: они описаны в разделе «Изменение (BC)».

Новое

  • Таблица связей max_chat_users: модель Models\MaxChatUser, настройка chats.chat_users_model
    (MAX_CHAT_USERS_MODEL). Это реестр зафиксированных взаимодействий пользователя с ботом, а не состав группы или
    канала: строки появляются только из апдейтов, в которых пришёл конкретный user_id.
  • Связи моделей: MaxChat::maxUsers() и MaxChat::chatUsers(), MaxUser::chatLinks().
    MaxUser::maxChats() сохранена и теперь проходит через max_chat_users (внутри hasManyThrough).
  • Аддитивная миграция 0001_01_01_000003_create_max_chat_users_table — новая таблица.
  • Команда php artisan max:upgrade — перенос данных существующих установок: пары «чат + пользователь» уезжают
    в max_chat_users вместе со статусом и last_activity_at, дубли чата сливаются в одну строку, после чего user_id
    и суррогатный id удаляются, а chat_id становится первичным ключом. Опции: --dry-run (план без применения),
    --force (без подтверждения), --rollback (вернуть прежнюю форму). Логика — Services\MaxSchemaUpgrade,
    команда идемпотентна.
  • Схема реестров: у max_chats первичный ключ — сам chat_id из MAX (строка одна на чат, суррогатного id нет),
    у max_users — user_id. Единственный суррогатный ключ — id строки связи max_chat_users: uuid (uuid7, значение
    выдаёт модель через HasUuids). Отдельный индекс на max_chat_users.chat_id не добавляется: он полностью покрыт
    левым префиксом UNIQUE (chat_id, user_id).
  • Индексы на max_chats.status и max_chats.last_activity_at — по ним работают activeChatIds() и
    max:chats:refresh. Установленным проектам их добавляет max:upgrade, чтобы схема после обновления совпадала
    со схемой чистой установки.

Изменение (BC)

  • max_chats хранит одну строку на чат. Прежде в ней лежала строка на пару «пользователь + чат» с уникальным ключом
    (user_id, chat_id). Колонки user_id и суррогатный id удалены, первичным ключом стал chat_id.
  • Колонка max_chats.title_checked_at переименована в chat_checked_at. Отметка ставится один раз на проверку
    getChat и относится ко всем метаданным сразу (название, описание, ссылка, иконка), а не к одному названию.
    Переименование выполняет max:upgrade с сохранением значения времени (в --rollback прежнее имя возвращается,
    иначе код v1.1.4 упал бы на отсутствующей колонке). Установка v1.1.3 и старше аддитивных миграций метаданных не
    получала: пересборка читает только существующие колонки, поэтому обновление проходит, а чат попадает в очередь
    max:chats:refresh один раз.
  • Настройка интервала перепроверки переименована: chats.title_check_interval → chats.chat_check_interval,
    MAX_CHATS_TITLE_CHECK_INTERVAL → MAX_CHATS_CHAT_CHECK_INTERVAL. Прежние имена остаются рабочими: если нового
    ключа в конфиге нет вовсе (конфиг опубликован до v1.2.0), читается старый; если новая переменная окружения пуста
    (так заполнен .env.example), читается прежнее имя переменной. Явно выставленный новый ключ — в том числе 0 —
    всегда побеждает прежний, иначе переопределить старый интервал было бы нечем.
  • Суррогатный id строки max_chats удалён, а chat_id стал первичным ключом. Смена первичного ключа
    выполняется пересборкой таблицы (временная таблица в целевой форме, копирование одним insert … select,
    переименование): на месте ключ сменить нельзя — SQLite отклоняет удаление колонки ключа («cannot drop PRIMARY KEY
    column») и молча игнорирует primary() вне create, MySQL требует отдельных команд. MaxChat больше не использует
    HasUuids: $primaryKey = 'chat_id', $incrementing = false, $keyType = 'int'. У max_chat_users суррогатный
    uuid-ключ остаётся — идентификаторов от MAX у строки связи нет, поэтому вставка идёт через Eloquent: голый
    DB::table('max_chat_users')->insert() без id не сработает, у колонки нет значения по умолчанию на стороне базы.
  • MaxChat::maxUser() удалён — метод был привязан к прежней форме. Замена: $chat->maxUsers() (HasManyThrough) или
    $chat->chatUsers()->first()?->maxUser для первого пользователя.
  • max:upgrade вместо реструктурирующей миграции. Перенос данных делает команда, а не файл в таблице
    migrations: перед изменением схемы виден план (--dry-run), результат откатывается (--rollback), а у потребителя
    не появляется лишняя запись в migrations. Обновление стало двухшаговым: php artisan migrate (создаёт таблицу
    связей) и php artisan max:upgrade (переносит данные). Ручные правки схемы команда, как и прежняя её версия,
    не закрывает.
  • MaxChatProfileService::inheritKnownMetadata() удалён: с одной строкой на чат наследовать нечего.
  • Create-миграции 000001/000002 переписаны под новую схему. Для новых установок это обычные миграции; у
    установленных проектов имена 000001/000002 числятся выполненными, поэтому их форму меняет миграция 000004.
    Их форму меняет max:upgrade. У потребителей, которые копировали миграции вручную (см. README, раздел «Если миграции
    пакета публиковались раньше»), копии обязательно удаляются, иначе они перекроют файлы пакета.
  • Апдейт без объекта user больше не пишет max_users, но строка чата заводится и обновляется, а связь
    max_chat_users — только при известном user_id.

Исправления

  • bot_removed без user больше не теряется и для неизвестного чата. Прежде новая строка не заводилась, потому что
    user_id был частью уникального ключа; чат навсегда оставался active, activeChatIds() его возвращал, а
    max:chats:refresh бесконечно опрашивал getChat. Теперь строка чата создаётся по одному chat_id.
  • Метаданные чата запрашиваются один раз на чат. Проверка «есть ли заполненная строка» больше не зависит от числа
    участников: 50 участников группы дают один getChat, а не пятьдесят.
  • MaxChat::displayName() при пустом title берёт имя первого известного пользователя чата.
  • Колонки идентификаторов объявлены знаковым BIGINT. Прежде chat_id и user_id были unsigned, а
    идентификаторы групп и каналов в MAX отрицательные (владелец прислал реальный -79032376695376): на MySQL такой
    чат в реестр не попадал (Out of range value), причём SQLite знаковость игнорирует, поэтому тесты баг не видели.
    Знаковым объявлены и user_id — идентификаторы пользователей положительные, но знаковая колонка принимает всё, что
    принимала unsigned. Существующим установкам правит та же команда max:upgrade: chat_id — пересборкой,
    max_users — шагом change() на месте без переобъявления первичного ключа. Отдельной миграции ради смены типа
    не заводили. Знаковость колонок сама по себе проблему не закрывала: отрицательные идентификаторы отбрасывались
    ещё и фильтрами в MaxChatProfileService, MaxChatsRefreshCommand и MaxUserProfileService, поэтому название
    группы и канала не запрашивалось никогда (в апдейтах его нет, а getChat() — единственный источник), свежая
    отметка chat_checked_at не учитывалась, и профили участников групп и каналов не заполнялись. Фильтры убраны,
    отбрасывается только 0 — такого идентификатора в MAX не бывает.
    Тем же приёмом команда возвращает max_users.phone_verified_at, если колонки нет: её создавала
    аддитивная миграция v1.1.4, у установки v1.1.3 её не будет, а PersistMaxUserPhoneListener пишет отметку
    безусловно — без возврата запись телефона падала бы с «no such column».

Затронутые сценарии

  • Обновление существующей установки: два шага — php artisan migrate (создаёт max_chat_users) и
    php artisan max:upgrade (переносит данные). Данные реестров сохраняются: пары переносятся, дубли чатов сливаются
    по правилам «первые непустые метаданные, max(last_activity_at), min(created_at), статус removed > stopped > active». Между шагами схема неполна — max_chats ещё в прежней форме, а модель уже ждёт новую, поэтому порядок
    обязателен. Команда идемпотентна: повторный запуск на новой форме ничего не меняет.
  • Новая установка: публиковать миграции не нужно, loadMigrationsFrom подхватывает их из пакета; достаточно
    одного php artisan migrate, max:upgrade не требуется.
  • Откат max:upgrade --rollback: симметричен — он тоже пересобирает таблицу, поэтому возвращает и прежнюю форму
    (строка на пару, user_id, счётчик id в auto-increment), а не только набор колонок. user_id становится
    nullable: чат без зафиксированных пар получает строку с NULL — терять факт чата хуже, чем падать на откате. Значения
    прежних ключей как числа не восстанавливаются (они не сохранялись), но повторный запуск команды снова приводит реестр
    к новой форме. Колонка max_users.user_id сужается обратно только если в таблице нет отрицательных
    идентификаторов, — иначе шаг пропускается, потому что сузить их молча означало бы потерять данные.
  • Удаление бота из чата: чат переходит в removed независимо от наличия user в апдейте, и больше не
    опрашивается max:chats:refresh.
  • Диалог и группа: displayName() возвращает title, а при пустом title — имя собеседника или первого
    известного пользователя.
  • Профиль пользователя: MaxUserProfileService::refresh() берёт активные chat_id из max_chats, а
    пользователей — из max_chat_users.
  • Логирование не публикует персональные данные. При явно включённом logging.log_request_body тело апдейта
    с вложением-контактом содержит телефон и содержимое vCard; маскирование знало только про токены, поэтому ключи
    phone, vcf_info и vcf_phone добавлены в список секретных (A09).

Качество

  • PHP 8.4, declare(strict_types=1), PSR-12, PHPStan level max.
  • composer run lint — 0 файлов с пр...
Read more

v1.1.4

Choose a tag to compare

@geekcodev geekcodev released this 30 Sep 18:04
b8b14aa

v1.1.4 — Метаданные чатов, телефон из контакта, обновление схемы

Релиз адаптера geekcodev/laravel-max-client поверх ядра geekcodev/max-php-client: ^1.1.8. Предыдущий тег — v1.1.3.
Версия PATCH: только аддитивные изменения и исправления, за исключением двух конструкторов, перечисленных в разделе
«Изменение (BC)».

Новое

  • Метаданные чатов в max_chats: колонки title, description, link, icon_url,
    title_checked_at. Заполняются из getChat() при bot_added/bot_started и из самого апдейта при
    chat_title_changed. Один ответ getChat пишется во все записи чата, поэтому в группе на 50 участников делается один
    запрос, а не пятьдесят. Пустой ответ не затирает уже известное значение.
  • MaxChatProfileService: sync(), ensureMetadata(), inheritKnownMetadata(), refresh(),
    pendingChatIds(), fetch(), chatTypeFor(), activeChatIds(), forgetChatCache(),
    hasKnownTitle(). Настройки chats.fetch_metadata (MAX_CHATS_FETCH_METADATA, по умолчанию true) и
    chats.title_check_interval (MAX_CHATS_TITLE_CHECK_INTERVAL, 0 — проверять все чаты).
  • MaxContactService: проверка целостности contact-вложения ядром (ContactVerifier) и извлечение телефона
    (ContactPhoneExtractor, с v1.1.7). Входящего поля vcf_phone в апдейтах нет, телефон разбирается из vcf_info.
  • Телефон в max_users: поле phone_verified_at и сохранение номера из подтверждённого
    request_contact. Отдельного флага включения нет — запись заводится, если её не было, отличающийся номер
    перезаписывается (смена номера аккаунта), совпадающий обновляет только отметку проверки. Значение сохраняется как
    есть, без канонизации; телефон и vcf_info в лог не попадают.
  • php artisan max:chats:refresh [--all|--chat=ID]: дозаполнение и перепроверка метаданных чатов.
  • php artisan max:upgrade [--dry-run]: идемпотентная проверка и дополнение схемы реестров для
    рассинхронизированных или ранее опубликованных миграций. В штатном обновлении не нужен.
  • Индекс по chat_id в max_chats отдельной миграцией.
  • Additive-миграции вместо правки create-миграций: 0001_01_01_000003_add_phone_verified_at_to_max_users_table,
    0001_01_01_000004_add_chat_id_index_to_max_chats_table,
    0001_01_01_000005_add_chat_metadata_to_max_chats_table. Ранее выполненные create-миграции больше не правятся на
    месте. Миграции загружаются провайдером (loadMigrationsFrom), публикация не требуется.

Изменение (BC)

  • Ничего не удалено: сверка публичного API с тегом v1.1.3 даёт 0 удалённых методов.
  • Два конструктора изменились — документированное исключение из правила «в patch-релизе сигнатуры не меняются».
    Сохранение прежних сигнатур потребовало бы держать в PersistMaxChatListener
    зависимость ApiClient, которая больше не используется, поэтому решено оставить чистые сигнатуры и пометить классы
    как внутренние:
Класс v1.1.3 v1.1.4
Listeners\PersistMaxChatListener (Config, ApiClient) (Config, MaxChatProfileService)
Services\MaxUserProfileService (ApiClient, Config, Logger) (ApiClient, Config, Logger, MaxChatProfileService)

Оба класса собираются контейнером, и правка в MaxServiceProvider внесена для каждого по-своему:
MaxUserProfileService получает четвёртую зависимость в привязке register(), а
PersistMaxChatListener в привязке не участвует — он регистрируется как класс слушателя в boot(), поэтому
переопределять его нужно подменой привязки на MaxUpdateReceived, а не подменой класса. Кто собирает любой из двух
классов вручную, обязан передавать новый набор зависимостей.

  • Поведение bot_stopped/bot_removed исправлено (см. «Затронутые сценарии»), обратная совместимость не нарушена: для
    апдейтов с user поведение прежнее.
  • Config::usersCapturePhone() не выпускался ни в одном теге, поэтому его удаление вместе с
    MAX_USERS_CAPTURE_PHONE не является ломающим изменением.

Исправления

  • bot_removed без user больше не теряется. bot_added/bot_started/bot_stopped/bot_removed — события о
    боте в чате, а не о пользователе, поэтому user в них может отсутствовать. Прежде слушатель пропускал такой апдейт,
    чат навсегда оставался status = active, activeChatIds() продолжал его возвращать, а max:chats:refresh бесконечно
    опрашивал getChat. Теперь при userId === null строка не создаётся (user_id часть уникального ключа), но
    status, last_activity_at и chat_type
    обновляются во всех записях с этим chat_id.
  • Новая запись участника наследует метаданные чата без запроса. Проверка перед запросом отвечала на вопрос «есть ли
    хоть одна заполненная строка», а заполнение шло по всем строкам с chat_id, поэтому все участники после первого
    оставались без названия. Правило в три состояния: неизвестно нигде →
    getChat и запись всем; часть пуста → наследование без запроса; заполнены все → ничего. Существующие значения не
    затираются, title_checked_at переносится вместе с данными.
  • phone не затирается в upsertUser() и MaxUserProfileService::upsertFromMember() — там перечислены явно
    обновляемые поля.

Затронутые сценарии

  • Обновление существующей установки: достаточно php artisan migrate. Аддитивные миграции видны как невыполненные,
    данные сохраняются. php artisan max:upgrade — страховка для рассинхронизированной или ранее опубликованной схемы,
    идемпотентна.
  • Новая установка: публиковать миграции не нужно, loadMigrationsFrom подхватывает их из пакета.
  • Удаление бота из чата: чат переходит в removed независимо от наличия user в апдейте, и больше не опрашивается
    max:chats:refresh.
  • Группа с несколькими участниками: один запрос getChat на чат, метаданные у всех записей, включая записи
    участников, пришедших позже.
  • Контакт в сообщении: телефон из подтверждённого request_contact попадает в max_users.phone; неподтверждённое
    вложение игнорируется; пересланные контакты приходят файлами и в этот путь не попадают.
  • Слушатели: PersistMaxUserPhoneListener регистрируется всегда и независимо от chats.enabled. Приложения,
    полагавшиеся на отсутствие listeners для MaxUpdateReceived (например, на ручную постановку в очередь), учитывают это
    при обновлении.

Качество

  • PHP 8.4, declare(strict_types=1), PSR-12, PHPStan level max.
  • composer run lint — 0 файлов с правками (62 файла).
  • vendor/bin/phpstan analyse — 0 ошибок.
  • vendor/bin/phpunit — 298 тестов, 732 утверждения, все зелёные.
  • composer run coverage — 98.11% строк (1041/1061), 90.97% методов, порог 95%.
  • composer audit — 0 уязвимостей.
  • Guard-тесты схемы: testCreateMigrationsStayFrozen (create-миграции не правятся),
    testNewColumnsAreDeclaredInAdditiveMigrationsOnly, testChatIdIndexHasItsOwnMigration, сценарий обновления
    установленной версии с сохранением данных и rollback --step 3.
  • Проверено сравнением публичного API с тегом v1.1.3: удалённых методов 0.

v1.1.3

Choose a tag to compare

@geekcodev geekcodev released this 28 Sep 09:55
3c52ce4

v1.1.3 — Синхронизация с ядром v1.1.6 (Comments API, getUpdatesBatch)

Новое

  • Зависимость ядра: geekcodev/max-php-client повышен ^1.1.0 → ^1.1.6. Через фасад Max доступны новые методы
    ядра: Comments API (getComments, getComment, sendComment, editComment, deleteComment) и
    getUpdatesBatch (батчевое получение апдейтов, UpdatesResult).
  • chat_type из comment-апдейтов: PersistMaxChatListener дозаполняет max_chats.chat_type из
    Update::$comment->recipient->chatType (ядро с v1.1.6 присылает события comment_created/comment_edited/
    comment_removed с recipient — chat_id, chat_type, post_id). Как и для message/callback-апдейтов, статус не
    меняется и чат не создаётся; неизвестный chat_type не перетирается.
  • Guard-тест фасад ↔ ядро: MaxFacadeTest::testFacadeDocumentsEveryPublicApiClientMethodWithItsReturnType
    сверяет PHPDoc Facades\Max со всеми публичными методами ApiClient ядра (имя + тип возврата) — расхождение больше
    не проходит тесты молча при обновлении зависимости.
  • Логирование отклонённого вебхука: в max.log при 400 по невалидному payload добавлено message ошибки API рядом
    с exception и code (без payload, токена и секрета).
  • Документация синхронизирована с ядром: README.md (требование geekcodev/max-php-client ^1.1.6, заполнение
    chat_type), AGENTS.md (constraint ^1.1.6, факты Comments API и getUpdatesBatch, отсылка к guard-тесту).

Изменение (BC)

  • Нет. Сигнатуры публичного API пакета не менялись, изменён только PHPDoc фасада (новые @method).
  • Уточнены типы возврата в PHPDoc фасада по ядру v1.1.6: uploadMedia() → UploadedInfo (вместо UploadResult),
    sendAnswer() — с параметрами notification и disableLinkPreview.
  • Помечены deprecated getChats() и addChatMembers() (депозиция в ядре, удаление в v2.0.0) — методы по-прежнему
    работают, адаптер их не вызывает.

Затронутые сценарии

  • Комментарии к постам в каналах: чтение/правка/удаление через Max::getComments(), Max::getComment(),
    Max::sendComment(), Max::editComment(), Max::deleteComment(); права бота — read_all_messages
    (токен админа канала).
  • Обработка comment_created/comment_edited/comment_removed в MaxUpdateReceived: если чат уже в реестре, у него
    проставляется chat_type; статус и состав реестра не меняются.
  • Батчевое чтение апдейтов вместо цикла Max::getUpdates().
  • Диагностика вебхука: в лог попадает сообщение ошибки API при отклонении payload.

Качество

  • PHP 8.4, PSR-12 (lint — 0 файлов с правками), PHPStan level max — 0 ошибок.
  • PHPUnit зелёные: 196 тестов / 463 assertions; покрытие строк 98.20% (655/667), порог ≥ 95%.
  • composer audit — без уязвимостей.
  • Интеграционные смоук-тесты (--group integration) не запускались: нет MAX_API_TOKEN.

v1.1.2

Choose a tag to compare

@geekcodev geekcodev released this 17 Sep 13:22
2c652dd

v1.1.2 — Профиль для диалогов (dialog_with_user)

Новое

  • Профили в диалогах: MaxUserProfileService теперь обрабатывает диалоги (chat_type = dialog), где
    getChatMembers недоступен («Method is not available for dialogs»). Для диалога источник полного профиля с аватаром —
    getChat() → dialog_with_user (собеседник); профиль пишется в max_users через тот же
    upsertFromMember(). Запись сохраняется только если собеседник входит в запрошенный список userIds.
  • Резолв типа чата (chatTypeFor()): тип берётся из реестра max_chats.chat_type; при неизвестном —
    getChat() с записью в реестр (whereNull('chat_type'), без перетирания известного значения). Работает и в
    refresh(), и в ensureAvatar() без явного chatId.
  • Кэш чатов (chatFor()): один вызов getChat() на chat_id в рамках прохода сервиса (chatCache), ошибки
    логируются в max.log (code + message, без чувствительных данных).
  • Зависимость ядра: geekcodev/max-php-client повышен ^1.0.6 → ^1.1.0 (DTO Chat с dialog_with_user,
    UserWithPhoto).
  • PHPDoc-контракт MaxChat: задокументированы @property — user_id, chat_id, status, chat_type,
    last_activity_at.

Изменение (BC)

  • Нет. Новое поведение затрагивает только диалоги; группы/каналы используют прежний путь через getChatMembers.

Затронутые сценарии

  • refresh() / ensureAvatar() для диалога: вместо getChatMembers — getChat() + dialog_with_user; для
    неизвестного chat_type тип резолвится автоматически и сохраняется в реестр чатов.

Качество

  • PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
  • PHPUnit зелёные (191 тест / 455 assertions); покрытие строк 98.20%, порог ≥ 95%.
  • composer audit — без уязвимостей.

v1.1.1

Choose a tag to compare

@geekcodev geekcodev released this 04 Sep 09:04
0834322

v1.1.1 — chat_type в реестре чатов

Новое

  • chat_type (nullable string(16)) — колонка в max_chats, тип чата: dialog / chat / channel. Каст в
    ядерный GeekCo\MaxPhpClient\Enum\ChatType.
  • Lifecycle-события (bot_added/bot_started) заполняют chat_type: isChannel → Channel, иначе — fallback
    getChat() API. При сбое значение остаётся null.
  • Входящие message/callback дозаполняют chat_type у существующего чата (whereNull), не создавая запись и не
    меняя status. SRP сохранён: реестр отслеживает добавление бота, дозаполнение — молчаливая операция.
  • Publishable-миграция: 0001_01_01_000002_create_max_chats_table.php добавлена колонка chat_type.

Изменение (BC)

  • Нет. Колонка nullable, существующие записи не затронуты.

Качество

  • PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
  • PHPUnit зелёные (187 тестов / 436 assertions); покрытие строк 99.36%, порог ≥ 95%.
  • composer audit — без уязвимостей.

v1.1.0

Choose a tag to compare

@geekcodev geekcodev released this 28 Aug 10:33
25d0b01

v1.1.0 — Профиль пользователя (аватар), рефакторинг реестра чатов

Новое

  • MaxUserProfileService (src/Services/MaxUserProfileService.php, singleton в контейнере): наполнение
    max_users полноценным профилем — avatar_url, full_avatar_url, name, description — через эндпоинт ядра
    getChatMembers (в апдейтах MAX аватар не приходит; источник истины — участники чата).
    • refresh(int|list<int>): bool — chat_id из активных max_chats; если активного чата нет — false без вызова
      API;
    • upsertFromMember(ChatMember): MaxUser — updateOrCreate по user_id, пишет profile_checked_at;
    • ensureAvatar(MaxUser, ?int $chatId = null): bool — дозаполняет аватар, если пуст; явный chatId работает без
      реестра; при уже заполненном аватаре ничего не вызывает.
    • Группировка userIds по чатам и батчинг по users.profile_batch_size (по умолчанию 50) на вызов getChatMembers.
  • Конфиг users.*: profile_from_active_chats (env MAX_USERS_PROFILE_FROM_ACTIVE_CHATS, default true) — резолв
    chat_id из активных max_chats; profile_batch_size (env MAX_USERS_PROFILE_BATCH_SIZE, default 50) — лимит
    userIds на вызов getChatMembers; profile_check_interval (env MAX_USERS_PROFILE_CHECK_INTERVAL, default 86400 —
    раз в сутки) — периодичность перепроверки профиля в ensureAvatar, сек (0 — обновлять только при пустом аватаре).
  • max_users: колонки phone, email, profile_checked_at (nullable timestamp синхронизации профиля).

Изменение (BC)

  • Реестр чатов переименован: класс BotChat → MaxChat (таблица max_chats), enum
    ChatStatus → MaxChatStatus, слушатель PersistChatListener → PersistMaxChatListener, связь
    MaxUser::chats() → MaxUser::maxChats(). Уже задеплоенным БД нужна отдельная миграция на rename.
  • Публичный контракт конфигурации сохранён: ключи chats.*, env MAX_CHATS_*, Support\Config::chatsModel() не
    менялись. Модели переопределяются через MAX_CHATS_MODEL/MAX_USERS_MODEL.

Затронутые сценарии

  • Профиль с аватаром подтягивается «по требованию»: RefreshDatabase-совместимый вызов refresh()/ensureAvatar()
    из приложения после того, как бот добавлен в чат. Заполнение аватара прямо при апдейтах намеренно НЕ добавлено
    (экономим вызовы API под Rate limit) — основной путь по требованию.

Качество

  • PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
  • PHPUnit зелёные (166 тестов / 397 assertions); покрытие строк 99.48%, порог ≥ 95%.
  • composer audit — без уязвимостей.

v1.0.9

Choose a tag to compare

@geekcodev geekcodev released this 19 Aug 08:30
83ce71e

v1.0.9 — last_activity_at в max_bot_chats

Новое

  • last_activity_at в max_bot_chats: nullable timestamp последней активности в чате, обновляется при каждом
    апдейте (bot_added/bot_started/bot_stopped/bot_removed). Удобно для сортировки диалогов по свежести.

Качество

  • PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
  • PHPUnit зелёные (151 тест / 343 assertions); покрытие строк 99.80%, порог ≥ 95%.
  • composer audit — без уязвимостей.

v1.0.8

Choose a tag to compare

@geekcodev geekcodev released this 18 Aug 19:26
a0563ec

v1.0.8 — Реестр пользователей max_users, max_bot_chats

Новое

  • Реестр пользователей (max_users): новая миграция и модель MaxUser с полным набором полей
    UserWithPhoto — user_id (PK), first_name, last_name, username, is_bot, last_activity_time,
    name, description, avatar_url, full_avatar_url. Все колонки с ->comment() на русском.
  • Listener PersistBotChatListener при chats.enabled=true автоматически upsert запись в max_users
    при получении bot_added/bot_started/bot_stopped/bot_removed с объектом user.
  • Связь BotChat → MaxUser через user_id (Eloquent belongsTo, без FK-constraint — гибко при отсутствии
    пользователя в max_users).
  • Конфиг: новая секция users.model (env MAX_USERS_MODEL) для переопределения модели MaxUser.

Изменение (BC)

  • Таблица bot_chats переименована в max_bot_chats. Потребителям необходимо выполнить
    php artisan vendor:publish --tag=laravel-max-client-migrations --force и php artisan migrate.
  • Миграции переименованы в 0001_01_01_00000x (Laravel publishable convention) — повторная публикация перезапишет
    старые файлы миграций.

Затронутые сценарии

  • При добавлении/запуске бота в чат — пользователь автоматически сохраняется в max_users с данными из MAX API. Поля
    description/avatar_url/full_avatar_url заполняются при наличии UserWithPhoto
    (webhook отдаёт User — поля доступны для заполнения через API getChatMembers/getBotMembership).
  • Связь BotChat::maxUser работает напрямую по user_id — без дополнительных join или подзапросов.

Качество

  • PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
  • PHPUnit зелёные (149 тестов / 340 assertions); покрытие строк 99.79%, порог ≥ 95%.
  • composer audit — без уязвимостей.

v1.0.7

Choose a tag to compare

@geekcodev geekcodev released this 15 Aug 19:03
0c11dc9

v1.0.7 — Ядро ^1.0.6: глобальный rate limit 30 rps, sendAnswer без message, deprecated-права админов, алиас max.log

Зависимости

  • Ядро geekcodev/max-php-client поднято до ^1.0.6. В v1.0.6 ядра:
    • ApiClient::create() получил опцию global_rate_limiter (по умолчанию RateLimiter(30, 30)) — глобальный
      предохранитель 30 req/s на весь API, применяется к каждому запросу (включая ретраи и загрузку медиа), при
      исчерпании ядро ждёт пополнения, а не бросает исключение;
    • sendAnswer() без message шлёт тело {} вместо пустого тела (раньше API отвечал 400 Empty request body);
    • ChatAdminPermission получил deprecated-значения post_edit_delete_message/edit_message/delete_message —
      теперь getBotMembership()/getChatAdmins()/getChatMembers() парсят чаты со старыми правами; addChatAdmin()
      выдачу таких прав отклоняет (InvalidArgumentException) и получил параметр ?int $marker;
    • getChatAdmins() больше не шлёт query marker/count (их нет в спеке; параметры deprecated и игнорируются);
    • join_time — миллисекунды (документировано в спеке).

Новое

  • Глобальный rate limit выведен в конфиг адаптера: блок global_rate_limit (env MAX_GLOBAL_RATE_LIMIT_*, дефолт
    30/30 rps). MaxServiceProvider передаёт его в ApiClient::create(globalRateLimiter: ...).
  • PHPDoc фасада Max синхронизирован с ядром v1.0.6: addChatAdmin(..., ?int $marker = null).

Изменение (BC)

  • Алиас middleware логирования переименован max_bot.log → max.log (единый нейминг с max.webapp/max.csp).
    Потребителям, использовавшим max_bot.log в middleware роутов, нужно обновить роуты на max.log. Класс
    LogMaxRequestsMiddleware и конфиг/MAX_LOGGING_* не менялись.

Документация

  • README.md (Требования ^1.0.6, таблица env MAX_GLOBAL_RATE_LIMIT_*, сигнатуры фасада), AGENTS.md (constraints,
    факт 30 rps, global_rate_limit.*) и .env.example синхронизированы с ядром v1.0.6 и новым алиасом max.log.
  • UPGRADE_PLAN.md обновлён (состояние, история прецедентов v1.0.6).

Затронутые сценарии

  • Ответ на callback без обновления сообщения (кнопка обработана, меню не меняется): sendAnswer теперь работает
    (200 вместо 400 Empty request body) — ядро, правки в адаптере не требовались.
  • Чтение администраторов/участников на чатах со старыми правами: больше не падает с InvalidResponseException.
  • Интенсивная работа с API: глобальный предохранитель 30 rps защищает от rate limit независимо от числа диалогов.

Качество

  • PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
  • PHPUnit зелёные (140 тестов / 314 assertions); покрытие строк ≥95%.
  • composer audit — без уязвимостей.