// 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 }