v0.0.17.4
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
madipo2611 2025-08-11 18:37:52 +03:00
parent ef8595afae
commit c98667c5b5
3 changed files with 193 additions and 119 deletions

View File

@ -107,44 +107,29 @@ func (r *messageResolver) Sender(ctx context.Context, obj *domain.Message) (*dom
return user, nil return user, nil
} }
// Receiver - возвращает получателя сообщения
func (r *messageResolver) Receiver(ctx context.Context, obj *domain.Message) (*domain.User, error) { func (r *messageResolver) Receiver(ctx context.Context, obj *domain.Message) (*domain.User, error) {
// 1. Проверка на nil // 1. Если receiver явно указан в сообщении
if obj == nil {
return nil, fmt.Errorf("message is nil")
}
// 2. Если есть receiver_id - используем его напрямую
if obj.ReceiverID != 0 { 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 { 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 не установлен) // 3. Если ничего не найдено
if obj.ChatID == 0 { return nil, fmt.Errorf("cannot determine receiver - both receiver_id and chat_id are not set")
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
} }
// CreatedAt - форматирует время сообщения // CreatedAt - форматирует время сообщения

View File

@ -2,6 +2,8 @@ package handlers
import ( import (
"context" "context"
"encoding/json"
"fmt"
"log" "log"
"net/http" "net/http"
"tailly_back_v2/internal/domain" "tailly_back_v2/internal/domain"
@ -69,114 +71,197 @@ func (h *ChatHandler) readPump(ctx context.Context, conn *websocket.Conn, client
defer ticker.Stop() defer ticker.Stop()
defer conn.Close() defer conn.Close()
// 1. Ожидаем первое сообщение с токеном // 1. Аутентификация
var authMsg struct { if err := h.authenticateConnection(conn, client); err != nil {
Type string `json:"type"` log.Printf("Authentication failed: %v", err)
Token string `json:"token"`
}
if err := conn.ReadJSON(&authMsg); err != nil {
log.Printf("Failed to read auth message: %v", err)
return return
} }
if authMsg.Type != "auth" || authMsg.Token == "" { // 2. Основной цикл обработки сообщений
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. Основной цикл обработки сообщений
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
if err := conn.WriteJSON(map[string]string{"type": "ping"}); err != nil { if err := h.sendPing(conn); err != nil {
log.Printf("Ping error: %v", err)
return return
} }
case <-ctx.Done(): case <-ctx.Done():
return return
default: default:
var msg struct { if err := h.handleMessage(ctx, conn, client); err != nil {
Type string `json:"type"`
Payload struct {
ReceiverID int `json:"receiverId"`
Content string `json:"content"`
} `json:"payload"`
}
if err := conn.ReadJSON(&msg); err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err) log.Printf("WebSocket error: %v", err)
} }
return return
} }
}
}
}
switch msg.Type { func (h *ChatHandler) authenticateConnection(conn *websocket.Conn, client *ws.Client) error {
case "pong": var authMsg struct {
continue Type string `json:"type"`
case "ping": Token string `json:"token"`
conn.WriteJSON(map[string]string{"type": "pong"}) }
continue
case "message": // Устанавливаем таймаут для аутентификации
if client.UserID == 0 { conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.WriteJSON(map[string]string{"type": "error", "message": "Not authenticated"}) defer conn.SetReadDeadline(time.Time{})
continue
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) { func (h *ChatHandler) writePump(ctx context.Context, conn *websocket.Conn, client *ws.Client) {

View File

@ -17,6 +17,7 @@ type ChatService interface {
GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error) GetUserChats(ctx context.Context, userID int) ([]*domain.Chat, error)
GetOrCreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error) GetOrCreateChat(ctx context.Context, user1ID, user2ID int) (*domain.Chat, error)
GetUnreadCount(ctx context.Context, chatID, userID int) (int, error) GetUnreadCount(ctx context.Context, chatID, userID int) (int, error)
GetChatByID(ctx context.Context, id int) (*domain.Chat, error)
} }
type chatService struct { 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) { func (s *chatService) GetUnreadCount(ctx context.Context, chatID, userID int) (int, error) {
return s.chatRepo.GetUnreadCount(ctx, chatID, userID) 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)
}