package handlers import ( "net/http" "github.com/gin-gonic/gin" "github.com/oklog/ulid/v2" "github.com/timetracker/backend/internal/api/middleware" "github.com/timetracker/backend/internal/api/utils" dto "github.com/timetracker/backend/internal/dtos" "github.com/timetracker/backend/internal/models" ) // UserHandler handles user-related API endpoints type UserHandler struct{} // NewUserHandler creates a new UserHandler func NewUserHandler() *UserHandler { return &UserHandler{} } // GetUsers handles GET /users // // @Summary Get all users // @Description Get a list of all users // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} utils.Response{data=[]dto.UserDto} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /users [get] func (h *UserHandler) GetUsers(c *gin.Context) { // Get users from the database users, err := models.GetAllUsers(c.Request.Context()) if err != nil { utils.InternalErrorResponse(c, "Error retrieving users: "+err.Error()) return } // Convert to DTOs userDTOs := make([]dto.UserDto, len(users)) for i, user := range users { userDTOs[i] = convertUserToDTO(&user) } utils.SuccessResponse(c, http.StatusOK, userDTOs) } // GetUserByID handles GET /users/:id // // @Summary Get user by ID // @Description Get a user by their ID // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Success 200 {object} utils.Response{data=dto.UserDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 404 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /users/{id} [get] func (h *UserHandler) GetUserByID(c *gin.Context) { // Parse ID from URL idStr := c.Param("id") id, err := ulid.Parse(idStr) if err != nil { utils.BadRequestResponse(c, "Invalid user ID format") return } // Get user from the database user, err := models.GetUserByID(c.Request.Context(), id) if err != nil { utils.InternalErrorResponse(c, "Error retrieving user: "+err.Error()) return } if user == nil { utils.NotFoundResponse(c, "User not found") return } // Convert to DTO userDTO := convertUserToDTO(user) utils.SuccessResponse(c, http.StatusOK, userDTO) } // CreateUser handles POST /users // // @Summary Create a new user // @Description Create a new user // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param user body dto.UserCreateDto true "User data" // @Success 201 {object} utils.Response{data=dto.UserDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /users [post] func (h *UserHandler) CreateUser(c *gin.Context) { // Parse request body var userCreateDTO dto.UserCreateDto if err := c.ShouldBindJSON(&userCreateDTO); err != nil { utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) return } // Convert DTO to model userCreate := convertCreateDTOToModel(userCreateDTO) // Create user in the database user, err := models.CreateUser(c.Request.Context(), userCreate) if err != nil { utils.InternalErrorResponse(c, "Error creating user: "+err.Error()) return } // Convert to DTO userDTO := convertUserToDTO(user) utils.SuccessResponse(c, http.StatusCreated, userDTO) } // UpdateUser handles PUT /users/:id // // @Summary Update a user // @Description Update an existing user // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Param user body dto.UserUpdateDto true "User data" // @Success 200 {object} utils.Response{data=dto.UserDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 404 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /users/{id} [put] func (h *UserHandler) UpdateUser(c *gin.Context) { // Parse ID from URL idStr := c.Param("id") id, err := ulid.Parse(idStr) if err != nil { utils.BadRequestResponse(c, "Invalid user ID format") return } // Parse request body var userUpdateDTO dto.UserUpdateDto if err := c.ShouldBindJSON(&userUpdateDTO); err != nil { utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) return } // Set ID from URL userUpdateDTO.ID = id.String() // Convert DTO to model userUpdate := convertUpdateDTOToModel(userUpdateDTO) // Update user in the database user, err := models.UpdateUser(c.Request.Context(), userUpdate) if err != nil { utils.InternalErrorResponse(c, "Error updating user: "+err.Error()) return } if user == nil { utils.NotFoundResponse(c, "User not found") return } // Convert to DTO userDTO := convertUserToDTO(user) utils.SuccessResponse(c, http.StatusOK, userDTO) } // DeleteUser handles DELETE /users/:id // // @Summary Delete a user // @Description Delete a user by their ID // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Success 204 {object} utils.Response // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /users/{id} [delete] func (h *UserHandler) DeleteUser(c *gin.Context) { // Parse ID from URL idStr := c.Param("id") id, err := ulid.Parse(idStr) if err != nil { utils.BadRequestResponse(c, "Invalid user ID format") return } // Delete user from the database err = models.DeleteUser(c.Request.Context(), id) if err != nil { utils.InternalErrorResponse(c, "Error deleting user: "+err.Error()) return } utils.SuccessResponse(c, http.StatusNoContent, nil) } // Login handles POST /auth/login // // @Summary Login // @Description Authenticate a user and get a JWT token // @Tags auth // @Accept json // @Produce json // @Param credentials body dto.LoginDto true "Login credentials" // @Success 200 {object} utils.Response{data=dto.TokenDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /auth/login [post] func (h *UserHandler) Login(c *gin.Context) { // Parse request body var loginDTO dto.LoginDto if err := c.ShouldBindJSON(&loginDTO); err != nil { utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) return } // Authenticate user user, err := models.AuthenticateUser(c.Request.Context(), loginDTO.Email, loginDTO.Password) if err != nil { utils.UnauthorizedResponse(c, "Invalid login credentials") return } // Generate JWT token token, err := middleware.GenerateToken(user) if err != nil { utils.InternalErrorResponse(c, "Error generating token: "+err.Error()) return } // Return token tokenDTO := dto.TokenDto{ Token: token, User: convertUserToDTO(user), } utils.SuccessResponse(c, http.StatusOK, tokenDTO) } // Register handles POST /auth/register // // @Summary Register // @Description Register a new user and get a JWT token // @Tags auth // @Accept json // @Produce json // @Param user body dto.UserCreateDto true "User data" // @Success 201 {object} utils.Response{data=dto.TokenDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /auth/register [post] func (h *UserHandler) Register(c *gin.Context) { // Parse request body var userCreateDTO dto.UserCreateDto if err := c.ShouldBindJSON(&userCreateDTO); err != nil { utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) return } // Convert DTO to model userCreate := convertCreateDTOToModel(userCreateDTO) // Create user in the database user, err := models.CreateUser(c.Request.Context(), userCreate) if err != nil { utils.InternalErrorResponse(c, "Error creating user: "+err.Error()) return } // Generate JWT token token, err := middleware.GenerateToken(user) if err != nil { utils.InternalErrorResponse(c, "Error generating token: "+err.Error()) return } // Return token tokenDTO := dto.TokenDto{ Token: token, User: convertUserToDTO(user), } utils.SuccessResponse(c, http.StatusCreated, tokenDTO) } // GetCurrentUser handles GET /auth/me // // @Summary Get current user // @Description Get the currently authenticated user // @Tags auth // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} utils.Response{data=dto.UserDto} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /auth/me [get] func (h *UserHandler) GetCurrentUser(c *gin.Context) { // Get user ID from context (set by AuthMiddleware) userID, err := middleware.GetUserIDFromContext(c) if err != nil { utils.UnauthorizedResponse(c, "User not authenticated") return } // Get user from the database user, err := models.GetUserByID(c.Request.Context(), userID) if err != nil { utils.InternalErrorResponse(c, "Error retrieving user: "+err.Error()) return } if user == nil { utils.NotFoundResponse(c, "User not found") return } // Convert to DTO userDTO := convertUserToDTO(user) utils.SuccessResponse(c, http.StatusOK, userDTO) } // Helper functions for DTO conversion func convertUserToDTO(user *models.User) dto.UserDto { return dto.UserDto{ ID: user.ID.String(), CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, Email: user.Email, Role: user.Role, CompanyID: int(user.CompanyID.Time()), // This is a simplification, adjust as needed HourlyRate: user.HourlyRate, } } func convertCreateDTOToModel(dto dto.UserCreateDto) models.UserCreate { // Convert CompanyID from int to ULID (this is a simplification, adjust as needed) companyID, _ := ulid.Parse("01H1VECTJQXS1RVWJT6QG3QJCJ") return models.UserCreate{ Email: dto.Email, Password: dto.Password, Role: dto.Role, CompanyID: companyID, HourlyRate: dto.HourlyRate, } } func convertUpdateDTOToModel(dto dto.UserUpdateDto) models.UserUpdate { id, _ := ulid.Parse(dto.ID) update := models.UserUpdate{ ID: id, } if dto.Email != nil { update.Email = dto.Email } if dto.Password != nil { update.Password = dto.Password } if dto.Role != nil { update.Role = dto.Role } if dto.CompanyID != nil { // Convert CompanyID from int to ULID (this is a simplification, adjust as needed) companyID, _ := ulid.Parse("01H1VECTJQXS1RVWJT6QG3QJCJ") update.CompanyID = &companyID } if dto.HourlyRate != nil { update.HourlyRate = dto.HourlyRate } return update }