feat(moderation): add user profile content moderation for avatars, covers, and bios
This commit is contained in:
129
internal/handler/admin_profile_audit_handler.go
Normal file
129
internal/handler/admin_profile_audit_handler.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AdminProfileAuditHandler struct {
|
||||
profileAuditService service.UserProfileAuditService
|
||||
}
|
||||
|
||||
func NewAdminProfileAuditHandler(profileAuditService service.UserProfileAuditService) *AdminProfileAuditHandler {
|
||||
return &AdminProfileAuditHandler{
|
||||
profileAuditService: profileAuditService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminProfileAuditHandler) GetPendingList(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
|
||||
}
|
||||
|
||||
contentType := c.Query("content_type")
|
||||
var ct model.UserProfileContentType
|
||||
switch contentType {
|
||||
case "avatar":
|
||||
ct = model.UserProfileContentTypeAvatar
|
||||
case "cover":
|
||||
ct = model.UserProfileContentTypeCover
|
||||
case "bio":
|
||||
ct = model.UserProfileContentTypeBio
|
||||
default:
|
||||
ct = ""
|
||||
}
|
||||
|
||||
audits, total, err := h.profileAuditService.GetPendingList(c.Request.Context(), ct, page, pageSize)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get pending profile audits")
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]map[string]any, 0, len(audits))
|
||||
for _, audit := range audits {
|
||||
item := map[string]any{
|
||||
"id": audit.ID,
|
||||
"user_id": audit.UserID,
|
||||
"content_type": audit.ContentType,
|
||||
"content": audit.Content,
|
||||
"status": audit.Status,
|
||||
"created_at": audit.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
if audit.User != nil {
|
||||
item["user"] = map[string]any{
|
||||
"id": audit.User.ID,
|
||||
"username": audit.User.Username,
|
||||
"nickname": audit.User.Nickname,
|
||||
"avatar": audit.User.Avatar,
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
response.Paginated(c, items, total, page, pageSize)
|
||||
}
|
||||
|
||||
func (h *AdminProfileAuditHandler) ModerateAudit(c *gin.Context) {
|
||||
auditID := c.Param("id")
|
||||
if auditID == "" {
|
||||
response.BadRequest(c, "audit id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != string(model.UserProfileAuditStatusApproved) && req.Status != string(model.UserProfileAuditStatusRejected) {
|
||||
response.BadRequest(c, "invalid status, must be 'approved' or 'rejected'")
|
||||
return
|
||||
}
|
||||
|
||||
reviewedBy := c.GetString("user_id")
|
||||
if err := h.profileAuditService.ModerateAudit(c.Request.Context(), auditID, model.UserProfileAuditStatus(req.Status), req.Reason, reviewedBy); err != nil {
|
||||
response.HandleError(c, err, "failed to moderate profile audit")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, map[string]any{"success": true})
|
||||
}
|
||||
|
||||
func (h *AdminProfileAuditHandler) BatchModerate(c *gin.Context) {
|
||||
var req struct {
|
||||
IDs []string `json:"ids" binding:"required"`
|
||||
Status string `json:"status" binding:"required"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != string(model.UserProfileAuditStatusApproved) && req.Status != string(model.UserProfileAuditStatusRejected) {
|
||||
response.BadRequest(c, "invalid status, must be 'approved' or 'rejected'")
|
||||
return
|
||||
}
|
||||
|
||||
reviewedBy := c.GetString("user_id")
|
||||
if err := h.profileAuditService.BatchModerateAudits(c.Request.Context(), req.IDs, model.UserProfileAuditStatus(req.Status), req.Reason, reviewedBy); err != nil {
|
||||
response.InternalServerError(c, "failed to batch moderate profile audits")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, map[string]any{"success": true})
|
||||
}
|
||||
@@ -32,13 +32,13 @@ func normalizePagination(page, pageSize int) (int, int) {
|
||||
|
||||
// UserHandler 用户处理器
|
||||
type UserHandler struct {
|
||||
userService service.UserService
|
||||
activityService service.UserActivityService // 用户活跃统计服务
|
||||
jwtService *service.JWTService
|
||||
logService *service.LogService
|
||||
userService service.UserService
|
||||
activityService service.UserActivityService
|
||||
profileAuditService service.UserProfileAuditService
|
||||
jwtService *service.JWTService
|
||||
logService *service.LogService
|
||||
}
|
||||
|
||||
// NewUserHandler 创建用户处理器
|
||||
func NewUserHandler(userService service.UserService, activityService service.UserActivityService, logService *service.LogService) *UserHandler {
|
||||
return &UserHandler{
|
||||
userService: userService,
|
||||
@@ -47,6 +47,10 @@ func NewUserHandler(userService service.UserService, activityService service.Use
|
||||
}
|
||||
}
|
||||
|
||||
func (h *UserHandler) SetProfileAuditService(profileAuditService service.UserProfileAuditService) {
|
||||
h.profileAuditService = profileAuditService
|
||||
}
|
||||
|
||||
// SetJWTService 设置JWT服务
|
||||
func (h *UserHandler) SetJWTService(jwtService *service.JWTService) {
|
||||
h.jwtService = jwtService
|
||||
@@ -318,6 +322,7 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Avatar string `json:"avatar"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
Phone *string `json:"phone"`
|
||||
Email *string `json:"email"`
|
||||
}
|
||||
@@ -337,18 +342,12 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
|
||||
if req.Nickname != "" {
|
||||
user.Nickname = req.Nickname
|
||||
}
|
||||
if req.Bio != "" {
|
||||
user.Bio = req.Bio
|
||||
}
|
||||
if req.Website != "" {
|
||||
user.Website = req.Website
|
||||
}
|
||||
if req.Location != "" {
|
||||
user.Location = req.Location
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
user.Avatar = req.Avatar
|
||||
}
|
||||
if req.Phone != nil {
|
||||
user.Phone = req.Phone
|
||||
}
|
||||
@@ -359,20 +358,53 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
|
||||
user.Email = req.Email
|
||||
}
|
||||
|
||||
avatarNeedsAudit := req.Avatar != "" && req.Avatar != user.Avatar && h.profileAuditService != nil
|
||||
coverNeedsAudit := req.CoverURL != "" && req.CoverURL != user.CoverURL && h.profileAuditService != nil
|
||||
bioNeedsAudit := req.Bio != "" && req.Bio != user.Bio && h.profileAuditService != nil
|
||||
|
||||
if avatarNeedsAudit {
|
||||
if _, err := h.profileAuditService.SubmitAvatarAudit(c.Request.Context(), userID, req.Avatar); err != nil {
|
||||
avatarNeedsAudit = false
|
||||
user.Avatar = req.Avatar
|
||||
}
|
||||
} else if req.Avatar != "" {
|
||||
user.Avatar = req.Avatar
|
||||
}
|
||||
|
||||
if coverNeedsAudit {
|
||||
if _, err := h.profileAuditService.SubmitCoverAudit(c.Request.Context(), userID, req.CoverURL); err != nil {
|
||||
coverNeedsAudit = false
|
||||
user.CoverURL = req.CoverURL
|
||||
}
|
||||
} else if req.CoverURL != "" {
|
||||
user.CoverURL = req.CoverURL
|
||||
}
|
||||
|
||||
if bioNeedsAudit {
|
||||
if _, err := h.profileAuditService.SubmitBioAudit(c.Request.Context(), userID, req.Bio); err != nil {
|
||||
bioNeedsAudit = false
|
||||
user.Bio = req.Bio
|
||||
}
|
||||
} else if req.Bio != "" {
|
||||
user.Bio = req.Bio
|
||||
}
|
||||
|
||||
err = h.userService.UpdateUser(c.Request.Context(), user)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to update user")
|
||||
return
|
||||
}
|
||||
|
||||
// 实时计算帖子数量
|
||||
postsCount, err := h.userService.GetUserPostCount(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
// 如果获取失败,使用数据库中的值
|
||||
postsCount = int64(user.PostsCount)
|
||||
}
|
||||
|
||||
response.Success(c, dto.ConvertUserToDetailResponseWithPostsCount(user, int(postsCount)))
|
||||
resp := gin.H{"user": dto.ConvertUserToDetailResponseWithPostsCount(user, int(postsCount))}
|
||||
if avatarNeedsAudit || coverNeedsAudit || bioNeedsAudit {
|
||||
resp["audit_pending"] = true
|
||||
}
|
||||
response.Success(c, resp)
|
||||
}
|
||||
|
||||
// SendEmailVerifyCode 发送当前用户邮箱验证码
|
||||
|
||||
Reference in New Issue
Block a user