Код chat service находится в chat_around.
around_chat - отдельный Go-сервис для 1:1 чатов. Он работает рядом с Django, но использует общие PostgreSQL, Redis и JWT secret. Django отвечает за пользователей и выпуск JWT, chat service отвечает за таблицы chats, chat_messages, HTTP API, WebSocket и Redis fan-out.
HTTP_ADDR=:8080POSTGRES_DSN или POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORDREDIS_URL или VALKEY_LOCATION + VALKEY_PASSWORDDJANGO_SECRET_KEY или JWT_SECRET должен совпадать с Django signing key.JWT_ALGORITHM=HS256REDIS_CHAT_CHANNEL=chat:eventsWS_ALLOWED_ORIGINS должен включать frontend/domains.cmd/api/main.go загружает config.chat.EnsureSchema() создает таблицы и индексы, если их нет:
chatschat_messageschats.last_message_id -> chat_messages.idREDIS_CHAT_CHANNEL.Все HTTP endpoints, кроме health, требуют Authorization: Bearer <access-token>.
Endpoints:
GET /healthzGET /api/v1/chatsPOST /api/v1/chatsGET /api/v1/chats/:chatID/messagesAuth:
AuthMiddleware извлекает Bearer token.JWTVerifier.VerifyToken() проверяет алгоритм HS256 и подпись.token_type должен быть access.user_id кладется в Gin context.List chats:
GET /api/v1/chats.Service.ListChats() вызывает Repository.ListChats(userID).chats, где user является user_1_id или user_2_id.updated_at DESC, id DESC.{ "items": [...] }.Create direct chat:
POST /api/v1/chats с { "peer_user_id": <id> }.Service.CreateChat() запрещает чат с самим собой.is_active=true в authentication_user.Repository.GetOrCreateChat() делает INSERT ... ON CONFLICT.chat.created в Redis.201, если создан новый чат;200, если вернулся существующий.List messages:
GET /api/v1/chats/:chatID/messages.limit, default 50, max 200;before_id для пагинации назад.ORDER BY id DESC.{ "items": [...] }.Endpoint: GET /ws/chats.
Auth:
Authorization: Bearer <access-token>.token=<access-token>.token_type=access, user_id.WS_ALLOWED_ORIGINS; если список пустой, origin разрешается.Connection lifecycle:
Client{UserID, Conn, Send}.Hub.last_event_id, service делает replay сообщений после этого id. Событие sync.replayed отправляется только если найдены сообщения после указанного id.WebSocket command envelope:
{
"type": "message.send",
"request_id": "client-request-id",
"payload": {}
}
Commands:
ping -> pong.chat.create с { "peer_user_id": 123 } -> chat.create.accepted.message.send с { "chat_id": 1, "client_message_id": "...", "body": "..." } -> message.send.accepted.message.ack с { "message_id": 1 } -> message.ack.accepted.message.read с { "message_id": 1 } -> message.read.accepted.Message send:
message.send требует непустые client_message_id и body.INSERT ... ON CONFLICT (chat_id, sender_id, client_message_id).chats.last_message_id и chats.updated_at.message.sent в Redis для обоих участников.Ack/read:
message.ack ставит delivered_at, только если текущий пользователь не sender.message.read ставит delivered_at и read_at, тоже только для получателя.message.delivered, message.read.chat:events.type, список users, payload, created_at.Hub.PublishToUsers(users, event).last_event_id.Handler.sendReplay() вызывает Service.ReplayMessages(userID, afterID, 200).m.id > last_event_id из чатов пользователя.last_event_id, client получает событие:{
"type": "sync.replayed",
"payload": {
"items": []
}
}
django_around/around/chat_bridge связывает Django users и chat service:
ChatBridgeConfig.ready() регистрирует signal handler.User signal post_save вызывает SupportChatService.ensure_support_chat_for_user(user).SUPPORT_USER_PHONESUPPORT_USER_FIRST_NAMESUPPORT_USER_LAST_NAMEchats уже есть, Django добавляет колонку is_support, если ее нет.INSERT ... ON CONFLICT ... DO UPDATE SET is_support=TRUE.chats еще нет, soft path пишет warning и пропускает создание.cd django_around/around
python manage.py ensure_support_chats
Флаг --soft не падает при отсутствующей таблице chats или ошибке insert.
Production nginx проксирует:
/ws/chats -> around_chat:8080/api/v1/chats -> around_chat:8080Остальной /api/v1/* идет в Django around_back.
Base URLs и local/prod ports описаны в Environment and runtime.
Django endpoint /api/schema/ добавляет chat paths через chat_bridge.openapi.CHAT_OPENAPI_FRAGMENT.
Документируются:
/api/v1/chats/api/v1/chats/{chatID}/messages/ws/chats/api/v1/uploads/photosТекущее расхождение: /api/v1/uploads/photos documented-but-not-implemented. В коде chat_around/internal/httpapi/handlers.go не найден handler для этого endpoint; он есть в OpenAPI fragment, но не подключен в Go router.
Также OpenAPI fragment описывает расширенный ChatSummary, а текущий Go handler возвращает базовые chat rows из Repository.ListChats(). Оба пункта ведутся в Known drift.
chats:
iduser_1_iduser_2_idlast_message_idcreated_atupdated_atDjango chat_bridge может добавить колонку is_support, если таблица chats уже создана.
chat_messages:
idchat_idsender_idclient_message_idbodycreated_atdelivered_atread_at(chat_id, sender_id, client_message_id).GET /healthz отвечает {"status":"ok"}.GET /api/v1/chats с Django access token возвращает items.POST /api/v1/chats создает или возвращает existing chat./ws/chats принимает access token и отвечает pong на ping.message.sent на второй WebSocket client.POST /api/v1/uploads/photos не считается рабочим route до исправления known drift.python manage.py ensure_support_chats --soft не падает и создает support chats после старта Go service.Пошаговый QA smoke: QA local smoke.