Appearance
Chat Backend — Доработки для полноты функционала
Статус: все 5 доработок реализованы (2026-02-12)
Бэкенд чата работает, основа отличная. Ниже — список доработок, которые нужны фронтенду для полноценного UX. Каждый пункт — отдельная задача с примером ожидаемого результата.
1. Добавить image_url товара в Conversation
Проблема: Ответ GET /vendor/conversations и POST /vendor/conversations возвращает product: { id, title, price, status } — нет фото товара. Без фото карточка диалога выглядит бедно. У Avito в каждом чате есть миниатюра товара.
Ожидаемый ответ:
json
{
"data": {
"id": 5,
"product": {
"id": 42,
"title": "Тормозные колодки передние",
"price": 2500,
"status": "active",
"image_url": "https://s3.partizap.ru/products/42/thumb_abc123.webp"
},
"companion": { ... },
"last_message": { ... },
"unread_count": 0
}
}Что нужно: при JOIN на products — взять primary image thumbnail (поле thumbnail_webp из product_images где is_primary = true). Если нет фото — null.
Приоритет: Высокий (влияет на весь UI списка чатов).
2. Добавить статус delivered для сообщений
Проблема: Сейчас статусы: sent → read. Нет промежуточного состояния «доставлено» (сообщение достигло устройства получателя, но ещё не прочитано).
Зачем: Стандарт мессенджеров (WhatsApp, Telegram, VK). Покупатель видит:
- ✓ — отправлено (сервер принял)
- ✓✓ — доставлено (дошло до получателя)
- ✓✓ (синие) — прочитано
Без delivered покупатель не понимает: сообщение застряло или продавец просто не открыл чат?
Предложение по реализации:
Вариант A: Фронт подтверждает доставку (рекомендуемый)
- Фронт получателя получает
message.newчерез Centrifugo - Фронт отправляет
POST /vendor/conversations/{id}/messages/{messageId}/delivered - Бэкенд обновляет
messages.status = 'delivered' - Бэкенд публикует событие отправителю:
json
{
"type": "message.delivered",
"data": {
"conversation_id": 123,
"message_id": 456
}
}Вариант B: Batch delivery confirmation
Чтобы не слать по запросу на каждое сообщение — фронт подтверждает последний полученный message_id:
POST /vendor/conversations/{id}/delivered
{ "last_message_id": 456 }Бэкенд обновляет все sent сообщения до этого ID → delivered.
Итоговая цепочка статусов: sent → delivered → read
Обновить Zod-тип: messageStatusSchema = z.enum(['sent', 'delivered', 'read'])
Приоритет: Средний (UX улучшение, но не блокирует базовый функционал).
3. Добавить тип сообщений system
Проблема: Сейчас типы: text | image. Нет системных сообщений.
Зачем: Покупатель ведёт переговоры, а товар уже продан. Без системного сообщения в чате он об этом не узнает.
Сценарии:
- Товар продан →
"Товар «Фара левая BMW E46» отмечен как проданный" - Цена изменена →
"Продавец изменил цену: 5 000 ₽ → 4 500 ₽" - Товар удалён →
"Объявление удалено продавцом" - Товар снова активен →
"Объявление снова доступно"
Ожидаемое сообщение:
json
{
"id": 789,
"conversation_id": 123,
"sender_id": null,
"type": "system",
"text": "Товар «Фара левая BMW E46» отмечен как проданный",
"image_url": null,
"image_thumbnail": null,
"status": "sent",
"created_at": "2026-02-11T20:00:00+00:00"
}Что нужно:
- Добавить
'system'в enum типов сообщений - Разрешить
sender_id: nullдля системных сообщений - При смене статуса товара (sold, archived, deleted, reactivated) — создать системное сообщение во всех активных conversations по этому товару
- Публиковать
message.newобоим участникам через Centrifugo
Триггеры на бэке:
PUT /vendor/products/{id}(если меняется price) → system messagePOST /vendor/products/{id}/publish→ нет (не нужно)- Товар переходит в
sold/archived/rejected→ system message DELETE /vendor/products/{id}→ system message
Приоритет: Средний (важно для UX, но не блокирует запуск чата).
4. Добавить last_read_message_id в событие message.read
Проблема: Сейчас событие:
json
{
"type": "message.read",
"data": {
"conversation_id": 123,
"reader_id": 7
}
}Нет информации о том, до какого сообщения прочитано. Фронт вынужден пометить ВСЕ свои сообщения как read, даже если новые пришли после момента прочтения.
Ожидаемое событие:
json
{
"type": "message.read",
"data": {
"conversation_id": 123,
"reader_id": 7,
"last_read_message_id": 456
}
}Фронт пометит read только сообщения с id <= 456. Сообщения отправленные позже останутся sent/delivered.
Что нужно: при POST /conversations/{id}/read — запомнить ID последнего сообщения собеседника и включить его в Centrifugo event.
Приоритет: Низкий (текущий вариант работает для 90% случаев, но edge cases возможны).
5. Realtime online-статус через Centrifugo
Проблема: Сейчас GET /vendor/users/{id}/online — REST запрос с 5-минутным окном. Проблемы:
- Фронт видит статус только при открытии чата (одноразовый GET)
- Нет realtime обновления: не увидим как собеседник «появился в сети» пока мы в чате
- Для обновления нужен polling каждые N секунд → лишние запросы к PHP
Предложение: при изменении online-статуса (подключение/отключение Centrifugo) — публиковать событие в персональные каналы собеседников всех активных диалогов.
Ожидаемое событие:
json
{
"type": "presence",
"data": {
"user_id": 7,
"online": true,
"last_seen_at": null
}
}json
{
"type": "presence",
"data": {
"user_id": 7,
"online": false,
"last_seen_at": "2026-02-11T20:15:00+00:00"
}
}Как реализовать:
Centrifugo поддерживает connect/disconnect proxy. При подключении/отключении клиента Centrifugo может вызывать PHP webhook:
POST /api/centrifugo/connect → уже есть (валидация сессии)
POST /api/centrifugo/disconnect → добавитьВ disconnect handler:
- Обновить
last_seen_atв Redis/БД - Найти все активные conversations пользователя
- Опубликовать
presence { online: false }вpersonal:#companionIdдля каждого собеседника
В connect handler (существующий):
- После успешной валидации — опубликовать
presence { online: true }собеседникам
Альтернатива (проще): оставить REST endpoint, но фронт будет polling раз в 30с пока чат открыт. Менее элегантно, но рабочий вариант.
Приоритет: Низкий (REST + polling работает, realtime через Centrifugo — nice-to-have).
Сводная таблица
| # | Доработка | Приоритет | Статус |
|---|---|---|---|
| 1 | product.image_url в conversations | Высокий | Готово |
| 2 | Статус delivered | Средний | Готово |
| 3 | Тип system сообщений | Средний | Готово |
| 4 | last_read_message_id в message.read | Низкий | Готово |
| 5 | Realtime presence через Centrifugo | Низкий | Готово |
Что работает и не требует изменений
- Все REST endpoints (conversations, messages, read, typing, unread-count)
- Connect Proxy (авторизация через куки)
- Centrifugo events (message.new, typing, message.read, unread.update)
- Image upload с EXIF stripping и thumbnails
- Cursor pagination
- Soft delete с автовосстановлением
- Online middleware (Redis TTL 5 мин)
- Rate limiting