Cybersecurity-Projects/PROJECTS/advanced/monitor-the-situation-dashb.../backend/internal/user/handler.go

319 lines
7.1 KiB
Go

// AngelaMos | 2026
// handler.go
package user
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/core"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/middleware"
)
type Handler struct {
service *Service
validator *validator.Validate
}
func NewHandler(service *Service) *Handler {
return &Handler{
service: service,
validator: validator.New(validator.WithRequiredStructEnabled()),
}
}
func (h *Handler) RegisterRoutes(
r chi.Router,
authenticator func(http.Handler) http.Handler,
) {
r.Route("/users", func(r chi.Router) {
r.Use(authenticator)
r.Get("/me", h.GetMe)
r.Put("/me", h.UpdateMe)
r.Put("/me/email", h.UpdateMyEmail)
r.Delete("/me", h.DeleteMe)
})
}
func (h *Handler) GetMe(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
user, err := h.service.GetMe(r.Context(), userID)
if err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
core.InternalServerError(w, err)
return
}
core.OK(w, ToUserResponse(user))
}
func (h *Handler) UpdateMe(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
var req UpdateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
core.BadRequest(w, "invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
core.BadRequest(w, core.FormatValidationError(err))
return
}
user, err := h.service.UpdateMe(r.Context(), userID, req)
if err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
core.InternalServerError(w, err)
return
}
core.OK(w, ToUserResponse(user))
}
// UpdateMyEmail changes the authenticated user's email after re-verifying
// their password.
func (h *Handler) UpdateMyEmail(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
var req UpdateEmailRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
core.BadRequest(w, "invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
core.BadRequest(w, core.FormatValidationError(err))
return
}
user, err := h.service.UpdateEmail(
r.Context(),
userID,
req.CurrentPassword,
req.NewEmail,
)
if err != nil {
if errors.Is(err, core.ErrUnauthorized) {
core.JSONError(
w,
core.UnauthorizedError("current password is incorrect"),
)
return
}
if errors.Is(err, core.ErrDuplicateKey) {
core.JSONError(w, core.DuplicateError("email"))
return
}
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
if errors.Is(err, core.ErrInvalidInput) {
core.BadRequest(w, err.Error())
return
}
core.InternalServerError(w, err)
return
}
core.OK(w, ToUserResponse(user))
}
func (h *Handler) DeleteMe(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
if err := h.service.DeleteMe(r.Context(), userID); err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
core.InternalServerError(w, err)
return
}
core.NoContent(w)
}
// RegisterAdminRoutes registers admin-only user management endpoints.
func (h *Handler) RegisterAdminRoutes(
r chi.Router,
authenticator, adminOnly func(http.Handler) http.Handler,
) {
r.Route("/admin/users", func(r chi.Router) {
r.Use(authenticator)
r.Use(adminOnly)
r.Get("/", h.ListUsers)
r.Post("/", h.AdminCreateUser)
r.Get("/{userID}", h.GetUser)
r.Put("/{userID}", h.AdminUpdateUser)
r.Delete("/{userID}", h.DeleteUser)
})
}
// ListUsers returns a paginated list of users with optional filtering.
func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) {
params := ListUsersParams{
Page: parseIntQuery(r, "page", 1),
PageSize: parseIntQuery(r, "page_size", 20),
Search: r.URL.Query().Get("search"),
Role: r.URL.Query().Get("role"),
Tier: r.URL.Query().Get("tier"),
}
users, total, err := h.service.ListUsers(r.Context(), params)
if err != nil {
core.InternalServerError(w, err)
return
}
core.Paginated(
w,
ToUserResponseList(users),
params.Page,
params.PageSize,
total,
)
}
// GetUser returns a specific user by ID (admin only).
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
user, err := h.service.GetUser(r.Context(), userID)
if err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
core.InternalServerError(w, err)
return
}
core.OK(w, ToUserResponse(user))
}
// AdminUpdateUser updates a specific user — name, role, tier (admin only).
func (h *Handler) AdminUpdateUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
var req AdminUpdateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
core.BadRequest(w, "invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
core.BadRequest(w, core.FormatValidationError(err))
return
}
user, err := h.service.AdminUpdateUser(r.Context(), userID, req)
if err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
if errors.Is(err, core.ErrInvalidInput) {
core.BadRequest(w, err.Error())
return
}
core.InternalServerError(w, err)
return
}
core.OK(w, ToUserResponse(user))
}
// AdminCreateUser creates a new user (admin only).
func (h *Handler) AdminCreateUser(w http.ResponseWriter, r *http.Request) {
var req AdminCreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
core.BadRequest(w, "invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
core.BadRequest(w, core.FormatValidationError(err))
return
}
user, err := h.service.AdminCreateUser(r.Context(), req)
if err != nil {
if errors.Is(err, core.ErrDuplicateKey) {
core.JSONError(w, core.DuplicateError("email"))
return
}
if errors.Is(err, core.ErrInvalidInput) {
core.BadRequest(w, err.Error())
return
}
core.InternalServerError(w, err)
return
}
core.Created(w, ToUserResponse(user))
}
// DeleteUser soft deletes a user account (admin only).
func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
requesterID := middleware.GetUserID(r.Context())
targetID := chi.URLParam(r, "userID")
if err := h.service.CanDeleteUser(
r.Context(),
requesterID,
targetID,
); err != nil {
if errors.Is(err, core.ErrForbidden) {
core.Forbidden(w, "insufficient permissions")
return
}
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
core.InternalServerError(w, err)
return
}
if err := h.service.DeleteUser(r.Context(), targetID); err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "user")
return
}
core.InternalServerError(w, err)
return
}
core.NoContent(w)
}
func parseIntQuery(r *http.Request, key string, defaultVal int) int {
val := r.URL.Query().Get(key)
if val == "" {
return defaultVal
}
parsed, err := strconv.Atoi(val)
if err != nil {
return defaultVal
}
return parsed
}