From 476d0761729b0f0f1e723750ba317de34c9daad1 Mon Sep 17 00:00:00 2001 From: LifeRIP <89425013+LifeRIP@users.noreply.github.com> Date: Fri, 16 May 2025 12:46:36 -0500 Subject: [PATCH 1/6] feat: Update WebSocket API to require Firebase Auth Token in the query params URL --- docs/docs.go | 14 +++++---- docs/swagger.json | 14 +++++---- docs/swagger.yaml | 8 ++++-- internal/handlers/websocket_handler.go | 33 +++++++++++++++++----- internal/routes/router.go | 39 +++++++++++++------------- 5 files changed, 70 insertions(+), 38 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 531f430..0cfd7bb 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -609,11 +609,6 @@ const docTemplate = `{ }, "/chat/ws": { "get": { - "security": [ - { - "BearerAuth": [] - } - ], "description": "Establece una conexión WebSocket para mensajería en tiempo real", "consumes": [ "application/json" @@ -625,6 +620,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", diff --git a/docs/swagger.json b/docs/swagger.json index f2b2da9..83a41ad 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -602,11 +602,6 @@ }, "/chat/ws": { "get": { - "security": [ - { - "BearerAuth": [] - } - ], "description": "Establece una conexión WebSocket para mensajería en tiempo real", "consumes": [ "application/json" @@ -618,6 +613,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", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d48e44c..ccf6a41 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -503,6 +503,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 +520,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/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/routes/router.go b/internal/routes/router.go index 8b816d1..1033a10 100644 --- a/internal/routes/router.go +++ b/internal/routes/router.go @@ -56,28 +56,29 @@ 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.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) + }) + }) }) }) From e1ff2b221374720a105cb1441dcde536311b046f Mon Sep 17 00:00:00 2001 From: LifeRIP <89425013+LifeRIP@users.noreply.github.com> Date: Fri, 16 May 2025 17:38:08 -0500 Subject: [PATCH 2/6] refactor: Revises message ordering and removes deprecated query param Changes message ordering from descending to ascending in room and direct chat repositories to align with updated requirements. Removes the 'before' query parameter from API documentation as it is no longer supported. --- docs/docs.go | 12 ------------ docs/swagger.json | 12 ------------ docs/swagger.yaml | 8 -------- internal/handlers/chat_handler.go | 2 -- internal/repositories/message_repository.go | 4 ++-- 5 files changed, 2 insertions(+), 36 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 0cfd7bb..a1eb92f 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": { @@ -568,12 +562,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": { diff --git a/docs/swagger.json b/docs/swagger.json index 83a41ad..af46987 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": { @@ -561,12 +555,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": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ccf6a41..5d30edd 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -192,10 +192,6 @@ paths: in: query name: limit type: integer - - description: Timestamp para obtener mensajes anteriores - in: query - name: before - type: string produces: - application/json responses: @@ -441,10 +437,6 @@ paths: in: query name: limit type: integer - - description: Timestamp para obtener mensajes anteriores - in: query - name: before - type: string produces: - application/json responses: diff --git a/internal/handlers/chat_handler.go b/internal/handlers/chat_handler.go index 4539d14..0a7db5d 100644 --- a/internal/handlers/chat_handler.go +++ b/internal/handlers/chat_handler.go @@ -129,7 +129,6 @@ func (h *ChatHandler) GetUserRooms(w http.ResponseWriter, r *http.Request) { // @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" // @Failure 401 {string} string "No autorizado" // @Failure 404 {string} string "Sala no encontrada" @@ -235,7 +234,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" diff --git a/internal/repositories/message_repository.go b/internal/repositories/message_repository.go index aebe448..896ce21 100644 --- a/internal/repositories/message_repository.go +++ b/internal/repositories/message_repository.go @@ -63,7 +63,7 @@ func (r *MessageRepository) GetRoomMessages(roomID string, limit int) ([]models. messagesRef := r.FirestoreClient.Client. Collection("rooms").Doc(roomID). Collection("messages"). - OrderBy("createdAt", firestore.Desc). + OrderBy("createdAt", firestore.Asc). Limit(limit) docs, err := messagesRef.Documents(ctx).GetAll() @@ -91,7 +91,7 @@ func (r *MessageRepository) GetDirectChatMessages(directChatID string, limit int messagesRef := r.FirestoreClient.Client. Collection("directChats").Doc(directChatID). Collection("messages"). - OrderBy("createdAt", firestore.Desc). + OrderBy("createdAt", firestore.Asc). Limit(limit) docs, err := messagesRef.Documents(ctx).GetAll() From 98c3bf6efe8dacdf27ab70e2d533031f0db97363 Mon Sep 17 00:00:00 2001 From: Joan Manuel Jaramillo Avila <89425013+LifeRIP@users.noreply.github.com> Date: Mon, 2 Jun 2025 22:00:56 -0500 Subject: [PATCH 3/6] feat: Adds enriched message responses with user display names - Introduces a new `MessageResponse` type to include user display names alongside messages. - Updates retrieval methods for room and direct chat messages to fetch user data and enhance responses. --- internal/models/message.go | 6 ++ internal/repositories/message_repository.go | 70 +++++++++++++++++++-- internal/services/directchat_service.go | 2 +- internal/services/room_service.go | 2 +- 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/internal/models/message.go b/internal/models/message.go index a31d44d..ba05466 100644 --- a/internal/models/message.go +++ b/internal/models/message.go @@ -12,3 +12,9 @@ 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"` +} diff --git a/internal/repositories/message_repository.go b/internal/repositories/message_repository.go index 896ce21..8285821 100644 --- a/internal/repositories/message_repository.go +++ b/internal/repositories/message_repository.go @@ -55,10 +55,11 @@ func (r *MessageRepository) SaveDirectMessage(message *models.Message) error { } // 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) ([]models.MessageResponse, error) { ctx := context.Background() var messages []models.Message + var response []models.MessageResponse messagesRef := r.FirestoreClient.Client. Collection("rooms").Doc(roomID). @@ -71,22 +72,53 @@ 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) { +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). @@ -99,13 +131,43 @@ 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 { + 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 messages, nil + return response, nil } 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..a463ffa 100644 --- a/internal/services/room_service.go +++ b/internal/services/room_service.go @@ -52,7 +52,7 @@ func (s *RoomService) GetUserRooms(userID string) ([]models.Room, error) { } // GetRoomMessages obtiene los mensajes de una sala -func (s *RoomService) GetRoomMessages(roomID string, limit int) ([]models.Message, error) { +func (s *RoomService) GetRoomMessages(roomID string, limit int) ([]models.MessageResponse, error) { return s.MessageRepo.GetRoomMessages(roomID, limit) } From 78bf041d654a6f07d658c5d5852a858e27338bec Mon Sep 17 00:00:00 2001 From: Joan Manuel Jaramillo Avila <89425013+LifeRIP@users.noreply.github.com> Date: Mon, 2 Jun 2025 22:12:51 -0500 Subject: [PATCH 4/6] feat: Enables polling with a 1000ms interval in Air config - Updates the Air configuration to enable polling and sets the poll interval to 1000ms. This change likely improves file monitoring or responsiveness in development workflows in Windows. --- .air.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 5d268658c2020790b69f7dced7b5dcb409346ff1 Mon Sep 17 00:00:00 2001 From: Joan Manuel Jaramillo Avila <89425013+LifeRIP@users.noreply.github.com> Date: Wed, 4 Jun 2025 00:54:21 -0500 Subject: [PATCH 5/6] feat: Adds paginated room messages retrieval - Enhances the GetRoomMessages functionality by introducing pagination support with descending order based on creation date. - Updates API specifications to include a cursor for pagination and returns a structured response containing paginated messages, next cursor, and a flag for more data availability. - Refactors repository logic to handle pagination, including timestamp-based cursors. --- docs/docs.go | 811 +------------------- docs/swagger.json | 807 +------------------ docs/swagger.yaml | 44 +- internal/handlers/chat_handler.go | 19 +- internal/models/message.go | 7 + internal/repositories/message_repository.go | 72 +- internal/services/room_service.go | 6 +- 7 files changed, 116 insertions(+), 1650 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index a1eb92f..80170f0 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,816 +1,11 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT +// Code generated by swaggo/swag. DO NOT EDIT. + package docs import "github.com/swaggo/swag" const docTemplate = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "termsOfService": "https://pachat.online/terms/", - "contact": { - "name": "Parchat Support", - "url": "https://pachat.online/support", - "email": "parchat.soporte@gmail.com" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/auth/me": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los datos del usuario autenticado basado en el ID del contexto", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Obtiene el usuario actual", - "responses": { - "200": { - "description": "Datos del usuario actual", - "schema": { - "$ref": "#/definitions/models.User" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/auth/signup": { - "post": { - "description": "Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Registra un nuevo usuario", - "parameters": [ - { - "description": "Datos del usuario a registrar", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.PayloadSignUp" - } - } - ], - "responses": { - "201": { - "description": "Usuario creado exitosamente", - "schema": { - "$ref": "#/definitions/models.User" - } - }, - "400": { - "description": "Solicitud inválida", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/direct/me": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve todos los chats directos del usuario autenticado", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene chats directos", - "responses": { - "200": { - "description": "Lista de chats directos", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.DirectChat" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/direct/{chatId}/messages": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los mensajes de un chat directo específico", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene mensajes de un chat directo", - "parameters": [ - { - "type": "string", - "description": "ID del chat directo", - "name": "chatId", - "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 del chat directo", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Message" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Chat no encontrado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/direct/{otherUserId}": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Crea o encuentra un chat directo entre el usuario autenticado y otro usuario", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Crea un chat directo", - "parameters": [ - { - "type": "string", - "description": "ID del otro usuario", - "name": "otherUserId", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Chat directo creado o encontrado", - "schema": { - "$ref": "#/definitions/models.DirectChat" - } - }, - "400": { - "description": "Solicitud inválida", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve todas las salas ordenadas por fecha de actualización descendente", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene todas las salas", - "responses": { - "200": { - "description": "Lista de salas", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Room" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Crea una nueva sala de chat con el usuario actual como propietario", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Crea una nueva sala de chat", - "parameters": [ - { - "description": "Detalles de la sala", - "name": "room", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.CreateRoomRequest" - } - } - ], - "responses": { - "201": { - "description": "Sala creada exitosamente", - "schema": { - "$ref": "#/definitions/models.Room" - } - }, - "400": { - "description": "Solicitud inválida", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms/me": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve todas las salas a las que pertenece el usuario autenticado", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene las salas del usuario", - "responses": { - "200": { - "description": "Lista de salas", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Room" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms/{roomId}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los detalles de una sala específica", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene una sala por ID", - "parameters": [ - { - "type": "string", - "description": "ID de la sala", - "name": "roomId", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Detalles de la sala", - "schema": { - "$ref": "#/definitions/models.Room" - } - }, - "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/rooms/{roomId}/join": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Permite al usuario autenticado unirse a una sala específica", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Unirse a una sala", - "parameters": [ - { - "type": "string", - "description": "ID de la sala", - "name": "roomId", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Usuario unido exitosamente", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "403": { - "description": "No permitido unirse a esta sala", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Sala no encontrada", - "schema": { - "type": "string" - } - }, - "409": { - "description": "Usuario ya es miembro de la sala", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms/{roomId}/messages": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los mensajes de una sala específica", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene mensajes de una sala", - "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.Message" - } - } - }, - "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" - ], - "produces": [ - "application/json" - ], - "tags": [ - "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", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - } - } - } - } - }, - "definitions": { - "handlers.PayloadSignUp": { - "type": "object", - "properties": { - "displayName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "models.CreateRoomRequest": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "isPrivate": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "userIds": { - "description": "IDs of users to be added to the room", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "models.DirectChat": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "id": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "lastMessage": { - "$ref": "#/definitions/models.Message" - }, - "updatedAt": { - "type": "string" - }, - "userIds": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "models.Message": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "id": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "roomId": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "userId": { - "type": "string" - } - } - }, - "models.Room": { - "type": "object", - "properties": { - "admins": { - "type": "array", - "items": { - "type": "string" - } - }, - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "imageUrl": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "isPrivate": { - "type": "boolean" - }, - "lastMessage": { - "$ref": "#/definitions/models.Message" - }, - "members": { - "type": "array", - "items": { - "type": "string" - } - }, - "name": { - "type": "string" - }, - "ownerId": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "models.User": { - "type": "object", - "properties": { - "createdAt": { - "description": "BlockedUsers []string ` + "`" + `json:\"blockedUsers\" firestore:\"blockedUsers\"` + "`" + `", - "type": "string" - }, - "displayName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "lastSeen": { - "type": "string" - }, - "photoUrl": { - "type": "string" - }, - "status": { - "type": "string" - }, - "uid": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - } - }, - "securityDefinitions": { - "BearerAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -}` + "schemes": {{ marshal .Schemes }},"swagger":"2.0","info":{"description":"{{escape .Description}}","title":"{{.Title}}","termsOfService":"https://pachat.online/terms/","contact":{"name":"Parchat Support","url":"https://pachat.online/support","email":"parchat.soporte@gmail.com"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"{{.Version}}"},"host":"{{.Host}}","basePath":"{{.BasePath}}","paths":{"/auth/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los datos del usuario autenticado basado en el ID del contexto","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Obtiene el usuario actual","responses":{"200":{"description":"Datos del usuario actual","schema":{"$ref":"#/definitions/models.User"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/auth/signup":{"post":{"description":"Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Registra un nuevo usuario","parameters":[{"description":"Datos del usuario a registrar","name":"payload","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.PayloadSignUp"}}],"responses":{"201":{"description":"Usuario creado exitosamente","schema":{"$ref":"#/definitions/models.User"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todos los chats directos del usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene chats directos","responses":{"200":{"description":"Lista de chats directos","schema":{"type":"array","items":{"$ref":"#/definitions/models.DirectChat"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{chatId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de un chat directo específico","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de un chat directo","parameters":[{"type":"string","description":"ID del chat directo","name":"chatId","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 del chat directo","schema":{"type":"array","items":{"$ref":"#/definitions/models.Message"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"404":{"description":"Chat no encontrado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{otherUserId}":{"post":{"security":[{"BearerAuth":[]}],"description":"Crea o encuentra un chat directo entre el usuario autenticado y otro usuario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea un chat directo","parameters":[{"type":"string","description":"ID del otro usuario","name":"otherUserId","in":"path","required":true}],"responses":{"201":{"description":"Chat directo creado o encontrado","schema":{"$ref":"#/definitions/models.DirectChat"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas ordenadas por fecha de actualización descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene todas las salas","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Crea una nueva sala de chat con el usuario actual como propietario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea una nueva sala de chat","parameters":[{"description":"Detalles de la sala","name":"room","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateRoomRequest"}}],"responses":{"201":{"description":"Sala creada exitosamente","schema":{"$ref":"#/definitions/models.Room"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas a las que pertenece el usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene las salas del usuario","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los detalles de una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene una sala por ID","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Detalles de la sala","schema":{"$ref":"#/definitions/models.Room"}},"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/rooms/{roomId}/join":{"post":{"security":[{"BearerAuth":[]}],"description":"Permite al usuario autenticado unirse a una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Unirse a una sala","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Usuario unido exitosamente","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"403":{"description":"No permitido unirse a esta sala","schema":{"type":"string"}},"404":{"description":"Sala no encontrada","schema":{"type":"string"}},"409":{"description":"Usuario ya es miembro de la sala","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de una sala","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"},{"type":"string","default":"\"1747441934\"","description":"Cursor para paginación (timestamp)","name":"cursor","in":"query"}],"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"}}}}},"/chat/ws":{"get":{"description":"Establece una conexión WebSocket para mensajería en tiempo real","consumes":["application/json"],"produces":["application/json"],"tags":["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","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}}}}}},"definitions":{"handlers.PayloadSignUp":{"type":"object","properties":{"displayName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}},"models.CreateRoomRequest":{"type":"object","properties":{"description":{"type":"string"},"isPrivate":{"type":"boolean"},"name":{"type":"string"},"userIds":{"description":"IDs of users to be added to the room","type":"array","items":{"type":"string"}}}},"models.DirectChat":{"type":"object","properties":{"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"updatedAt":{"type":"string"},"userIds":{"type":"array","items":{"type":"string"}}}},"models.Message":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"roomId":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}},"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":{"admins":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"imageUrl":{"type":"string"},"isDeleted":{"type":"boolean"},"isPrivate":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"members":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"ownerId":{"type":"string"},"updatedAt":{"type":"string"}}},"models.User":{"type":"object","properties":{"createdAt":{"description":"BlockedUsers []string ` + "`" + `json:\"blockedUsers\" firestore:\"blockedUsers\"` + "`" + `","type":"string"},"displayName":{"type":"string"},"email":{"type":"string"},"isDeleted":{"type":"boolean"},"lastSeen":{"type":"string"},"photoUrl":{"type":"string"},"status":{"type":"string"},"uid":{"type":"string"},"updatedAt":{"type":"string"}}}},"securityDefinitions":{"BearerAuth":{"type":"apiKey","name":"Authorization","in":"header"}}}` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ diff --git a/docs/swagger.json b/docs/swagger.json index af46987..beefb17 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,806 +1 @@ -{ - "swagger": "2.0", - "info": { - "description": "This is Parchat API.", - "title": "Parchat API", - "termsOfService": "https://pachat.online/terms/", - "contact": { - "name": "Parchat Support", - "url": "https://pachat.online/support", - "email": "parchat.soporte@gmail.com" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" - }, - "basePath": "/api/v1/", - "paths": { - "/auth/me": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los datos del usuario autenticado basado en el ID del contexto", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Obtiene el usuario actual", - "responses": { - "200": { - "description": "Datos del usuario actual", - "schema": { - "$ref": "#/definitions/models.User" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/auth/signup": { - "post": { - "description": "Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Registra un nuevo usuario", - "parameters": [ - { - "description": "Datos del usuario a registrar", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.PayloadSignUp" - } - } - ], - "responses": { - "201": { - "description": "Usuario creado exitosamente", - "schema": { - "$ref": "#/definitions/models.User" - } - }, - "400": { - "description": "Solicitud inválida", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/direct/me": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve todos los chats directos del usuario autenticado", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene chats directos", - "responses": { - "200": { - "description": "Lista de chats directos", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.DirectChat" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/direct/{chatId}/messages": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los mensajes de un chat directo específico", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene mensajes de un chat directo", - "parameters": [ - { - "type": "string", - "description": "ID del chat directo", - "name": "chatId", - "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 del chat directo", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Message" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Chat no encontrado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/direct/{otherUserId}": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Crea o encuentra un chat directo entre el usuario autenticado y otro usuario", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Crea un chat directo", - "parameters": [ - { - "type": "string", - "description": "ID del otro usuario", - "name": "otherUserId", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Chat directo creado o encontrado", - "schema": { - "$ref": "#/definitions/models.DirectChat" - } - }, - "400": { - "description": "Solicitud inválida", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve todas las salas ordenadas por fecha de actualización descendente", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene todas las salas", - "responses": { - "200": { - "description": "Lista de salas", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Room" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Crea una nueva sala de chat con el usuario actual como propietario", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Crea una nueva sala de chat", - "parameters": [ - { - "description": "Detalles de la sala", - "name": "room", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.CreateRoomRequest" - } - } - ], - "responses": { - "201": { - "description": "Sala creada exitosamente", - "schema": { - "$ref": "#/definitions/models.Room" - } - }, - "400": { - "description": "Solicitud inválida", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms/me": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve todas las salas a las que pertenece el usuario autenticado", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene las salas del usuario", - "responses": { - "200": { - "description": "Lista de salas", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Room" - } - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms/{roomId}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los detalles de una sala específica", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene una sala por ID", - "parameters": [ - { - "type": "string", - "description": "ID de la sala", - "name": "roomId", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Detalles de la sala", - "schema": { - "$ref": "#/definitions/models.Room" - } - }, - "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/rooms/{roomId}/join": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Permite al usuario autenticado unirse a una sala específica", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Unirse a una sala", - "parameters": [ - { - "type": "string", - "description": "ID de la sala", - "name": "roomId", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Usuario unido exitosamente", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - }, - "403": { - "description": "No permitido unirse a esta sala", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Sala no encontrada", - "schema": { - "type": "string" - } - }, - "409": { - "description": "Usuario ya es miembro de la sala", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Error interno del servidor", - "schema": { - "type": "string" - } - } - } - } - }, - "/chat/rooms/{roomId}/messages": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Devuelve los mensajes de una sala específica", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Chat" - ], - "summary": "Obtiene mensajes de una sala", - "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.Message" - } - } - }, - "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" - ], - "produces": [ - "application/json" - ], - "tags": [ - "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", - "schema": { - "type": "string" - } - }, - "401": { - "description": "No autorizado", - "schema": { - "type": "string" - } - } - } - } - } - }, - "definitions": { - "handlers.PayloadSignUp": { - "type": "object", - "properties": { - "displayName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "models.CreateRoomRequest": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "isPrivate": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "userIds": { - "description": "IDs of users to be added to the room", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "models.DirectChat": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "id": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "lastMessage": { - "$ref": "#/definitions/models.Message" - }, - "updatedAt": { - "type": "string" - }, - "userIds": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "models.Message": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "id": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "roomId": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "userId": { - "type": "string" - } - } - }, - "models.Room": { - "type": "object", - "properties": { - "admins": { - "type": "array", - "items": { - "type": "string" - } - }, - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "imageUrl": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "isPrivate": { - "type": "boolean" - }, - "lastMessage": { - "$ref": "#/definitions/models.Message" - }, - "members": { - "type": "array", - "items": { - "type": "string" - } - }, - "name": { - "type": "string" - }, - "ownerId": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "models.User": { - "type": "object", - "properties": { - "createdAt": { - "description": "BlockedUsers []string `json:\"blockedUsers\" firestore:\"blockedUsers\"`", - "type": "string" - }, - "displayName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "isDeleted": { - "type": "boolean" - }, - "lastSeen": { - "type": "string" - }, - "photoUrl": { - "type": "string" - }, - "status": { - "type": "string" - }, - "uid": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - } - }, - "securityDefinitions": { - "BearerAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -} \ No newline at end of file +{"swagger":"2.0","info":{"description":"This is Parchat API.","title":"Parchat API","termsOfService":"https://pachat.online/terms/","contact":{"name":"Parchat Support","url":"https://pachat.online/support","email":"parchat.soporte@gmail.com"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"basePath":"/api/v1/","paths":{"/auth/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los datos del usuario autenticado basado en el ID del contexto","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Obtiene el usuario actual","responses":{"200":{"description":"Datos del usuario actual","schema":{"$ref":"#/definitions/models.User"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/auth/signup":{"post":{"description":"Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Registra un nuevo usuario","parameters":[{"description":"Datos del usuario a registrar","name":"payload","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.PayloadSignUp"}}],"responses":{"201":{"description":"Usuario creado exitosamente","schema":{"$ref":"#/definitions/models.User"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todos los chats directos del usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene chats directos","responses":{"200":{"description":"Lista de chats directos","schema":{"type":"array","items":{"$ref":"#/definitions/models.DirectChat"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{chatId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de un chat directo específico","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de un chat directo","parameters":[{"type":"string","description":"ID del chat directo","name":"chatId","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 del chat directo","schema":{"type":"array","items":{"$ref":"#/definitions/models.Message"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"404":{"description":"Chat no encontrado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{otherUserId}":{"post":{"security":[{"BearerAuth":[]}],"description":"Crea o encuentra un chat directo entre el usuario autenticado y otro usuario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea un chat directo","parameters":[{"type":"string","description":"ID del otro usuario","name":"otherUserId","in":"path","required":true}],"responses":{"201":{"description":"Chat directo creado o encontrado","schema":{"$ref":"#/definitions/models.DirectChat"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas ordenadas por fecha de actualización descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene todas las salas","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Crea una nueva sala de chat con el usuario actual como propietario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea una nueva sala de chat","parameters":[{"description":"Detalles de la sala","name":"room","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateRoomRequest"}}],"responses":{"201":{"description":"Sala creada exitosamente","schema":{"$ref":"#/definitions/models.Room"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas a las que pertenece el usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene las salas del usuario","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los detalles de una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene una sala por ID","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Detalles de la sala","schema":{"$ref":"#/definitions/models.Room"}},"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/rooms/{roomId}/join":{"post":{"security":[{"BearerAuth":[]}],"description":"Permite al usuario autenticado unirse a una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Unirse a una sala","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Usuario unido exitosamente","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"403":{"description":"No permitido unirse a esta sala","schema":{"type":"string"}},"404":{"description":"Sala no encontrada","schema":{"type":"string"}},"409":{"description":"Usuario ya es miembro de la sala","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de una sala","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"},{"type":"string","default":"\"1747441934\"","description":"Cursor para paginación (timestamp)","name":"cursor","in":"query"}],"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"}}}}},"/chat/ws":{"get":{"description":"Establece una conexión WebSocket para mensajería en tiempo real","consumes":["application/json"],"produces":["application/json"],"tags":["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","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}}}}}},"definitions":{"handlers.PayloadSignUp":{"type":"object","properties":{"displayName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}},"models.CreateRoomRequest":{"type":"object","properties":{"description":{"type":"string"},"isPrivate":{"type":"boolean"},"name":{"type":"string"},"userIds":{"description":"IDs of users to be added to the room","type":"array","items":{"type":"string"}}}},"models.DirectChat":{"type":"object","properties":{"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"updatedAt":{"type":"string"},"userIds":{"type":"array","items":{"type":"string"}}}},"models.Message":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"roomId":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}},"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":{"admins":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"imageUrl":{"type":"string"},"isDeleted":{"type":"boolean"},"isPrivate":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"members":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"ownerId":{"type":"string"},"updatedAt":{"type":"string"}}},"models.User":{"type":"object","properties":{"createdAt":{"description":"BlockedUsers []string `json:\"blockedUsers\" firestore:\"blockedUsers\"`","type":"string"},"displayName":{"type":"string"},"email":{"type":"string"},"isDeleted":{"type":"boolean"},"lastSeen":{"type":"string"},"photoUrl":{"type":"string"},"status":{"type":"string"},"uid":{"type":"string"},"updatedAt":{"type":"string"}}}},"securityDefinitions":{"BearerAuth":{"type":"apiKey","name":"Authorization","in":"header"}}} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5d30edd..dc63573 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: @@ -425,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 @@ -437,15 +468,18 @@ paths: in: query name: limit type: integer + - default: '"1747441934"' + description: Cursor para paginación (timestamp) + in: query + name: cursor + type: string produces: - application/json responses: "200": - description: Lista de mensajes de la sala + description: Mensajes paginados de la sala schema: - items: - $ref: '#/definitions/models.Message' - type: array + $ref: '#/definitions/models.PaginatedMessagesResponse' "401": description: No autorizado schema: diff --git a/internal/handlers/chat_handler.go b/internal/handlers/chat_handler.go index 0a7db5d..ddab36f 100644 --- a/internal/handlers/chat_handler.go +++ b/internal/handlers/chat_handler.go @@ -119,17 +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) -// @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" @@ -147,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 diff --git a/internal/models/message.go b/internal/models/message.go index ba05466..bca0e73 100644 --- a/internal/models/message.go +++ b/internal/models/message.go @@ -18,3 +18,10 @@ 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 8285821..d4bdaae 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,33 +57,58 @@ 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.MessageResponse, 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.Asc). + OrderBy("createdAt", firestore.Desc). // Cambiado a Asc Limit(limit) - docs, err := messagesRef.Documents(ctx).GetAll() + // 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, err + return nil, "", fmt.Errorf("error obtaining messages: %v", err) } // Get messages and track unique user IDs userIDs := make(map[string]bool) - for _, doc := range docs { + + for i, doc := range docs { var message models.Message if err := doc.DataTo(&message); err != nil { - return nil, err + 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 @@ -89,28 +117,26 @@ func (r *MessageRepository) GetRoomMessages(roomID string, limit int) ([]models. // 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 - } + 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 { - msgMap := models.MessageResponse{ - Message: message, + msgResponse := models.MessageResponse{ + Message: message, + DisplayName: userDataCache[message.UserID], // Puede estar vacío si no se encontró } - - // Add displayName if available in cache - if displayName, exists := userDataCache[message.UserID]; exists { - msgMap.DisplayName = displayName - } - - response = append(response, msgMap) + response = append(response, msgResponse) } - return response, nil + return response, nextCursor, nil } // GetDirectChatMessages obtiene los mensajes de un chat directo diff --git a/internal/services/room_service.go b/internal/services/room_service.go index a463ffa..6c53526 100644 --- a/internal/services/room_service.go +++ b/internal/services/room_service.go @@ -51,9 +51,9 @@ 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.MessageResponse, 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) } // GetAllRooms obtiene todas las salas ordenadas por fecha de actualización From f1269d0aa18761f044dd4d3baaf73f5d2948a806 Mon Sep 17 00:00:00 2001 From: Joan Manuel Jaramillo Avila <89425013+LifeRIP@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:49:42 -0500 Subject: [PATCH 6/6] feat: Adds non-paginated room messages retrieval - Introduces a new endpoint for retrieving room messages without pagination, allowing clients to specify a limit. - Updates routing to differentiate between paginated and non-paginated message retrieval. --- docs/docs.go | 926 +++++++++++++++++++- docs/swagger.json | 922 ++++++++++++++++++- docs/swagger.yaml | 42 + internal/handlers/chat_handler.go | 37 + internal/repositories/message_repository.go | 57 ++ internal/routes/router.go | 3 +- internal/services/room_service.go | 5 + 7 files changed, 1987 insertions(+), 5 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 80170f0..5a45120 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,11 +1,931 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" const docTemplate = `{ - "schemes": {{ marshal .Schemes }},"swagger":"2.0","info":{"description":"{{escape .Description}}","title":"{{.Title}}","termsOfService":"https://pachat.online/terms/","contact":{"name":"Parchat Support","url":"https://pachat.online/support","email":"parchat.soporte@gmail.com"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"{{.Version}}"},"host":"{{.Host}}","basePath":"{{.BasePath}}","paths":{"/auth/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los datos del usuario autenticado basado en el ID del contexto","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Obtiene el usuario actual","responses":{"200":{"description":"Datos del usuario actual","schema":{"$ref":"#/definitions/models.User"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/auth/signup":{"post":{"description":"Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Registra un nuevo usuario","parameters":[{"description":"Datos del usuario a registrar","name":"payload","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.PayloadSignUp"}}],"responses":{"201":{"description":"Usuario creado exitosamente","schema":{"$ref":"#/definitions/models.User"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todos los chats directos del usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene chats directos","responses":{"200":{"description":"Lista de chats directos","schema":{"type":"array","items":{"$ref":"#/definitions/models.DirectChat"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{chatId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de un chat directo específico","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de un chat directo","parameters":[{"type":"string","description":"ID del chat directo","name":"chatId","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 del chat directo","schema":{"type":"array","items":{"$ref":"#/definitions/models.Message"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"404":{"description":"Chat no encontrado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{otherUserId}":{"post":{"security":[{"BearerAuth":[]}],"description":"Crea o encuentra un chat directo entre el usuario autenticado y otro usuario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea un chat directo","parameters":[{"type":"string","description":"ID del otro usuario","name":"otherUserId","in":"path","required":true}],"responses":{"201":{"description":"Chat directo creado o encontrado","schema":{"$ref":"#/definitions/models.DirectChat"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas ordenadas por fecha de actualización descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene todas las salas","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Crea una nueva sala de chat con el usuario actual como propietario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea una nueva sala de chat","parameters":[{"description":"Detalles de la sala","name":"room","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateRoomRequest"}}],"responses":{"201":{"description":"Sala creada exitosamente","schema":{"$ref":"#/definitions/models.Room"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas a las que pertenece el usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene las salas del usuario","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los detalles de una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene una sala por ID","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Detalles de la sala","schema":{"$ref":"#/definitions/models.Room"}},"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/rooms/{roomId}/join":{"post":{"security":[{"BearerAuth":[]}],"description":"Permite al usuario autenticado unirse a una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Unirse a una sala","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Usuario unido exitosamente","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"403":{"description":"No permitido unirse a esta sala","schema":{"type":"string"}},"404":{"description":"Sala no encontrada","schema":{"type":"string"}},"409":{"description":"Usuario ya es miembro de la sala","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de una sala","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"},{"type":"string","default":"\"1747441934\"","description":"Cursor para paginación (timestamp)","name":"cursor","in":"query"}],"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"}}}}},"/chat/ws":{"get":{"description":"Establece una conexión WebSocket para mensajería en tiempo real","consumes":["application/json"],"produces":["application/json"],"tags":["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","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}}}}}},"definitions":{"handlers.PayloadSignUp":{"type":"object","properties":{"displayName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}},"models.CreateRoomRequest":{"type":"object","properties":{"description":{"type":"string"},"isPrivate":{"type":"boolean"},"name":{"type":"string"},"userIds":{"description":"IDs of users to be added to the room","type":"array","items":{"type":"string"}}}},"models.DirectChat":{"type":"object","properties":{"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"updatedAt":{"type":"string"},"userIds":{"type":"array","items":{"type":"string"}}}},"models.Message":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"roomId":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}},"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":{"admins":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"imageUrl":{"type":"string"},"isDeleted":{"type":"boolean"},"isPrivate":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"members":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"ownerId":{"type":"string"},"updatedAt":{"type":"string"}}},"models.User":{"type":"object","properties":{"createdAt":{"description":"BlockedUsers []string ` + "`" + `json:\"blockedUsers\" firestore:\"blockedUsers\"` + "`" + `","type":"string"},"displayName":{"type":"string"},"email":{"type":"string"},"isDeleted":{"type":"boolean"},"lastSeen":{"type":"string"},"photoUrl":{"type":"string"},"status":{"type":"string"},"uid":{"type":"string"},"updatedAt":{"type":"string"}}}},"securityDefinitions":{"BearerAuth":{"type":"apiKey","name":"Authorization","in":"header"}}}` + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "https://pachat.online/terms/", + "contact": { + "name": "Parchat Support", + "url": "https://pachat.online/support", + "email": "parchat.soporte@gmail.com" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los datos del usuario autenticado basado en el ID del contexto", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Obtiene el usuario actual", + "responses": { + "200": { + "description": "Datos del usuario actual", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/auth/signup": { + "post": { + "description": "Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Registra un nuevo usuario", + "parameters": [ + { + "description": "Datos del usuario a registrar", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.PayloadSignUp" + } + } + ], + "responses": { + "201": { + "description": "Usuario creado exitosamente", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Solicitud inválida", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/direct/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve todos los chats directos del usuario autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene chats directos", + "responses": { + "200": { + "description": "Lista de chats directos", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DirectChat" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/direct/{chatId}/messages": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los mensajes de un chat directo específico", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene mensajes de un chat directo", + "parameters": [ + { + "type": "string", + "description": "ID del chat directo", + "name": "chatId", + "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 del chat directo", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Message" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Chat no encontrado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/direct/{otherUserId}": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Crea o encuentra un chat directo entre el usuario autenticado y otro usuario", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Crea un chat directo", + "parameters": [ + { + "type": "string", + "description": "ID del otro usuario", + "name": "otherUserId", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "Chat directo creado o encontrado", + "schema": { + "$ref": "#/definitions/models.DirectChat" + } + }, + "400": { + "description": "Solicitud inválida", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve todas las salas ordenadas por fecha de actualización descendente", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene todas las salas", + "responses": { + "200": { + "description": "Lista de salas", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Room" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Crea una nueva sala de chat con el usuario actual como propietario", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Crea una nueva sala de chat", + "parameters": [ + { + "description": "Detalles de la sala", + "name": "room", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CreateRoomRequest" + } + } + ], + "responses": { + "201": { + "description": "Sala creada exitosamente", + "schema": { + "$ref": "#/definitions/models.Room" + } + }, + "400": { + "description": "Solicitud inválida", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve todas las salas a las que pertenece el usuario autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene las salas del usuario", + "responses": { + "200": { + "description": "Lista de salas", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Room" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms/{roomId}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los detalles de una sala específica", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene una sala por ID", + "parameters": [ + { + "type": "string", + "description": "ID de la sala", + "name": "roomId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Detalles de la sala", + "schema": { + "$ref": "#/definitions/models.Room" + } + }, + "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/rooms/{roomId}/join": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Permite al usuario autenticado unirse a una sala específica", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Unirse a una sala", + "parameters": [ + { + "type": "string", + "description": "ID de la sala", + "name": "roomId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Usuario unido exitosamente", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "403": { + "description": "No permitido unirse a esta sala", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Sala no encontrada", + "schema": { + "type": "string" + } + }, + "409": { + "description": "Usuario ya es miembro de la sala", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms/{roomId}/messages": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene mensajes de una sala", + "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" + }, + { + "type": "string", + "default": "\"1747441934\"", + "description": "Cursor para paginación (timestamp)", + "name": "cursor", + "in": "query" + } + ], + "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" + } + } + } + } + }, + "/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" + ], + "produces": [ + "application/json" + ], + "tags": [ + "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", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "handlers.PayloadSignUp": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "models.CreateRoomRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "userIds": { + "description": "IDs of users to be added to the room", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "models.DirectChat": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "lastMessage": { + "$ref": "#/definitions/models.Message" + }, + "updatedAt": { + "type": "string" + }, + "userIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "models.Message": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "roomId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "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": { + "admins": { + "type": "array", + "items": { + "type": "string" + } + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "isPrivate": { + "type": "boolean" + }, + "lastMessage": { + "$ref": "#/definitions/models.Message" + }, + "members": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.User": { + "type": "object", + "properties": { + "createdAt": { + "description": "BlockedUsers []string ` + "`" + `json:\"blockedUsers\" firestore:\"blockedUsers\"` + "`" + `", + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "lastSeen": { + "type": "string" + }, + "photoUrl": { + "type": "string" + }, + "status": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ diff --git a/docs/swagger.json b/docs/swagger.json index beefb17..a6d059c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1 +1,921 @@ -{"swagger":"2.0","info":{"description":"This is Parchat API.","title":"Parchat API","termsOfService":"https://pachat.online/terms/","contact":{"name":"Parchat Support","url":"https://pachat.online/support","email":"parchat.soporte@gmail.com"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"basePath":"/api/v1/","paths":{"/auth/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los datos del usuario autenticado basado en el ID del contexto","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Obtiene el usuario actual","responses":{"200":{"description":"Datos del usuario actual","schema":{"$ref":"#/definitions/models.User"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/auth/signup":{"post":{"description":"Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore","consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Registra un nuevo usuario","parameters":[{"description":"Datos del usuario a registrar","name":"payload","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.PayloadSignUp"}}],"responses":{"201":{"description":"Usuario creado exitosamente","schema":{"$ref":"#/definitions/models.User"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todos los chats directos del usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene chats directos","responses":{"200":{"description":"Lista de chats directos","schema":{"type":"array","items":{"$ref":"#/definitions/models.DirectChat"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{chatId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de un chat directo específico","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de un chat directo","parameters":[{"type":"string","description":"ID del chat directo","name":"chatId","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 del chat directo","schema":{"type":"array","items":{"$ref":"#/definitions/models.Message"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"404":{"description":"Chat no encontrado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/direct/{otherUserId}":{"post":{"security":[{"BearerAuth":[]}],"description":"Crea o encuentra un chat directo entre el usuario autenticado y otro usuario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea un chat directo","parameters":[{"type":"string","description":"ID del otro usuario","name":"otherUserId","in":"path","required":true}],"responses":{"201":{"description":"Chat directo creado o encontrado","schema":{"$ref":"#/definitions/models.DirectChat"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas ordenadas por fecha de actualización descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene todas las salas","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Crea una nueva sala de chat con el usuario actual como propietario","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Crea una nueva sala de chat","parameters":[{"description":"Detalles de la sala","name":"room","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateRoomRequest"}}],"responses":{"201":{"description":"Sala creada exitosamente","schema":{"$ref":"#/definitions/models.Room"}},"400":{"description":"Solicitud inválida","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/me":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve todas las salas a las que pertenece el usuario autenticado","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene las salas del usuario","responses":{"200":{"description":"Lista de salas","schema":{"type":"array","items":{"$ref":"#/definitions/models.Room"}}},"401":{"description":"No autorizado","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los detalles de una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene una sala por ID","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Detalles de la sala","schema":{"$ref":"#/definitions/models.Room"}},"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/rooms/{roomId}/join":{"post":{"security":[{"BearerAuth":[]}],"description":"Permite al usuario autenticado unirse a una sala específica","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Unirse a una sala","parameters":[{"type":"string","description":"ID de la sala","name":"roomId","in":"path","required":true}],"responses":{"200":{"description":"Usuario unido exitosamente","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}},"403":{"description":"No permitido unirse a esta sala","schema":{"type":"string"}},"404":{"description":"Sala no encontrada","schema":{"type":"string"}},"409":{"description":"Usuario ya es miembro de la sala","schema":{"type":"string"}},"500":{"description":"Error interno del servidor","schema":{"type":"string"}}}}},"/chat/rooms/{roomId}/messages":{"get":{"security":[{"BearerAuth":[]}],"description":"Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente","consumes":["application/json"],"produces":["application/json"],"tags":["Chat"],"summary":"Obtiene mensajes de una sala","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"},{"type":"string","default":"\"1747441934\"","description":"Cursor para paginación (timestamp)","name":"cursor","in":"query"}],"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"}}}}},"/chat/ws":{"get":{"description":"Establece una conexión WebSocket para mensajería en tiempo real","consumes":["application/json"],"produces":["application/json"],"tags":["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","schema":{"type":"string"}},"401":{"description":"No autorizado","schema":{"type":"string"}}}}}},"definitions":{"handlers.PayloadSignUp":{"type":"object","properties":{"displayName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}},"models.CreateRoomRequest":{"type":"object","properties":{"description":{"type":"string"},"isPrivate":{"type":"boolean"},"name":{"type":"string"},"userIds":{"description":"IDs of users to be added to the room","type":"array","items":{"type":"string"}}}},"models.DirectChat":{"type":"object","properties":{"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"updatedAt":{"type":"string"},"userIds":{"type":"array","items":{"type":"string"}}}},"models.Message":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"id":{"type":"string"},"isDeleted":{"type":"boolean"},"roomId":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}},"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":{"admins":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"imageUrl":{"type":"string"},"isDeleted":{"type":"boolean"},"isPrivate":{"type":"boolean"},"lastMessage":{"$ref":"#/definitions/models.Message"},"members":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"ownerId":{"type":"string"},"updatedAt":{"type":"string"}}},"models.User":{"type":"object","properties":{"createdAt":{"description":"BlockedUsers []string `json:\"blockedUsers\" firestore:\"blockedUsers\"`","type":"string"},"displayName":{"type":"string"},"email":{"type":"string"},"isDeleted":{"type":"boolean"},"lastSeen":{"type":"string"},"photoUrl":{"type":"string"},"status":{"type":"string"},"uid":{"type":"string"},"updatedAt":{"type":"string"}}}},"securityDefinitions":{"BearerAuth":{"type":"apiKey","name":"Authorization","in":"header"}}} \ No newline at end of file +{ + "swagger": "2.0", + "info": { + "description": "This is Parchat API.", + "title": "Parchat API", + "termsOfService": "https://pachat.online/terms/", + "contact": { + "name": "Parchat Support", + "url": "https://pachat.online/support", + "email": "parchat.soporte@gmail.com" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "basePath": "/api/v1/", + "paths": { + "/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los datos del usuario autenticado basado en el ID del contexto", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Obtiene el usuario actual", + "responses": { + "200": { + "description": "Datos del usuario actual", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/auth/signup": { + "post": { + "description": "Crea un nuevo usuario en Firebase Authentication y lo guarda en Firestore", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Registra un nuevo usuario", + "parameters": [ + { + "description": "Datos del usuario a registrar", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.PayloadSignUp" + } + } + ], + "responses": { + "201": { + "description": "Usuario creado exitosamente", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "400": { + "description": "Solicitud inválida", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/direct/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve todos los chats directos del usuario autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene chats directos", + "responses": { + "200": { + "description": "Lista de chats directos", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DirectChat" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/direct/{chatId}/messages": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los mensajes de un chat directo específico", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene mensajes de un chat directo", + "parameters": [ + { + "type": "string", + "description": "ID del chat directo", + "name": "chatId", + "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 del chat directo", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Message" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Chat no encontrado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/direct/{otherUserId}": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Crea o encuentra un chat directo entre el usuario autenticado y otro usuario", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Crea un chat directo", + "parameters": [ + { + "type": "string", + "description": "ID del otro usuario", + "name": "otherUserId", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "Chat directo creado o encontrado", + "schema": { + "$ref": "#/definitions/models.DirectChat" + } + }, + "400": { + "description": "Solicitud inválida", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve todas las salas ordenadas por fecha de actualización descendente", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene todas las salas", + "responses": { + "200": { + "description": "Lista de salas", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Room" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Crea una nueva sala de chat con el usuario actual como propietario", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Crea una nueva sala de chat", + "parameters": [ + { + "description": "Detalles de la sala", + "name": "room", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CreateRoomRequest" + } + } + ], + "responses": { + "201": { + "description": "Sala creada exitosamente", + "schema": { + "$ref": "#/definitions/models.Room" + } + }, + "400": { + "description": "Solicitud inválida", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve todas las salas a las que pertenece el usuario autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene las salas del usuario", + "responses": { + "200": { + "description": "Lista de salas", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Room" + } + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms/{roomId}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los detalles de una sala específica", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene una sala por ID", + "parameters": [ + { + "type": "string", + "description": "ID de la sala", + "name": "roomId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Detalles de la sala", + "schema": { + "$ref": "#/definitions/models.Room" + } + }, + "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/rooms/{roomId}/join": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Permite al usuario autenticado unirse a una sala específica", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Unirse a una sala", + "parameters": [ + { + "type": "string", + "description": "ID de la sala", + "name": "roomId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Usuario unido exitosamente", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + }, + "403": { + "description": "No permitido unirse a esta sala", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Sala no encontrada", + "schema": { + "type": "string" + } + }, + "409": { + "description": "Usuario ya es miembro de la sala", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Error interno del servidor", + "schema": { + "type": "string" + } + } + } + } + }, + "/chat/rooms/{roomId}/messages": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Devuelve los mensajes de una sala específica con soporte para paginación ordernada por fecha de creación descendente", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Obtiene mensajes de una sala", + "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" + }, + { + "type": "string", + "default": "\"1747441934\"", + "description": "Cursor para paginación (timestamp)", + "name": "cursor", + "in": "query" + } + ], + "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" + } + } + } + } + }, + "/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" + ], + "produces": [ + "application/json" + ], + "tags": [ + "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", + "schema": { + "type": "string" + } + }, + "401": { + "description": "No autorizado", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "handlers.PayloadSignUp": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "models.CreateRoomRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "userIds": { + "description": "IDs of users to be added to the room", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "models.DirectChat": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "lastMessage": { + "$ref": "#/definitions/models.Message" + }, + "updatedAt": { + "type": "string" + }, + "userIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "models.Message": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "roomId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "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": { + "admins": { + "type": "array", + "items": { + "type": "string" + } + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "isPrivate": { + "type": "boolean" + }, + "lastMessage": { + "$ref": "#/definitions/models.Message" + }, + "members": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.User": { + "type": "object", + "properties": { + "createdAt": { + "description": "BlockedUsers []string `json:\"blockedUsers\" firestore:\"blockedUsers\"`", + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "lastSeen": { + "type": "string" + }, + "photoUrl": { + "type": "string" + }, + "status": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index dc63573..8c01db3 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -497,6 +497,48 @@ paths: 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: + "200": + description: Lista de mensajes de la sala + schema: + items: + $ref: '#/definitions/models.MessageResponse' + type: array + "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 (versión simple) + tags: + - Chat /chat/rooms/me: get: consumes: diff --git a/internal/handlers/chat_handler.go b/internal/handlers/chat_handler.go index ddab36f..4a8e754 100644 --- a/internal/handlers/chat_handler.go +++ b/internal/handlers/chat_handler.go @@ -339,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/repositories/message_repository.go b/internal/repositories/message_repository.go index d4bdaae..cc6d5b9 100644 --- a/internal/repositories/message_repository.go +++ b/internal/repositories/message_repository.go @@ -197,3 +197,60 @@ func (r *MessageRepository) GetDirectChatMessages(directChatID string, limit int return response, nil } + +// 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("rooms").Doc(roomID). + Collection("messages"). + OrderBy("createdAt", firestore.Asc). + Limit(limit) + + docs, err := messagesRef.Documents(ctx).GetAll() + if err != nil { + 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 response, nil +} diff --git a/internal/routes/router.go b/internal/routes/router.go index 1033a10..147b58d 100644 --- a/internal/routes/router.go +++ b/internal/routes/router.go @@ -68,7 +68,8 @@ func NewRouter( r.Get("/me", chatHandler.GetUserRooms) r.Get("/", chatHandler.GetAllRooms) r.Get("/{roomId}", chatHandler.GetRoom) - r.Get("/{roomId}/messages", chatHandler.GetRoomMessages) + r.Get("/{roomId}/messages", chatHandler.GetRoomMessagesSimple) + r.Get("/{roomId}/messages/paginated", chatHandler.GetRoomMessages) r.Post("/{roomId}/join", chatHandler.JoinRoom) }) diff --git a/internal/services/room_service.go b/internal/services/room_service.go index 6c53526..9dcb7d3 100644 --- a/internal/services/room_service.go +++ b/internal/services/room_service.go @@ -56,6 +56,11 @@ func (s *RoomService) GetRoomMessages(roomID string, limit int, cursor string) ( 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 func (s *RoomService) GetAllRooms() ([]models.Room, error) { return s.RoomRepo.GetAllRooms()