Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,40 @@ const docTemplate = `{
}
}
}
},
"/user/create": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Verifica si el usuario autenticado existe en la base de datos, si no, lo crea con los datos de autenticación",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Asegura que el usuario exista en la base de datos",
"responses": {
"200": {
"description": "Datos del usuario",
"schema": {
"$ref": "#/definitions/models.User"
}
},
"500": {
"description": "Error interno del servidor",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
Expand Down Expand Up @@ -829,6 +863,10 @@ const docTemplate = `{
"createdAt": {
"type": "string"
},
"displayName": {
"description": "Excluido de Firestore",
"type": "string"
},
"id": {
"type": "string"
},
Expand Down
38 changes: 38 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,40 @@
}
}
}
},
"/user/create": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Verifica si el usuario autenticado existe en la base de datos, si no, lo crea con los datos de autenticación",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Asegura que el usuario exista en la base de datos",
"responses": {
"200": {
"description": "Datos del usuario",
"schema": {
"$ref": "#/definitions/models.User"
}
},
"500": {
"description": "Error interno del servidor",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
Expand Down Expand Up @@ -822,6 +856,10 @@
"createdAt": {
"type": "string"
},
"displayName": {
"description": "Excluido de Firestore",
"type": "string"
},
"id": {
"type": "string"
},
Expand Down
25 changes: 25 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ definitions:
type: string
createdAt:
type: string
displayName:
description: Excluido de Firestore
type: string
id:
type: string
isDeleted:
Expand Down Expand Up @@ -626,6 +629,28 @@ paths:
summary: Conexión WebSocket para chat en tiempo real
tags:
- Chat
/user/create:
post:
consumes:
- application/json
description: Verifica si el usuario autenticado existe en la base de datos,
si no, lo crea con los datos de autenticación
produces:
- application/json
responses:
"200":
description: Datos del usuario
schema:
$ref: '#/definitions/models.User'
"500":
description: Error interno del servidor
schema:
type: string
security:
- BearerAuth: []
summary: Asegura que el usuario exista en la base de datos
tags:
- User
securityDefinitions:
BearerAuth:
in: header
Expand Down
38 changes: 19 additions & 19 deletions internal/handlers/chat_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ func (h *ChatHandler) GetUserRooms(w http.ResponseWriter, r *http.Request) {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param roomId path string true "ID de la sala"
// @Param limit query int false "Límite de mensajes a obtener" default(50)
// @Param cursor query string false "Cursor para paginación (timestamp)" default("1747441934")
// @Param roomId path string true "ID de la sala"
// @Param limit query int false "Límite de mensajes a obtener" default(50)
// @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"
// @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/paginated [get]
func (h *ChatHandler) GetRoomMessages(w http.ResponseWriter, r *http.Request) {
roomID := chi.URLParam(r, "roomId")
Expand Down Expand Up @@ -342,19 +342,19 @@ func (h *ChatHandler) JoinRoom(w http.ResponseWriter, r *http.Request) {

// 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 [get]
// @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 [get]
func (h *ChatHandler) GetRoomMessagesSimple(w http.ResponseWriter, r *http.Request) {
roomID := chi.URLParam(r, "roomId")

Expand Down
43 changes: 42 additions & 1 deletion internal/handlers/user_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import (

type UserHandler struct {
UserService *services.UserService
AuthService *services.AuthService
}

// NewUserHandler crea una nueva instancia de UserHandler
func NewUserHandler(userService *services.UserService) *UserHandler {
func NewUserHandler(userService *services.UserService, authService *services.AuthService) *UserHandler {
return &UserHandler{
UserService: userService,
AuthService: authService,
}
}

Expand All @@ -35,3 +37,42 @@ func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}

// EnsureUserExists verifica si el usuario existe en la base de datos, si no, lo crea con los datos de autenticación
//
// @Summary Asegura que el usuario exista en la base de datos
// @Description Verifica si el usuario autenticado existe en la base de datos, si no, lo crea con los datos de autenticación
// @Tags User
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} models.User "Datos del usuario"
// @Failure 500 {string} string "Error interno del servidor"
// @Router /user/create [post]
func (h *UserHandler) EnsureUserExists(w http.ResponseWriter, r *http.Request) {
// Obtener el ID del usuario del contexto
userID, ok := r.Context().Value("userID").(string)
if !ok {
http.Error(w, "User ID not found in context", http.StatusInternalServerError)
return
}

// Obtener los datos del usuario desde Firebase Auth
authUser, err := h.AuthService.GetUserByID(r.Context(), userID)
if err != nil {
http.Error(w, "Error getting user from auth: "+err.Error(), http.StatusInternalServerError)
return
}
println("Auth User:", authUser.UID, authUser.Email, authUser.DisplayName)

// Asegurar que el usuario exista en la base de datos
user, err := h.UserService.EnsureUserExists(r.Context(), authUser)
if err != nil {
http.Error(w, "Error ensuring user exists: "+err.Error(), http.StatusInternalServerError)
return
}

// Responder con los datos del usuario
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
20 changes: 20 additions & 0 deletions internal/repositories/user_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,23 @@ func (r *UserRepository) CreateUser(user *models.User) error {

return nil
}

// GetUserByID obtiene un usuario de la base de datos por su ID
func (r *UserRepository) GetUserByID(ctx context.Context, userID string) (*models.User, error) {
docRef := r.FirestoreClient.Client.Collection("users").Doc(userID)
docSnap, err := docRef.Get(ctx)
if err != nil {
return nil, err
}

if !docSnap.Exists() {
return nil, nil // El usuario no existe
}

var user models.User
if err := docSnap.DataTo(&user); err != nil {
return nil, err
}

return &user, nil
}
9 changes: 9 additions & 0 deletions internal/routes/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// NewRouter crea un nuevo router HTTP
func NewRouter(
authHandler *handlers.AuthHandler,
userHandler *handlers.UserHandler, // Add userHandler parameter
chatHandler *handlers.ChatHandler,
webSocketHandler *handlers.WebSocketHandler,
authMw *authMiddleware.AuthMiddleware,
Expand Down Expand Up @@ -45,6 +46,14 @@ func NewRouter(
// Rutas protegidas (requieren token)
r.Route("/api/v1", func(r chi.Router) {
// Rutas de usuario
r.Route("/user", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(authMw.VerifyToken) // Aplicar middleware de autenticación
r.Post("/create", userHandler.EnsureUserExists) // Nueva ruta para asegurar que el usuario exista
})
})

// Rutas de autenticación
r.Route("/auth", func(r chi.Router) {
r.Post("/signup", authHandler.SignUpAndCreateUser) // Ruta para registrar y crear un nuevo usuario

Expand Down
28 changes: 28 additions & 0 deletions internal/services/user_service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package services

import (
"context"

"github.com/Parchat/backend/internal/config"
"github.com/Parchat/backend/internal/models"
"github.com/Parchat/backend/internal/repositories"
Expand Down Expand Up @@ -28,3 +30,29 @@ func (s *UserService) CreateUser(user *models.User) error {

return nil
}

// GetUserByID obtiene un usuario de la base de datos por su ID
func (s *UserService) GetUserByID(ctx context.Context, userID string) (*models.User, error) {
return s.UserRepo.GetUserByID(ctx, userID)
}

// EnsureUserExists verifica si el usuario existe en la base de datos, si no, lo crea
func (s *UserService) EnsureUserExists(ctx context.Context, authUser *models.User) (*models.User, error) {
// Verificar si el usuario ya existe en la base de datos
user, err := s.GetUserByID(ctx, authUser.UID)

// Si hay un error pero NO es del tipo "no encontrado", retornamos el error
// Si es un error de "no encontrado" o si no hay error pero user es nil, creamos el usuario
if err == nil && user != nil {
// Usuario encontrado, lo retornamos
return user, nil
}

// Si llegamos aquí, o hubo un error de "usuario no encontrado" o user es nil,
// en ambos casos queremos crear un nuevo usuario
err = s.CreateUser(authUser)
if err != nil {
return nil, err
}
return authUser, nil
}