This commit is contained in:
Tomas Dvorak
2025-11-02 21:31:00 +01:00
parent b9cea0cd77
commit 087f30e82c
130 changed files with 20104 additions and 34330 deletions
+38 -14
View File
@@ -15,6 +15,29 @@ import (
type CommentController struct{ DB *gorm.DB }
// Admin: list active bans
// GET /api/v1/admin/comments/bans
func (cc *CommentController) AdminListBans(c *gin.Context) {
var bans []models.CommentBan
// Active = until is NULL (permanent) OR until > now
now := time.Now()
if err := cc.DB.Where("until IS NULL OR until > ?", now).Order("created_at DESC").Find(&bans).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error":"Failed to load bans"}); return
}
c.JSON(http.StatusOK, gin.H{"items": bans})
}
// Admin: lift a ban early by setting until = now
// POST /api/v1/admin/comments/bans/:id/lift
func (cc *CommentController) AdminLiftBan(c *gin.Context) {
id := c.Param("id")
now := time.Now()
if err := cc.DB.Model(&models.CommentBan{}).Where("id = ?", id).Update("until", now).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error":"Failed to lift ban"}); return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// ReportComment allows a user to report a comment with an optional reason
func (cc *CommentController) ReportComment(c *gin.Context) {
id := c.Param("id")
@@ -59,6 +82,9 @@ func (cc *CommentController) React(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to react"})
return
}
// Award a small amount of points for reactions (capped per day in service)
svc := services.NewEngagementService(cc.DB)
_, _ = svc.AwardPointsCapped(uid.(uint), 1, "comment_reacted", map[string]interface{}{"comment_id": cm.ID})
c.JSON(http.StatusOK, gin.H{"ok": true})
}
@@ -196,8 +222,8 @@ type userSlim struct {
ID uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Role string `json:"role"`
Username string `json:"username,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
}
@@ -217,7 +243,6 @@ func toOutput(c models.Comment) commentOutput {
ID: c.User.ID,
FirstName: c.User.FirstName,
LastName: c.User.LastName,
Email: c.User.Email,
Role: c.User.Role,
},
SpamScore: c.SpamScore,
@@ -291,25 +316,24 @@ func (cc *CommentController) GetComments(c *gin.Context) {
for _, r := range rs { myReactions[r.CommentID] = r.Type }
}
}
// Preload user profiles for avatar (prefer animated when available)
avatarByUser := map[uint]string{}
// Preload user profiles for username + avatar (prefer animated when available)
type up struct{ UserID uint; AvatarURL string; AnimatedAvatarURL string; Username string }
profByUser := map[uint]up{}
if len(userIDs) > 0 {
type up struct{ UserID uint; AvatarURL string; AnimatedAvatarURL string }
var profs []up
_ = cc.DB.Table("user_profiles").Select("user_id, avatar_url, animated_avatar_url").Where("user_id IN ?", userIDs).Scan(&profs).Error
_ = cc.DB.Table("user_profiles").Select("user_id, avatar_url, animated_avatar_url, username").Where("user_id IN ?", userIDs).Scan(&profs).Error
for _, p := range profs {
if strings.TrimSpace(p.AnimatedAvatarURL) != "" {
avatarByUser[p.UserID] = p.AnimatedAvatarURL
} else {
avatarByUser[p.UserID] = p.AvatarURL
}
profByUser[p.UserID] = p
}
}
for _, r := range rows {
co := toOutput(r)
if co.User.ID != 0 {
if av, ok := avatarByUser[co.User.ID]; ok { co.User.AvatarURL = av }
if p, ok := profByUser[co.User.ID]; ok {
if strings.TrimSpace(p.Username) != "" { co.User.Username = p.Username }
if strings.TrimSpace(p.AnimatedAvatarURL) != "" { co.User.AvatarURL = p.AnimatedAvatarURL } else { co.User.AvatarURL = p.AvatarURL }
}
}
if rc, ok := reactionCounts[r.ID]; ok { co.Reactions = rc } else { co.Reactions = map[string]int{} }
if myReactions != nil { if t, ok := myReactions[r.ID]; ok { co.MyReaction = t } }
@@ -395,10 +419,10 @@ func (cc *CommentController) CreateComment(c *gin.Context) {
return
}
// Award engagement points for visible comment
// Award engagement points for visible comment (capped per day)
if status == "visible" {
svc := services.NewEngagementService(cc.DB)
_, _ = svc.AwardPoints(userID, 5, "comment_create", map[string]interface{}{"comment_id": cm.ID})
_, _ = svc.AwardPointsCapped(userID, 5, "comment_create", map[string]interface{}{"comment_id": cm.ID})
_ = svc.CheckAndAwardAchievements(userID)
}