Files
BoostAI/Backend/internal/handlers/api/users/handler.go
2026-05-25 17:05:06 +01:00

134 lines
3.5 KiB
Go

// Path: Backend/internal/handlers/api/users/handler.go
package users
import (
"boostai-backend/internal/handlers/api/shared"
"boostai-backend/internal/http/params"
"boostai-backend/internal/http/respond"
"boostai-backend/internal/sqlc"
"errors"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
)
type Handler struct {
queries *sqlc.Queries
}
type UserResponse struct {
ID int64 `json:"id"`
Email string `json:"email"`
Role string `json:"role"`
FullName string `json:"full_name"`
IsActive bool `json:"is_active"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
PasswordHash *string `json:"password_hash,omitempty"`
}
type createUserRequest struct {
Email string `json:"email"`
Password *string `json:"password"`
Role string `json:"role"`
FullName string `json:"full_name"`
}
func NewHandler(queries *sqlc.Queries) *Handler {
return &Handler{queries: queries}
}
func (h *Handler) ListUsersByRole(c *fiber.Ctx) error {
role := strings.TrimSpace(c.Query("role"))
if role == "" {
return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Query parameter 'role' is required")
}
ctx, cancel := shared.WithTimeout()
defer cancel()
users, err := h.queries.ListUsersByRole(ctx, sqlc.UserRole(role))
if err != nil {
return respond.DatabaseError(c, err)
}
items := make([]UserResponse, 0, len(users))
for _, user := range users {
items = append(items, mapUser(user, false))
}
return c.JSON(shared.ListResponse[UserResponse]{Data: items})
}
func (h *Handler) GetUserByID(c *fiber.Ctx) error {
id, err := params.Int64PathParam(c, "id")
if err != nil {
return err
}
ctx, cancel := shared.WithTimeout()
defer cancel()
user, err := h.queries.GetUserByID(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return respond.Error(c, fiber.StatusNotFound, "not_found", "User not found")
}
return respond.DatabaseError(c, err)
}
return c.JSON(mapUser(user, false))
}
func (h *Handler) CreateUser(c *fiber.Ctx) error {
var req createUserRequest
if err := c.BodyParser(&req); err != nil {
return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Unable to parse request body")
}
if strings.TrimSpace(req.Email) == "" || strings.TrimSpace(req.FullName) == "" || strings.TrimSpace(req.Role) == "" {
return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "email, full_name, and role are required")
}
passwordHash, err := shared.MaybeHashPassword(req.Password)
if err != nil {
return respond.Error(c, fiber.StatusBadRequest, "invalid_request", err.Error())
}
ctx, cancel := shared.WithTimeout()
defer cancel()
user, err := h.queries.CreateUser(ctx, sqlc.CreateUserParams{
Email: strings.TrimSpace(req.Email),
PasswordHash: passwordHash,
Role: sqlc.UserRole(strings.TrimSpace(req.Role)),
FullName: strings.TrimSpace(req.FullName),
})
if err != nil {
return respond.DatabaseError(c, err)
}
return c.Status(fiber.StatusCreated).JSON(mapUser(user, false))
}
func mapUser(user sqlc.User, includePasswordHash bool) UserResponse {
response := UserResponse{
ID: user.ID,
Email: user.Email,
Role: string(user.Role),
FullName: user.FullName,
IsActive: user.IsActive,
CreatedAt: shared.TimePointer(user.CreatedAt),
UpdatedAt: shared.TimePointer(user.UpdatedAt),
}
if includePasswordHash {
response.PasswordHash = shared.TextPointer(user.PasswordHash)
}
return response
}