134 lines
3.5 KiB
Go
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
|
|
}
|