feat(handler): add AdminHandler with admin routes
This commit is contained in:
@@ -0,0 +1,111 @@
|
|||||||
|
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/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AdminHandler handles admin-only endpoints for user management.
|
||||||
|
type AdminHandler struct {
|
||||||
|
userRepo repository.UserRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdminHandler creates an AdminHandler.
|
||||||
|
func NewAdminHandler(userRepo repository.UserRepository) *AdminHandler {
|
||||||
|
return &AdminHandler{userRepo: userRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.userRepo.Delete(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.userRepo.FindByIDIncludeDeleted(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.userRepo.ListIncludeDeleted(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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ func setupProtectedRoutes(rg *gin.RouterGroup, webApp *app.WebApp) {
|
|||||||
jwtSecret := []byte(webApp.Config.JWT.Secret)
|
jwtSecret := []byte(webApp.Config.JWT.Secret)
|
||||||
accountHandler := handler.NewAccountHandler(webApp.AuthService)
|
accountHandler := handler.NewAccountHandler(webApp.AuthService)
|
||||||
fileHandler := handler.NewFileHandler(webApp.FileService)
|
fileHandler := handler.NewFileHandler(webApp.FileService)
|
||||||
|
adminHandler := handler.NewAdminHandler(webApp.UserRepo)
|
||||||
|
|
||||||
rg.Use(middleware.AuthRequired(jwtSecret))
|
rg.Use(middleware.AuthRequired(jwtSecret))
|
||||||
|
|
||||||
@@ -36,4 +37,12 @@ func setupProtectedRoutes(rg *gin.RouterGroup, webApp *app.WebApp) {
|
|||||||
files.PUT("/:id", fileHandler.Update)
|
files.PUT("/:id", fileHandler.Update)
|
||||||
files.DELETE("/:id", fileHandler.Delete)
|
files.DELETE("/:id", fileHandler.Delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
admin := rg.Group("/admin")
|
||||||
|
admin.Use(middleware.AdminRequired(webApp.UserRepo))
|
||||||
|
{
|
||||||
|
admin.GET("/users", adminHandler.ListUsers)
|
||||||
|
admin.GET("/users/:id", adminHandler.GetUser)
|
||||||
|
admin.DELETE("/users/:id", adminHandler.DeleteUser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user