This commit is contained in:
parent
ef8595afae
commit
c98667c5b5
@ -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 - форматирует время сообщения
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user