From c98667c5b55a449d13156627abf0d033f2576a3f Mon Sep 17 00:00:00 2001 From: madipo2611 Date: Mon, 11 Aug 2025 18:37:52 +0300 Subject: [PATCH] v0.0.17.4 --- internal/http/graph/message_resolvers.go | 49 ++--- internal/http/handlers/chat.go | 259 +++++++++++++++-------- internal/service/chat_service.go | 4 + 3 files changed, 193 insertions(+), 119 deletions(-) diff --git a/internal/http/graph/message_resolvers.go b/internal/http/graph/message_resolvers.go index f947076..d2097ab 100644 --- a/internal/http/graph/message_resolvers.go +++ b/internal/http/graph/message_resolvers.go @@ -107,44 +107,29 @@ func (r *messageResolver) Sender(ctx context.Context, obj *domain.Message) (*dom return user, nil } -// Receiver - возвращает получателя сообщения func (r *messageResolver) Receiver(ctx context.Context, obj *domain.Message) (*domain.User, error) { - // 1. Проверка на nil - if obj == nil { - return nil, fmt.Errorf("message is nil") - } - - // 2. Если есть receiver_id - используем его напрямую + // 1. Если receiver явно указан в сообщении if obj.ReceiverID != 0 { - user, err := r.Services.User.GetByID(ctx, obj.ReceiverID) + return r.Services.User.GetByID(ctx, obj.ReceiverID) + } + + // 2. Если есть chat, определяем получателя через чат + if obj.ChatID != 0 { + chat, err := r.chatRepo.GetChatByID(ctx, obj.ChatID) if err != nil { - return nil, fmt.Errorf("failed to get receiver by ID: %v", err) + return nil, fmt.Errorf("failed to get chat: %v", err) } - return user, nil + + // Определяем получателя + receiverID := chat.User1ID + if obj.SenderID == chat.User1ID { + receiverID = chat.User2ID + } + return r.Services.User.GetByID(ctx, receiverID) } - // 3. Альтернативный вариант через chat_id (если receiver_id не установлен) - if obj.ChatID == 0 { - return nil, fmt.Errorf("both receiver_id and chat_id are not set") - } - - chat, err := r.chatRepo.GetChatByID(ctx, obj.ChatID) - if err != nil { - return nil, fmt.Errorf("failed to get chat: %v", err) - } - - // Определяем ID получателя - receiverID := chat.User1ID - if obj.SenderID == chat.User1ID { - receiverID = chat.User2ID - } - - user, err := r.Services.User.GetByID(ctx, receiverID) - if err != nil { - return nil, fmt.Errorf("failed to get receiver user: %v", err) - } - - return user, nil + // 3. Если ничего не найдено + return nil, fmt.Errorf("cannot determine receiver - both receiver_id and chat_id are not set") } // CreatedAt - форматирует время сообщения diff --git a/internal/http/handlers/chat.go b/internal/http/handlers/chat.go index 309fa10..5986fbb 100644 --- a/internal/http/handlers/chat.go +++ b/internal/http/handlers/chat.go @@ -2,6 +2,8 @@ package handlers import ( "context" + "encoding/json" + "fmt" "log" "net/http" "tailly_back_v2/internal/domain" @@ -69,114 +71,197 @@ func (h *ChatHandler) readPump(ctx context.Context, conn *websocket.Conn, client defer ticker.Stop() defer conn.Close() - // 1. Ожидаем первое сообщение с токеном - var authMsg struct { - Type string `json:"type"` - Token string `json:"token"` - } - - if err := conn.ReadJSON(&authMsg); err != nil { - log.Printf("Failed to read auth message: %v", err) + // 1. Аутентификация + if err := h.authenticateConnection(conn, client); err != nil { + log.Printf("Authentication failed: %v", err) return } - if authMsg.Type != "auth" || authMsg.Token == "" { - log.Println("First message must be auth with token") - conn.WriteJSON(map[string]string{"type": "error", "message": "Auth required"}) - return - } - - // 2. Валидируем токен - userID, err := h.tokenAuth.ValidateAccessToken(authMsg.Token) - if err != nil { - log.Printf("Token validation error: %v", err) - conn.WriteJSON(map[string]string{"type": "error", "message": "Invalid token"}) - return - } - - client.UserID = userID - log.Printf("WebSocket authenticated, userID=%d", userID) - - // 3. Отправляем подтверждение авторизации - if err := conn.WriteJSON(map[string]string{"type": "auth_success"}); err != nil { - log.Printf("Failed to send auth confirmation: %v", err) - return - } - - // 4. Основной цикл обработки сообщений + // 2. Основной цикл обработки сообщений for { select { case <-ticker.C: - if err := conn.WriteJSON(map[string]string{"type": "ping"}); err != nil { - log.Printf("Ping error: %v", err) + if err := h.sendPing(conn); err != nil { return } case <-ctx.Done(): return default: - var msg struct { - Type string `json:"type"` - Payload struct { - ReceiverID int `json:"receiverId"` - Content string `json:"content"` - } `json:"payload"` - } - - if err := conn.ReadJSON(&msg); err != nil { + if err := h.handleMessage(ctx, conn, client); err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Printf("WebSocket error: %v", err) } return } + } + } +} - switch msg.Type { - case "pong": - continue - case "ping": - conn.WriteJSON(map[string]string{"type": "pong"}) - continue - case "message": - if client.UserID == 0 { - conn.WriteJSON(map[string]string{"type": "error", "message": "Not authenticated"}) - continue +func (h *ChatHandler) authenticateConnection(conn *websocket.Conn, client *ws.Client) error { + var authMsg struct { + Type string `json:"type"` + Token string `json:"token"` + } + + // Устанавливаем таймаут для аутентификации + conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + defer conn.SetReadDeadline(time.Time{}) + + if err := conn.ReadJSON(&authMsg); err != nil { + conn.WriteJSON(map[string]string{"type": "error", "message": "Auth required"}) + return fmt.Errorf("failed to read auth message: %v", err) + } + + if authMsg.Type != "auth" || authMsg.Token == "" { + conn.WriteJSON(map[string]string{"type": "error", "message": "Invalid auth message"}) + return fmt.Errorf("first message must be auth with token") + } + + userID, err := h.tokenAuth.ValidateAccessToken(authMsg.Token) + if err != nil { + conn.WriteJSON(map[string]string{"type": "error", "message": "Invalid token"}) + return fmt.Errorf("token validation error: %v", err) + } + + client.UserID = userID + log.Printf("WebSocket authenticated, userID=%d", userID) + + // Отправляем подтверждение аутентификации + if err := conn.WriteJSON(map[string]interface{}{ + "type": "auth_success", + "user": map[string]interface{}{ + "id": userID, + }, + }); err != nil { + return fmt.Errorf("failed to send auth confirmation: %v", err) + } + + return nil +} + +func (h *ChatHandler) sendPing(conn *websocket.Conn) error { + if err := conn.WriteJSON(map[string]string{"type": "ping"}); err != nil { + log.Printf("Ping error: %v", err) + return err + } + return nil +} + +func (h *ChatHandler) handleMessage(ctx context.Context, conn *websocket.Conn, client *ws.Client) error { + var msg struct { + Type string `json:"type"` + Payload json.RawMessage `json:"payload"` // Используем RawMessage для гибкости + } + + if err := conn.ReadJSON(&msg); err != nil { + return err + } + + switch msg.Type { + case "pong": + return nil + case "ping": + return conn.WriteJSON(map[string]string{"type": "pong"}) + case "message": + return h.handleChatMessage(ctx, conn, client, msg.Payload) + default: + log.Printf("Unknown message type: %s", msg.Type) + return conn.WriteJSON(map[string]string{ + "type": "error", + "message": "Unknown message type: " + msg.Type, + }) + } +} + +func (h *ChatHandler) handleChatMessage(ctx context.Context, conn *websocket.Conn, client *ws.Client, payload json.RawMessage) error { + if client.UserID == 0 { + return conn.WriteJSON(map[string]string{ + "type": "error", + "message": "Not authenticated", + }) + } + + var messageData struct { + ReceiverID int `json:"receiverId"` + ChatID int `json:"chatId"` + Content string `json:"content"` + } + + if err := json.Unmarshal(payload, &messageData); err != nil { + return conn.WriteJSON(map[string]interface{}{ + "type": "error", + "message": "Invalid message format", + "details": err.Error(), + }) + } + + // Валидация данных сообщения + if messageData.ReceiverID == 0 && messageData.ChatID == 0 { + return conn.WriteJSON(map[string]interface{}{ + "type": "error", + "message": "Either receiverId or chatId must be provided", + }) + } + + if messageData.Content == "" { + return conn.WriteJSON(map[string]interface{}{ + "type": "error", + "message": "Message content cannot be empty", + }) + } + + // Определяем chatId если не указан + chatID := messageData.ChatID + if chatID == 0 { + chat, err := h.chatService.GetOrCreateChat(ctx, client.UserID, messageData.ReceiverID) + if err != nil { + log.Printf("Chat error: %v", err) + return conn.WriteJSON(map[string]interface{}{ + "type": "error", + "message": "Failed to get or create chat", + "details": err.Error(), + }) + } + chatID = chat.ID + } + + // Отправляем сообщение + message, err := h.chatService.SendMessage( + ctx, + client.UserID, + chatID, + messageData.Content, + ) + if err != nil { + log.Printf("Message send error: %v", err) + return conn.WriteJSON(map[string]interface{}{ + "type": "error", + "message": "Failed to send message", + "details": err.Error(), + }) + } + + // Убедимся, что в сообщении есть получатель + if message.ReceiverID == 0 { + if messageData.ReceiverID != 0 { + message.ReceiverID = messageData.ReceiverID + } else { + // Если receiver не указан, определяем его через чат + chat, err := h.chatService.GetChatByID(ctx, chatID) + if err == nil { + if chat.User1ID == client.UserID { + message.ReceiverID = chat.User2ID + } else { + message.ReceiverID = chat.User1ID } - - if msg.Payload.ReceiverID == 0 { - conn.WriteJSON(map[string]interface{}{ - "type": "error", - "message": "Invalid receiver ID", - }) - continue - } - - chat, err := h.chatService.GetOrCreateChat(ctx, client.UserID, msg.Payload.ReceiverID) - if err != nil { - log.Printf("Chat error: %v", err) - conn.WriteJSON(map[string]interface{}{ - "type": "error", - "message": "Chat error", - "details": err.Error(), - }) - continue - } - - message, err := h.chatService.SendMessage( - ctx, - client.UserID, - chat.ID, - msg.Payload.Content, - ) - if err != nil { - log.Printf("Message send error: %v", err) - continue - } - - h.hub.Broadcast(message) - default: - conn.WriteJSON(map[string]string{"type": "error", "message": "Unknown message type"}) } } } + + // Рассылаем сообщение всем подписчикам + h.hub.Broadcast(message) + + return nil } func (h *ChatHandler) writePump(ctx context.Context, conn *websocket.Conn, client *ws.Client) { diff --git a/internal/service/chat_service.go b/internal/service/chat_service.go index 03f9268..df38e87 100644 --- a/internal/service/chat_service.go +++ b/internal/service/chat_service.go @@ -17,6 +17,7 @@ type ChatService interface { GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error) GetOrCreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error) GetUnreadCount(ctx context.Context, chatID, userID int) (int, error) + GetChatByID(ctx context.Context, id int) (*domain.Chat, error) } type chatService struct { @@ -129,3 +130,6 @@ func (s *chatService) GetOrCreateChat(ctx context.Context, user1ID, user2ID int) func (s *chatService) GetUnreadCount(ctx context.Context, chatID, userID int) (int, error) { return s.chatRepo.GetUnreadCount(ctx, chatID, userID) } +func (s *chatService) GetChatByID(ctx context.Context, id int) (*domain.Chat, error) { + return s.chatRepo.GetChatByID(ctx, id) +}