408 lines
11 KiB
Go
408 lines
11 KiB
Go
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"
|
|
"github.com/timetracker/backend/internal/types"
|
|
)
|
|
|
|
// 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(), types.FromULID(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 := types.ULIDFromString(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
|
|
}
|
|
|
|
// Convert DTO to Model
|
|
update := models.UserUpdate{
|
|
ID: id,
|
|
}
|
|
|
|
if userUpdateDTO.Email != nil {
|
|
update.Email = userUpdateDTO.Email
|
|
}
|
|
if userUpdateDTO.Password != nil {
|
|
update.Password = userUpdateDTO.Password
|
|
}
|
|
if userUpdateDTO.Role != nil {
|
|
update.Role = userUpdateDTO.Role
|
|
}
|
|
|
|
if userUpdateDTO.CompanyID.Valid {
|
|
if userUpdateDTO.CompanyID.Value != nil {
|
|
companyID, err := types.ULIDFromString(*userUpdateDTO.CompanyID.Value)
|
|
if err != nil {
|
|
utils.BadRequestResponse(c, "Invalid company ID format")
|
|
return
|
|
}
|
|
update.CompanyID = types.NewNullable(companyID)
|
|
} else {
|
|
update.CompanyID = types.Null[types.ULID]()
|
|
}
|
|
}
|
|
|
|
if userUpdateDTO.HourlyRate != nil {
|
|
update.HourlyRate = userUpdateDTO.HourlyRate
|
|
}
|
|
// Update user in the database
|
|
user, err := models.UpdateUser(c.Request.Context(), update)
|
|
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(), types.FromULID(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, c)
|
|
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, c)
|
|
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 {
|
|
var companyID *string
|
|
if user.CompanyID != nil {
|
|
s := user.CompanyID.String()
|
|
companyID = &s
|
|
}
|
|
return dto.UserDto{
|
|
ID: user.ID.String(),
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
CompanyID: companyID,
|
|
HourlyRate: user.HourlyRate,
|
|
}
|
|
}
|
|
|
|
func convertCreateDTOToModel(dto dto.UserCreateDto) models.UserCreate {
|
|
var companyID *types.ULID
|
|
if dto.CompanyID != nil {
|
|
wrapper, _ := types.ULIDFromString(*dto.CompanyID) // Ignoring error, validation happens in the model
|
|
companyID = &wrapper
|
|
}
|
|
|
|
return models.UserCreate{
|
|
Email: dto.Email,
|
|
Password: dto.Password,
|
|
Role: dto.Role,
|
|
CompanyID: companyID,
|
|
HourlyRate: dto.HourlyRate,
|
|
}
|
|
}
|