63ede5c237
- refactor: move HTTP status and DTO concerns out of model and service layers into API and handler code. - feat: add admin service boundary and route auth through services instead of direct repository access. - test: add architecture and error tests covering package boundaries, redaction, and service behavior. - docs: record the protocol-neutral boundary decision and update architecture and roadmap notes.
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/dhao2001/mygo/internal/api"
|
|
"github.com/dhao2001/mygo/internal/model"
|
|
"github.com/dhao2001/mygo/internal/service"
|
|
)
|
|
|
|
// AdminHandler handles admin-only endpoints for user management.
|
|
type AdminHandler struct {
|
|
adminService *service.AdminService
|
|
}
|
|
|
|
// NewAdminHandler creates an AdminHandler.
|
|
func NewAdminHandler(adminService *service.AdminService) *AdminHandler {
|
|
return &AdminHandler{adminService: adminService}
|
|
}
|
|
|
|
// adminUserResponse exposes all user fields, including status, for admin views.
|
|
type adminUserResponse struct {
|
|
ID string `json:"id"`
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
IsAdmin bool `json:"is_admin"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// adminUserListResponse wraps a paginated list of admin user views.
|
|
type adminUserListResponse struct {
|
|
Users []adminUserResponse `json:"users"`
|
|
Total int64 `json:"total"`
|
|
}
|
|
|
|
func toAdminResponse(u *model.User) adminUserResponse {
|
|
return adminUserResponse{
|
|
ID: u.ID,
|
|
Username: u.Username,
|
|
Email: u.Email,
|
|
IsAdmin: u.IsAdmin,
|
|
Status: u.Status,
|
|
CreatedAt: u.CreatedAt,
|
|
UpdatedAt: u.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
// DeleteUser handles DELETE /api/v1/admin/users/:id — soft-deletes a user.
|
|
func (h *AdminHandler) DeleteUser(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
if err := h.adminService.DeleteUser(c.Request.Context(), id); err != nil {
|
|
api.RespondError(c, err)
|
|
return
|
|
}
|
|
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// GetUser handles GET /api/v1/admin/users/:id — returns a user including deleted.
|
|
func (h *AdminHandler) GetUser(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
user, err := h.adminService.GetUser(c.Request.Context(), id)
|
|
if err != nil {
|
|
api.RespondError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, toAdminResponse(user))
|
|
}
|
|
|
|
// ListUsers handles GET /api/v1/admin/users — returns a paginated list of all users.
|
|
func (h *AdminHandler) ListUsers(c *gin.Context) {
|
|
offset, err := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
|
if err != nil || offset < 0 {
|
|
api.Error(c, http.StatusBadRequest, "invalid offset")
|
|
return
|
|
}
|
|
|
|
limit, err := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
|
if err != nil || limit < 1 {
|
|
api.Error(c, http.StatusBadRequest, "invalid limit")
|
|
return
|
|
}
|
|
if limit > 200 {
|
|
limit = 200
|
|
}
|
|
|
|
users, total, err := h.adminService.ListUsers(c.Request.Context(), offset, limit)
|
|
if err != nil {
|
|
api.RespondError(c, err)
|
|
return
|
|
}
|
|
|
|
items := make([]adminUserResponse, 0, len(users))
|
|
for i := range users {
|
|
items = append(items, toAdminResponse(&users[i]))
|
|
}
|
|
|
|
c.JSON(http.StatusOK, adminUserListResponse{
|
|
Users: items,
|
|
Total: total,
|
|
})
|
|
}
|