feat(admin): add comprehensive admin management system
- Add admin handlers and services for users, posts, comments, groups, and dashboard - Implement role permission management with ability to view and update permissions - Add database seeding for roles and Casbin permission policies - Upgrade Casbin from v2 to v3 with GORM adapter for persistent policy storage - Add admin-specific repository methods with filtering and pagination - Register new admin API routes under /api/v1/admin/*
This commit is contained in:
151
internal/handler/admin_comment_handler.go
Normal file
151
internal/handler/admin_comment_handler.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminCommentHandler 管理端评论处理器
|
||||
type AdminCommentHandler struct {
|
||||
adminCommentService service.AdminCommentService
|
||||
}
|
||||
|
||||
// NewAdminCommentHandler 创建管理端评论处理器
|
||||
func NewAdminCommentHandler(adminCommentService service.AdminCommentService) *AdminCommentHandler {
|
||||
return &AdminCommentHandler{
|
||||
adminCommentService: adminCommentService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommentList 获取评论列表
|
||||
// GET /api/v1/admin/comments
|
||||
func (h *AdminCommentHandler) GetCommentList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
postID := c.Query("post_id")
|
||||
authorID := c.Query("author_id")
|
||||
status := c.Query("status")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
// 验证状态参数
|
||||
if status != "" && status != string(model.CommentStatusDraft) &&
|
||||
status != string(model.CommentStatusPending) &&
|
||||
status != string(model.CommentStatusPublished) &&
|
||||
status != string(model.CommentStatusRejected) &&
|
||||
status != string(model.CommentStatusDeleted) {
|
||||
response.BadRequest(c, "invalid status value")
|
||||
return
|
||||
}
|
||||
|
||||
comments, total, err := h.adminCommentService.GetCommentList(c.Request.Context(), page, pageSize, keyword, postID, authorID, status, startDate, endDate)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get comment list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, comments, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetCommentDetail 获取评论详情
|
||||
// GET /api/v1/admin/comments/:id
|
||||
func (h *AdminCommentHandler) GetCommentDetail(c *gin.Context) {
|
||||
commentID := c.Param("id")
|
||||
if commentID == "" {
|
||||
response.BadRequest(c, "comment id is required")
|
||||
return
|
||||
}
|
||||
|
||||
commentDetail, err := h.adminCommentService.GetCommentDetail(c.Request.Context(), commentID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "comment not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, commentDetail)
|
||||
}
|
||||
|
||||
// DeleteComment 删除评论
|
||||
// DELETE /api/v1/admin/comments/:id
|
||||
func (h *AdminCommentHandler) DeleteComment(c *gin.Context) {
|
||||
commentID := c.Param("id")
|
||||
if commentID == "" {
|
||||
response.BadRequest(c, "comment id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminCommentService.DeleteComment(c.Request.Context(), commentID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to delete comment")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "comment deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// BatchDeleteComments 批量删除评论
|
||||
// POST /api/v1/admin/comments/batch-delete
|
||||
func (h *AdminCommentHandler) BatchDeleteComments(c *gin.Context) {
|
||||
var req dto.AdminBatchDeleteCommentsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.adminCommentService.BatchDeleteComments(c.Request.Context(), req.IDs)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to batch delete comments")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// GetPostComments 获取帖子的评论列表
|
||||
// GET /api/v1/admin/posts/:postId/comments
|
||||
func (h *AdminCommentHandler) GetPostComments(c *gin.Context) {
|
||||
postID := c.Param("postId")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
comments, total, err := h.adminCommentService.GetCommentsByPostID(c.Request.Context(), postID, page, pageSize)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get post comments")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, comments, total, page, pageSize)
|
||||
}
|
||||
100
internal/handler/admin_dashboard_handler.go
Normal file
100
internal/handler/admin_dashboard_handler.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminDashboardHandler 管理端仪表盘处理器
|
||||
type AdminDashboardHandler struct {
|
||||
dashboardService service.AdminDashboardService
|
||||
}
|
||||
|
||||
// NewAdminDashboardHandler 创建管理端仪表盘处理器
|
||||
func NewAdminDashboardHandler(dashboardService service.AdminDashboardService) *AdminDashboardHandler {
|
||||
return &AdminDashboardHandler{
|
||||
dashboardService: dashboardService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetStats 获取统计数据
|
||||
// GET /api/v1/admin/dashboard/stats
|
||||
func (h *AdminDashboardHandler) GetStats(c *gin.Context) {
|
||||
stats, err := h.dashboardService.GetStats(c.Request.Context())
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get dashboard stats")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, stats)
|
||||
}
|
||||
|
||||
// GetUserActivityTrend 获取用户活跃度趋势
|
||||
// GET /api/v1/admin/dashboard/user-activity
|
||||
func (h *AdminDashboardHandler) GetUserActivityTrend(c *gin.Context) {
|
||||
// 解析天数参数,默认7天
|
||||
days, _ := strconv.Atoi(c.DefaultQuery("days", "7"))
|
||||
if days <= 0 {
|
||||
days = 7
|
||||
}
|
||||
if days > 30 {
|
||||
days = 30
|
||||
}
|
||||
|
||||
trend, err := h.dashboardService.GetUserActivityTrend(c.Request.Context(), days)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get user activity trend")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, trend)
|
||||
}
|
||||
|
||||
// GetContentStats 获取内容统计
|
||||
// GET /api/v1/admin/dashboard/content-stats
|
||||
func (h *AdminDashboardHandler) GetContentStats(c *gin.Context) {
|
||||
stats, err := h.dashboardService.GetContentStats(c.Request.Context())
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get content stats")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, stats)
|
||||
}
|
||||
|
||||
// GetPendingContent 获取待审核内容
|
||||
// GET /api/v1/admin/dashboard/pending-content
|
||||
func (h *AdminDashboardHandler) GetPendingContent(c *gin.Context) {
|
||||
// 解析limit参数,默认10条
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if limit > 50 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
content, err := h.dashboardService.GetPendingContent(c.Request.Context(), limit)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get pending content")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, content)
|
||||
}
|
||||
|
||||
// GetOnlineUsers 获取在线用户数
|
||||
// GET /api/v1/admin/dashboard/online-users
|
||||
func (h *AdminDashboardHandler) GetOnlineUsers(c *gin.Context) {
|
||||
onlineUsers, err := h.dashboardService.GetOnlineUsers(c.Request.Context())
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get online users count")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, onlineUsers)
|
||||
}
|
||||
202
internal/handler/admin_group_handler.go
Normal file
202
internal/handler/admin_group_handler.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminGroupHandler 管理端群组处理器
|
||||
type AdminGroupHandler struct {
|
||||
adminGroupService service.AdminGroupService
|
||||
}
|
||||
|
||||
// NewAdminGroupHandler 创建管理端群组处理器
|
||||
func NewAdminGroupHandler(adminGroupService service.AdminGroupService) *AdminGroupHandler {
|
||||
return &AdminGroupHandler{
|
||||
adminGroupService: adminGroupService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetGroupList 获取群组列表
|
||||
// GET /api/v1/admin/groups
|
||||
func (h *AdminGroupHandler) GetGroupList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
ownerID := c.Query("owner_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
groups, total, err := h.adminGroupService.GetGroupList(c.Request.Context(), page, pageSize, keyword, status, ownerID, startDate, endDate)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get group list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, groups, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetGroupDetail 获取群组详情
|
||||
// GET /api/v1/admin/groups/:id
|
||||
func (h *AdminGroupHandler) GetGroupDetail(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
groupDetail, err := h.adminGroupService.GetGroupDetail(c.Request.Context(), groupID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "group not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, groupDetail)
|
||||
}
|
||||
|
||||
// GetGroupMembers 获取群组成员列表
|
||||
// GET /api/v1/admin/groups/:id/members
|
||||
func (h *AdminGroupHandler) GetGroupMembers(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
members, err := h.adminGroupService.GetGroupMembers(c.Request.Context(), groupID, page, pageSize)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to get group members")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, members)
|
||||
}
|
||||
|
||||
// DeleteGroup 解散群组
|
||||
// DELETE /api/v1/admin/groups/:id
|
||||
func (h *AdminGroupHandler) DeleteGroup(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminGroupService.DeleteGroup(c.Request.Context(), groupID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to delete group")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "group deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateGroupStatus 更新群组状态
|
||||
// PUT /api/v1/admin/groups/:id/status
|
||||
func (h *AdminGroupHandler) UpdateGroupStatus(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminUpdateGroupStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != "active" && req.Status != "dissolved" {
|
||||
response.BadRequest(c, "invalid status value, must be 'active' or 'dissolved'")
|
||||
return
|
||||
}
|
||||
|
||||
groupDetail, err := h.adminGroupService.UpdateGroupStatus(c.Request.Context(), groupID, req.Status)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to update group status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, groupDetail)
|
||||
}
|
||||
|
||||
// RemoveMember 移除群组成员
|
||||
// DELETE /api/v1/admin/groups/:id/members/:userId
|
||||
func (h *AdminGroupHandler) RemoveMember(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
userID := c.Param("userId")
|
||||
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
if userID == "" {
|
||||
response.BadRequest(c, "user id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminGroupService.RemoveMember(c.Request.Context(), groupID, userID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to remove member")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "member removed successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// TransferOwner 转让群主
|
||||
// PUT /api/v1/admin/groups/:id/transfer
|
||||
func (h *AdminGroupHandler) TransferOwner(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminTransferGroupOwnerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
groupDetail, err := h.adminGroupService.TransferOwner(c.Request.Context(), groupID, req.NewOwnerID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to transfer owner")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, groupDetail)
|
||||
}
|
||||
228
internal/handler/admin_post_handler.go
Normal file
228
internal/handler/admin_post_handler.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminPostHandler 管理端帖子处理器
|
||||
type AdminPostHandler struct {
|
||||
adminPostService service.AdminPostService
|
||||
}
|
||||
|
||||
// NewAdminPostHandler 创建管理端帖子处理器
|
||||
func NewAdminPostHandler(adminPostService service.AdminPostService) *AdminPostHandler {
|
||||
return &AdminPostHandler{
|
||||
adminPostService: adminPostService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPostList 获取帖子列表
|
||||
// GET /api/v1/admin/posts
|
||||
func (h *AdminPostHandler) GetPostList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
authorID := c.Query("author_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
// 验证状态参数
|
||||
if status != "" && status != string(model.PostStatusDraft) &&
|
||||
status != string(model.PostStatusPending) &&
|
||||
status != string(model.PostStatusPublished) &&
|
||||
status != string(model.PostStatusRejected) {
|
||||
response.BadRequest(c, "invalid status value")
|
||||
return
|
||||
}
|
||||
|
||||
posts, total, err := h.adminPostService.GetPostList(c.Request.Context(), page, pageSize, keyword, status, authorID, startDate, endDate)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get post list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, posts, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetPostDetail 获取帖子详情
|
||||
// GET /api/v1/admin/posts/:id
|
||||
func (h *AdminPostHandler) GetPostDetail(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
postDetail, err := h.adminPostService.GetPostDetail(c.Request.Context(), postID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "post not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
|
||||
// DeletePost 删除帖子
|
||||
// DELETE /api/v1/admin/posts/:id
|
||||
func (h *AdminPostHandler) DeletePost(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminPostService.DeletePost(c.Request.Context(), postID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to delete post")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "post deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// ModeratePost 审核帖子
|
||||
// PUT /api/v1/admin/posts/:id/moderate
|
||||
func (h *AdminPostHandler) ModeratePost(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminModeratePostRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != string(model.PostStatusPublished) && req.Status != string(model.PostStatusRejected) {
|
||||
response.BadRequest(c, "invalid status value, must be 'published' or 'rejected'")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID作为审核人
|
||||
reviewedBy := c.GetString("user_id")
|
||||
|
||||
postDetail, err := h.adminPostService.ModeratePost(c.Request.Context(), postID, model.PostStatus(req.Status), req.Reason, reviewedBy)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to moderate post")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
|
||||
// BatchDeletePosts 批量删除帖子
|
||||
// POST /api/v1/admin/posts/batch-delete
|
||||
func (h *AdminPostHandler) BatchDeletePosts(c *gin.Context) {
|
||||
var req dto.AdminBatchDeletePostsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.adminPostService.BatchDeletePosts(c.Request.Context(), req.IDs)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to batch delete posts")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// BatchModeratePosts 批量审核帖子
|
||||
// POST /api/v1/admin/posts/batch-moderate
|
||||
func (h *AdminPostHandler) BatchModeratePosts(c *gin.Context) {
|
||||
var req dto.AdminBatchModeratePostsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != string(model.PostStatusPublished) && req.Status != string(model.PostStatusRejected) {
|
||||
response.BadRequest(c, "invalid status value, must be 'published' or 'rejected'")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID作为审核人
|
||||
reviewedBy := c.GetString("user_id")
|
||||
|
||||
result, err := h.adminPostService.BatchModeratePosts(c.Request.Context(), req.IDs, model.PostStatus(req.Status), reviewedBy)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to batch moderate posts")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// SetPostPin 置顶/取消置顶帖子
|
||||
// PUT /api/v1/admin/posts/:id/pin
|
||||
func (h *AdminPostHandler) SetPostPin(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminSetPostPinRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
postDetail, err := h.adminPostService.SetPostPin(c.Request.Context(), postID, req.IsPinned)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to set post pin status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
|
||||
// SetPostFeature 加精/取消加精帖子
|
||||
// PUT /api/v1/admin/posts/:id/feature
|
||||
func (h *AdminPostHandler) SetPostFeature(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminSetPostFeatureRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
postDetail, err := h.adminPostService.SetPostFeature(c.Request.Context(), postID, req.IsFeatured)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to set post feature status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
105
internal/handler/admin_user_handler.go
Normal file
105
internal/handler/admin_user_handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminUserHandler 管理端用户处理器
|
||||
type AdminUserHandler struct {
|
||||
adminUserService service.AdminUserService
|
||||
}
|
||||
|
||||
// NewAdminUserHandler 创建管理端用户处理器
|
||||
func NewAdminUserHandler(adminUserService service.AdminUserService) *AdminUserHandler {
|
||||
return &AdminUserHandler{
|
||||
adminUserService: adminUserService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
// GET /api/v1/admin/users
|
||||
func (h *AdminUserHandler) GetUserList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
|
||||
// 验证状态参数
|
||||
if status != "" && status != string(model.UserStatusActive) && status != string(model.UserStatusBanned) {
|
||||
response.BadRequest(c, "invalid status value")
|
||||
return
|
||||
}
|
||||
|
||||
users, total, err := h.adminUserService.GetUserList(c.Request.Context(), page, pageSize, keyword, status)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get user list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, users, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetUserDetail 获取用户详情
|
||||
// GET /api/v1/admin/users/:id
|
||||
func (h *AdminUserHandler) GetUserDetail(c *gin.Context) {
|
||||
userID := c.Param("id")
|
||||
if userID == "" {
|
||||
response.BadRequest(c, "user id is required")
|
||||
return
|
||||
}
|
||||
|
||||
userDetail, err := h.adminUserService.GetUserDetail(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, userDetail)
|
||||
}
|
||||
|
||||
// UpdateUserStatus 更新用户状态
|
||||
// PUT /api/v1/admin/users/:id/status
|
||||
func (h *AdminUserHandler) UpdateUserStatus(c *gin.Context) {
|
||||
userID := c.Param("id")
|
||||
if userID == "" {
|
||||
response.BadRequest(c, "user id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminUpdateUserStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != string(model.UserStatusActive) && req.Status != string(model.UserStatusBanned) {
|
||||
response.BadRequest(c, "invalid status value, must be 'active' or 'banned'")
|
||||
return
|
||||
}
|
||||
|
||||
userDetail, err := h.adminUserService.UpdateUserStatus(c.Request.Context(), userID, model.UserStatus(req.Status))
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to update user status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, userDetail)
|
||||
}
|
||||
@@ -202,3 +202,128 @@ func (h *RoleHandler) handleError(c *gin.Context, err error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetRoleDetail 获取角色详情
|
||||
// GET /api/v1/admin/roles/:name
|
||||
func (h *RoleHandler) GetRoleDetail(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "缺少角色名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
detail, err := h.roleService.GetRoleDetail(c.Request.Context(), name)
|
||||
if err != nil {
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": detail,
|
||||
})
|
||||
}
|
||||
|
||||
// GetRolePermissions 获取角色权限
|
||||
// GET /api/v1/admin/roles/:name/permissions
|
||||
func (h *RoleHandler) GetRolePermissions(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "缺少角色名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
permissions, err := h.roleService.GetRolePermissions(c.Request.Context(), name)
|
||||
if err != nil {
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": permissions,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllPermissions 获取所有权限定义
|
||||
// GET /api/v1/admin/permissions
|
||||
func (h *RoleHandler) GetAllPermissions(c *gin.Context) {
|
||||
permissions, err := h.roleService.GetAllPermissions(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": "INTERNAL_ERROR",
|
||||
"message": "获取权限列表失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": permissions,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRolePermissionsRequest 更新角色权限请求
|
||||
type UpdateRolePermissionsRequest struct {
|
||||
Permissions []PermissionInput `json:"permissions" binding:"required"`
|
||||
}
|
||||
|
||||
// PermissionInput 权限输入
|
||||
type PermissionInput struct {
|
||||
Resource string `json:"resource" binding:"required"`
|
||||
Action string `json:"action" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateRolePermissions 更新角色权限
|
||||
// PUT /api/v1/admin/roles/:name/permissions
|
||||
func (h *RoleHandler) UpdateRolePermissions(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "缺少角色名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateRolePermissionsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 转换权限格式
|
||||
var permissions []service.Permission
|
||||
for _, p := range req.Permissions {
|
||||
permissions = append(permissions, service.Permission{
|
||||
Resource: p.Resource,
|
||||
Action: p.Action,
|
||||
})
|
||||
}
|
||||
|
||||
err := h.roleService.UpdateRolePermissions(c.Request.Context(), name, permissions)
|
||||
if err != nil {
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的角色信息
|
||||
detail, err := h.roleService.GetRoleDetail(c.Request.Context(), name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "权限更新成功",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "权限更新成功",
|
||||
"data": detail,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user