diff --git a/.air.toml b/.air.toml index d063dd5..929e2c9 100644 --- a/.air.toml +++ b/.air.toml @@ -18,8 +18,8 @@ tmp_dir = "tmp" include_file = [] kill_delay = "0s" log = "build-errors.log" - poll = false - poll_interval = 0 + poll = true + poll_interval = 1000 post_cmd = [] pre_cmd = [] rerun = false diff --git a/docs/docs.go b/docs/docs.go index 531f430..5a45120 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -179,12 +179,6 @@ const docTemplate = `{ "description": "Límite de mensajes a obtener", "name": "limit", "in": "query" - }, - { - "type": "string", - "description": "Timestamp para obtener mensajes anteriores", - "name": "before", - "in": "query" } ], "responses": { @@ -543,7 +537,7 @@ const docTemplate = `{ "BearerAuth": [] } ], - "description": "Devuelve los mensajes de una sala específica", + "description": "Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente", "consumes": [ "application/json" ], @@ -571,19 +565,17 @@ const docTemplate = `{ }, { "type": "string", - "description": "Timestamp para obtener mensajes anteriores", - "name": "before", + "default": "\"1747441934\"", + "description": "Cursor para paginación (timestamp)", + "name": "cursor", "in": "query" } ], "responses": { "200": { - "description": "Lista de mensajes de la sala", + "description": "Mensajes paginados de la sala", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Message" - } + "$ref": "#/definitions/models.PaginatedMessagesResponse" } }, "401": { @@ -607,13 +599,73 @@ const docTemplate = `{ } } }, - "/chat/ws": { + "/chat/rooms/{roomId}/messages/simple": { "get": { "security": [ { "BearerAuth": [] } ], + "description": "Devuelve los mensajes de una sala específica sin paginación", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene mensajes de una sala (versión simple)", + "parameters": [ + { + "type": "string", + "description": "ID de la sala", + "name": "roomId", + "in": "path", + "required": true + }, + { + "type": "integer", + "default": 50, + "description": "Límite de mensajes a obtener", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Lista de mensajes de la sala", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Sala no encontrada", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/ws": { + "get": { "description": "Establece una conexión WebSocket para mensajería en tiempo real", "consumes": [ "application/json" @@ -625,6 +677,15 @@ const docTemplate = `{ "Chat" ], "summary": "Conexión WebSocket para chat en tiempo real", + "parameters": [ + { + "type": "string", + "description": "Firebase Auth Token", + "name": "token", + "in": "query", + "required": true + } + ], "responses": { "101": { "description": "Switching Protocols a WebSocket", @@ -730,6 +791,52 @@ const docTemplate = `{ } } }, + "models.MessageResponse": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "roomId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "models.PaginatedMessagesResponse": { + "type": "object", + "properties": { + "hasMore": { + "type": "boolean" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + }, + "nextCursor": { + "type": "string" + } + } + }, "models.Room": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index f2b2da9..a6d059c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -172,12 +172,6 @@ "description": "Límite de mensajes a obtener", "name": "limit", "in": "query" - }, - { - "type": "string", - "description": "Timestamp para obtener mensajes anteriores", - "name": "before", - "in": "query" } ], "responses": { @@ -536,7 +530,7 @@ "BearerAuth": [] } ], - "description": "Devuelve los mensajes de una sala específica", + "description": "Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente", "consumes": [ "application/json" ], @@ -564,19 +558,17 @@ }, { "type": "string", - "description": "Timestamp para obtener mensajes anteriores", - "name": "before", + "default": "\"1747441934\"", + "description": "Cursor para paginación (timestamp)", + "name": "cursor", "in": "query" } ], "responses": { "200": { - "description": "Lista de mensajes de la sala", + "description": "Mensajes paginados de la sala", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Message" - } + "$ref": "#/definitions/models.PaginatedMessagesResponse" } }, "401": { @@ -600,13 +592,73 @@ } } }, - "/chat/ws": { + "/chat/rooms/{roomId}/messages/simple": { "get": { "security": [ { "BearerAuth": [] } ], + "description": "Devuelve los mensajes de una sala específica sin paginación", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene mensajes de una sala (versión simple)", + "parameters": [ + { + "type": "string", + "description": "ID de la sala", + "name": "roomId", + "in": "path", + "required": true + }, + { + "type": "integer", + "default": 50, + "description": "Límite de mensajes a obtener", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Lista de mensajes de la sala", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Sala no encontrada", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/ws": { + "get": { "description": "Establece una conexión WebSocket para mensajería en tiempo real", "consumes": [ "application/json" @@ -618,6 +670,15 @@ "Chat" ], "summary": "Conexión WebSocket para chat en tiempo real", + "parameters": [ + { + "type": "string", + "description": "Firebase Auth Token", + "name": "token", + "in": "query", + "required": true + } + ], "responses": { "101": { "description": "Switching Protocols a WebSocket", @@ -723,6 +784,52 @@ } } }, + "models.MessageResponse": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "roomId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "models.PaginatedMessagesResponse": { + "type": "object", + "properties": { + "hasMore": { + "type": "boolean" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + }, + "nextCursor": { + "type": "string" + } + } + }, "models.Room": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d48e44c..8c01db3 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -57,6 +57,36 @@ definitions: userId: type: string type: object + models.MessageResponse: + properties: + content: + type: string + createdAt: + type: string + displayName: + type: string + id: + type: string + isDeleted: + type: boolean + roomId: + type: string + updatedAt: + type: string + userId: + type: string + type: object + models.PaginatedMessagesResponse: + properties: + hasMore: + type: boolean + messages: + items: + $ref: '#/definitions/models.MessageResponse' + type: array + nextCursor: + type: string + type: object models.Room: properties: admins: @@ -192,10 +222,6 @@ paths: in: query name: limit type: integer - - description: Timestamp para obtener mensajes anteriores - in: query - name: before - type: string produces: - application/json responses: @@ -429,7 +455,8 @@ paths: get: consumes: - application/json - description: Devuelve los mensajes de una sala específica + description: Devuelve los mensajes de una sala específica con soporte para paginación + ordernada por fecha de creación descendente parameters: - description: ID de la sala in: path @@ -441,10 +468,51 @@ paths: in: query name: limit type: integer - - description: Timestamp para obtener mensajes anteriores + - default: '"1747441934"' + description: Cursor para paginación (timestamp) in: query - name: before + name: cursor + type: string + produces: + - application/json + responses: + "200": + description: Mensajes paginados de la sala + schema: + $ref: '#/definitions/models.PaginatedMessagesResponse' + "401": + description: No autorizado + schema: + type: string + "404": + description: Sala no encontrada + schema: + type: string + "500": + description: Error interno del servidor + schema: + type: string + security: + - BearerAuth: [] + summary: Obtiene mensajes de una sala + tags: + - Chat + /chat/rooms/{roomId}/messages/simple: + get: + consumes: + - application/json + description: Devuelve los mensajes de una sala específica sin paginación + parameters: + - description: ID de la sala + in: path + name: roomId + required: true type: string + - default: 50 + description: Límite de mensajes a obtener + in: query + name: limit + type: integer produces: - application/json responses: @@ -452,7 +520,7 @@ paths: description: Lista de mensajes de la sala schema: items: - $ref: '#/definitions/models.Message' + $ref: '#/definitions/models.MessageResponse' type: array "401": description: No autorizado @@ -468,7 +536,7 @@ paths: type: string security: - BearerAuth: [] - summary: Obtiene mensajes de una sala + summary: Obtiene mensajes de una sala (versión simple) tags: - Chat /chat/rooms/me: @@ -503,6 +571,12 @@ paths: consumes: - application/json description: Establece una conexión WebSocket para mensajería en tiempo real + parameters: + - description: Firebase Auth Token + in: query + name: token + required: true + type: string produces: - application/json responses: @@ -514,8 +588,6 @@ paths: description: No autorizado schema: type: string - security: - - BearerAuth: [] summary: Conexión WebSocket para chat en tiempo real tags: - Chat diff --git a/internal/handlers/chat_handler.go b/internal/handlers/chat_handler.go index 4539d14..4a8e754 100644 --- a/internal/handlers/chat_handler.go +++ b/internal/handlers/chat_handler.go @@ -119,18 +119,18 @@ func (h *ChatHandler) GetUserRooms(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(rooms) } -// GetRoomMessages obtiene los mensajes de una sala +// GetRoomMessages obtiene los mensajes de una sala con paginación ordernada por fecha de creación descendente // // @Summary Obtiene mensajes de una sala -// @Description Devuelve los mensajes de una sala específica +// @Description Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente // @Tags Chat // @Accept json // @Produce json // @Security BearerAuth // @Param roomId path string true "ID de la sala" // @Param limit query int false "Límite de mensajes a obtener" default(50) -// @Param before query string false "Timestamp para obtener mensajes anteriores" -// @Success 200 {array} models.Message "Lista de mensajes de la sala" +// @Param cursor query string false "Cursor para paginación (timestamp)" default("1747441934") +// @Success 200 {object} models.PaginatedMessagesResponse "Mensajes paginados de la sala" // @Failure 401 {string} string "No autorizado" // @Failure 404 {string} string "Sala no encontrada" // @Failure 500 {string} string "Error interno del servidor" @@ -148,13 +148,21 @@ func (h *ChatHandler) GetRoomMessages(w http.ResponseWriter, r *http.Request) { } } - messages, err := h.RoomService.GetRoomMessages(roomID, limit) + cursor := r.URL.Query().Get("cursor") + + messages, nextCursor, err := h.RoomService.GetRoomMessages(roomID, limit, cursor) if err != nil { http.Error(w, "Error getting messages: "+err.Error(), http.StatusInternalServerError) return } - json.NewEncoder(w).Encode(messages) + response := models.PaginatedMessagesResponse{ + Messages: messages, + NextCursor: nextCursor, + HasMore: nextCursor != "", + } + + json.NewEncoder(w).Encode(response) } // CreateDirectChat crea o encuentra un chat directo entre dos usuarios @@ -235,7 +243,6 @@ func (h *ChatHandler) GetUserDirectChats(w http.ResponseWriter, r *http.Request) // @Security BearerAuth // @Param chatId path string true "ID del chat directo" // @Param limit query int false "Límite de mensajes a obtener" default(50) -// @Param before query string false "Timestamp para obtener mensajes anteriores" // @Success 200 {array} models.Message "Lista de mensajes del chat directo" // @Failure 401 {string} string "No autorizado" // @Failure 404 {string} string "Chat no encontrado" @@ -332,3 +339,40 @@ func (h *ChatHandler) JoinRoom(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) } + +// GetRoomMessagesSimple obtiene los mensajes de una sala sin paginación +// +// @Summary Obtiene mensajes de una sala (versión simple) +// @Description Devuelve los mensajes de una sala específica sin paginación +// @Tags Chat +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param roomId path string true "ID de la sala" +// @Param limit query int false "Límite de mensajes a obtener" default(50) +// @Success 200 {array} models.MessageResponse "Lista de mensajes de la sala" +// @Failure 401 {string} string "No autorizado" +// @Failure 404 {string} string "Sala no encontrada" +// @Failure 500 {string} string "Error interno del servidor" +// @Router /chat/rooms/{roomId}/messages/simple [get] +func (h *ChatHandler) GetRoomMessagesSimple(w http.ResponseWriter, r *http.Request) { + roomID := chi.URLParam(r, "roomId") + + limitStr := r.URL.Query().Get("limit") + limit := 50 // valor por defecto + + if limitStr != "" { + parsedLimit, err := strconv.Atoi(limitStr) + if err == nil && parsedLimit > 0 { + limit = parsedLimit + } + } + + messages, err := h.RoomService.GetRoomMessagesSimple(roomID, limit) + if err != nil { + http.Error(w, "Error getting messages: "+err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(messages) +} diff --git a/internal/handlers/websocket_handler.go b/internal/handlers/websocket_handler.go index abc6b10..ba23216 100644 --- a/internal/handlers/websocket_handler.go +++ b/internal/handlers/websocket_handler.go @@ -3,6 +3,7 @@ package handlers import ( "log" "net/http" + "strings" "github.com/Parchat/backend/internal/config" pws "github.com/Parchat/backend/internal/pkg/websocket" @@ -38,15 +39,33 @@ func NewWebSocketHandler(hub *pws.Hub, firebaseAuth *config.FirebaseAuth) *WebSo // @Tags Chat // @Accept json // @Produce json -// @Security BearerAuth -// @Success 101 {string} string "Switching Protocols a WebSocket" -// @Failure 401 {string} string "No autorizado" +// @Param token query string true "Firebase Auth Token" +// @Success 101 {string} string "Switching Protocols a WebSocket" +// @Failure 401 {string} string "No autorizado" // @Router /chat/ws [get] func (h *WebSocketHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request) { - // Verificar el token desde el contexto (añadido por el middleware de autenticación) - userID, ok := r.Context().Value("userID").(string) - if !ok { - http.Error(w, "Unauthorized", http.StatusUnauthorized) + // Extraer el token desde el parámetro de consulta + token := r.URL.Query().Get("token") + if token == "" { + http.Error(w, "Token not provided", http.StatusUnauthorized) + return + } + + // Limpiar el token (por si viene con "Bearer ") + token = strings.TrimPrefix(token, "Bearer ") + + // Verificar el token con Firebase + authToken, err := h.firebaseAuth.VerifyIDToken(r.Context(), token) + if err != nil { + log.Printf("Error verifying token: %v", err) + http.Error(w, "Invalid token", http.StatusUnauthorized) + return + } + + // Extraer el userID del token verificado + userID := authToken.UID + if userID == "" { + http.Error(w, "Invalid user ID in token", http.StatusUnauthorized) return } diff --git a/internal/models/message.go b/internal/models/message.go index a31d44d..bca0e73 100644 --- a/internal/models/message.go +++ b/internal/models/message.go @@ -12,3 +12,16 @@ type Message struct { UpdatedAt time.Time `json:"updatedAt" firestore:"updatedAt"` IsDeleted bool `json:"isDeleted" firestore:"isDeleted"` } + +// MessageResponse es la respuesta que incluye un mensaje y el nombre del usuario que lo envió +type MessageResponse struct { + Message + DisplayName string `json:"displayName,omitempty"` +} + +// PaginatedMessagesResponse representa una respuesta paginada de mensajes +type PaginatedMessagesResponse struct { + Messages []MessageResponse `json:"messages"` + NextCursor string `json:"nextCursor,omitempty"` + HasMore bool `json:"hasMore"` +} diff --git a/internal/repositories/message_repository.go b/internal/repositories/message_repository.go index aebe448..cc6d5b9 100644 --- a/internal/repositories/message_repository.go +++ b/internal/repositories/message_repository.go @@ -2,6 +2,9 @@ package repositories import ( "context" + "fmt" + "strconv" + "time" "cloud.google.com/go/firestore" "github.com/Parchat/backend/internal/config" @@ -54,16 +57,99 @@ func (r *MessageRepository) SaveDirectMessage(message *models.Message) error { return nil } -// GetRoomMessages obtiene los mensajes de una sala -func (r *MessageRepository) GetRoomMessages(roomID string, limit int) ([]models.Message, error) { +func (r *MessageRepository) GetRoomMessages(roomID string, limit int, cursor string) ([]models.MessageResponse, string, error) { ctx := context.Background() var messages []models.Message + var response []models.MessageResponse + var nextCursor string - messagesRef := r.FirestoreClient.Client. + // Crear la consulta base - Orden ASCENDENTE para paginación hacia atrás + query := r.FirestoreClient.Client. Collection("rooms").Doc(roomID). Collection("messages"). - OrderBy("createdAt", firestore.Desc). + OrderBy("createdAt", firestore.Desc). // Cambiado a Asc + Limit(limit) + + // Si hay un cursor, añadir la condición para empezar desde ese punto + if cursor != "" { + // Intentar convertir el cursor a un timestamp + // Se espera que el cursor sea un timestamp en formato Unix (string) + timestamp, err := strconv.ParseInt(cursor, 10, 64) + if err == nil { + // Convertir el timestamp a time.Time + cursorTime := time.Unix(timestamp, 0) + //fmt.Println("Cursor time:", cursorTime.String()) + + // Usar EndBefore con el valor convertido + query = query.StartAfter(cursorTime) + } + } + + // Ejecutar la consulta + docs, err := query.Documents(ctx).GetAll() + if err != nil { + return nil, "", fmt.Errorf("error obtaining messages: %v", err) + } + + // Get messages and track unique user IDs + userIDs := make(map[string]bool) + + for i, doc := range docs { + var message models.Message + if err := doc.DataTo(&message); err != nil { + return nil, "", fmt.Errorf("error decoding message: %v", err) + } + messages = append(messages, message) + userIDs[message.UserID] = true + + // Guardar el último timestamp para el cursor de siguiente página + if i == len(docs)-1 && len(docs) == limit { + //fmt.Println("Last message timestamp:", message.CreatedAt.Unix()) + //fmt.Println("Content:", message.Content) + nextCursor = strconv.FormatInt(message.CreatedAt.Unix(), 10) + } + } + + // Map to store user data to avoid duplicate fetches + userDataCache := make(map[string]string) // userId -> displayName + + // Fetch user data for all unique userIds + for userID := range userIDs { + userDoc, err := r.FirestoreClient.Client.Collection("users").Doc(userID).Get(ctx) + if err != nil { + // Si hay error, continuamos pero sin el displayName + continue + } + var user models.User + if err := userDoc.DataTo(&user); err == nil { + userDataCache[userID] = user.DisplayName + } + } + + // Construir la respuesta con los displayNames + for _, message := range messages { + msgResponse := models.MessageResponse{ + Message: message, + DisplayName: userDataCache[message.UserID], // Puede estar vacío si no se encontró + } + response = append(response, msgResponse) + } + + return response, nextCursor, nil +} + +// GetDirectChatMessages obtiene los mensajes de un chat directo +func (r *MessageRepository) GetDirectChatMessages(directChatID string, limit int) ([]models.MessageResponse, error) { + ctx := context.Background() + + var messages []models.Message + var response []models.MessageResponse + + messagesRef := r.FirestoreClient.Client. + Collection("directChats").Doc(directChatID). + Collection("messages"). + OrderBy("createdAt", firestore.Asc). Limit(limit) docs, err := messagesRef.Documents(ctx).GetAll() @@ -71,27 +157,58 @@ func (r *MessageRepository) GetRoomMessages(roomID string, limit int) ([]models. return nil, err } + // Get messages and track unique user IDs + userIDs := make(map[string]bool) for _, doc := range docs { var message models.Message if err := doc.DataTo(&message); err != nil { return nil, err } messages = append(messages, message) + userIDs[message.UserID] = true } - return messages, nil + // Map to store user data to avoid duplicate fetches + userDataCache := make(map[string]string) // userId -> displayName + + // Fetch user data for all unique userIds + for userID := range userIDs { + userDoc, err := r.FirestoreClient.Client.Collection("users").Doc(userID).Get(ctx) + if err == nil { + var user models.User + if err := userDoc.DataTo(&user); err == nil { + userDataCache[userID] = user.DisplayName + } + } + } + + for _, message := range messages { + msgMap := models.MessageResponse{ + Message: message, + } + + // Add displayName if available in cache + if displayName, exists := userDataCache[message.UserID]; exists { + msgMap.DisplayName = displayName + } + + response = append(response, msgMap) + } + + return response, nil } -// GetDirectChatMessages obtiene los mensajes de un chat directo -func (r *MessageRepository) GetDirectChatMessages(directChatID string, limit int) ([]models.Message, error) { +// GetRoomMessagesSimple obtiene los mensajes de una sala sin paginación +func (r *MessageRepository) GetRoomMessagesSimple(roomID string, limit int) ([]models.MessageResponse, error) { ctx := context.Background() var messages []models.Message + var response []models.MessageResponse messagesRef := r.FirestoreClient.Client. - Collection("directChats").Doc(directChatID). + Collection("rooms").Doc(roomID). Collection("messages"). - OrderBy("createdAt", firestore.Desc). + OrderBy("createdAt", firestore.Asc). Limit(limit) docs, err := messagesRef.Documents(ctx).GetAll() @@ -99,13 +216,41 @@ func (r *MessageRepository) GetDirectChatMessages(directChatID string, limit int return nil, err } + // Get messages and track unique user IDs + userIDs := make(map[string]bool) for _, doc := range docs { var message models.Message if err := doc.DataTo(&message); err != nil { return nil, err } messages = append(messages, message) + userIDs[message.UserID] = true + } + + // Map to store user data to avoid duplicate fetches + userDataCache := make(map[string]string) // userId -> displayName + + // Fetch user data for all unique userIds + for userID := range userIDs { + userDoc, err := r.FirestoreClient.Client.Collection("users").Doc(userID).Get(ctx) + if err != nil { + // Si hay error, continuamos pero sin el displayName + continue + } + var user models.User + if err := userDoc.DataTo(&user); err == nil { + userDataCache[userID] = user.DisplayName + } + } + + // Construir la respuesta con los displayNames + for _, message := range messages { + msgResponse := models.MessageResponse{ + Message: message, + DisplayName: userDataCache[message.UserID], // Puede estar vacío si no se encontró + } + response = append(response, msgResponse) } - return messages, nil + return response, nil } diff --git a/internal/routes/router.go b/internal/routes/router.go index 8b816d1..147b58d 100644 --- a/internal/routes/router.go +++ b/internal/routes/router.go @@ -56,28 +56,30 @@ func NewRouter( // Rutas de chat (protegidas) r.Route("/chat", func(r chi.Router) { - // Aplicar middleware de autenticación - r.Use(authMw.VerifyToken) + // WebSocket endpoint + r.Get("/ws", webSocketHandler.HandleWebSocket) - // Rutas de salas - r.Route("/rooms", func(r chi.Router) { - r.Post("/", chatHandler.CreateRoom) - r.Get("/me", chatHandler.GetUserRooms) - r.Get("/", chatHandler.GetAllRooms) - r.Get("/{roomId}", chatHandler.GetRoom) - r.Get("/{roomId}/messages", chatHandler.GetRoomMessages) - r.Post("/{roomId}/join", chatHandler.JoinRoom) - }) + r.Group(func(r chi.Router) { + r.Use(authMw.VerifyToken) // Aplicar middleware de autenticación - // Rutas de chats directos - r.Route("/direct", func(r chi.Router) { - r.Post("/{otherUserId}", chatHandler.CreateDirectChat) - r.Get("/me", chatHandler.GetUserDirectChats) - r.Get("/{chatId}/messages", chatHandler.GetDirectChatMessages) - }) + // Rutas de salas + r.Route("/rooms", func(r chi.Router) { + r.Post("/", chatHandler.CreateRoom) + r.Get("/me", chatHandler.GetUserRooms) + r.Get("/", chatHandler.GetAllRooms) + r.Get("/{roomId}", chatHandler.GetRoom) + r.Get("/{roomId}/messages", chatHandler.GetRoomMessagesSimple) + r.Get("/{roomId}/messages/paginated", chatHandler.GetRoomMessages) + r.Post("/{roomId}/join", chatHandler.JoinRoom) + }) - // WebSocket endpoint - r.Get("/ws", webSocketHandler.HandleWebSocket) + // Rutas de chats directos + r.Route("/direct", func(r chi.Router) { + r.Post("/{otherUserId}", chatHandler.CreateDirectChat) + r.Get("/me", chatHandler.GetUserDirectChats) + r.Get("/{chatId}/messages", chatHandler.GetDirectChatMessages) + }) + }) }) }) diff --git a/internal/services/directchat_service.go b/internal/services/directchat_service.go index 655c519..a6f5567 100644 --- a/internal/services/directchat_service.go +++ b/internal/services/directchat_service.go @@ -35,7 +35,7 @@ func (s *DirectChatService) GetUserDirectChats(userID string) ([]models.DirectCh } // GetDirectChatMessages obtiene los mensajes de un chat directo -func (s *DirectChatService) GetDirectChatMessages(directChatID string, limit int) ([]models.Message, error) { +func (s *DirectChatService) GetDirectChatMessages(directChatID string, limit int) ([]models.MessageResponse, error) { return s.MessageRepo.GetDirectChatMessages(directChatID, limit) } diff --git a/internal/services/room_service.go b/internal/services/room_service.go index e95a993..9dcb7d3 100644 --- a/internal/services/room_service.go +++ b/internal/services/room_service.go @@ -51,9 +51,14 @@ func (s *RoomService) GetUserRooms(userID string) ([]models.Room, error) { return s.RoomRepo.GetUserRooms(userID) } -// GetRoomMessages obtiene los mensajes de una sala -func (s *RoomService) GetRoomMessages(roomID string, limit int) ([]models.Message, error) { - return s.MessageRepo.GetRoomMessages(roomID, limit) +// GetRoomMessages obtiene los mensajes de una sala con paginación +func (s *RoomService) GetRoomMessages(roomID string, limit int, cursor string) ([]models.MessageResponse, string, error) { + return s.MessageRepo.GetRoomMessages(roomID, limit, cursor) +} + +// GetRoomMessagesSimple obtiene los mensajes de una sala sin paginación +func (s *RoomService) GetRoomMessagesSimple(roomID string, limit int) ([]models.MessageResponse, error) { + return s.MessageRepo.GetRoomMessagesSimple(roomID, limit) } // GetAllRooms obtiene todas las salas ordenadas por fecha de actualización