mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
dev day #79
This commit is contained in:
@@ -0,0 +1,508 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"fotbal-club/internal/models"
|
||||
"fotbal-club/internal/services"
|
||||
)
|
||||
|
||||
type CommentController struct{ DB *gorm.DB }
|
||||
|
||||
// ReportComment allows a user to report a comment with an optional reason
|
||||
func (cc *CommentController) ReportComment(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var cm models.Comment
|
||||
if err := cc.DB.First(&cm, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"})
|
||||
return
|
||||
}
|
||||
var body struct{ Reason string `json:"reason"` }
|
||||
_ = c.ShouldBindJSON(&body)
|
||||
uid, _ := c.Get("userID")
|
||||
// Prevent duplicate reports by same user
|
||||
var exists models.CommentReport
|
||||
if err := cc.DB.Where("comment_id = ? AND user_id = ?", cm.ID, uid).First(&exists).Error; err == nil && exists.ID != 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
return
|
||||
}
|
||||
rep := models.CommentReport{ CommentID: cm.ID, UserID: uid.(uint), Reason: strings.TrimSpace(body.Reason) }
|
||||
if err := cc.DB.Create(&rep).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error":"Failed"}); return }
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// React to a comment (auth)
|
||||
func (cc *CommentController) React(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var cm models.Comment
|
||||
if err := cc.DB.First(&cm, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"})
|
||||
return
|
||||
}
|
||||
var body struct{ Type string `json:"type"` }
|
||||
if err := c.ShouldBindJSON(&body); err != nil || strings.TrimSpace(body.Type) == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
uid, _ := c.Get("userID")
|
||||
// delete previous reaction for this user
|
||||
_ = cc.DB.Where("comment_id = ? AND user_id = ?", cm.ID, uid).Delete(&models.CommentReaction{}).Error
|
||||
// create new
|
||||
r := models.CommentReaction{ CommentID: cm.ID, UserID: uid.(uint), Type: strings.TrimSpace(body.Type) }
|
||||
if err := cc.DB.Create(&r).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to react"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// Remove reaction (auth)
|
||||
func (cc *CommentController) Unreact(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var cm models.Comment
|
||||
if err := cc.DB.First(&cm, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"})
|
||||
return
|
||||
}
|
||||
uid, _ := c.Get("userID")
|
||||
_ = cc.DB.Where("comment_id = ? AND user_id = ?", cm.ID, uid).Delete(&models.CommentReaction{}).Error
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// Admin: list all comments with filters
|
||||
func (cc *CommentController) AdminList(c *gin.Context) {
|
||||
var items []models.Comment
|
||||
q := cc.DB.Preload("User").Model(&models.Comment{})
|
||||
if v := strings.TrimSpace(c.Query("status")); v != "" { q = q.Where("status = ?", v) }
|
||||
if v := strings.TrimSpace(c.Query("target_type")); v != "" { q = q.Where("target_type = ?", v) }
|
||||
if v := strings.TrimSpace(c.Query("target_id")); v != "" { q = q.Where("target_id = ?", v) }
|
||||
if v := strings.TrimSpace(c.Query("user_id")); v != "" { q = q.Where("user_id = ?", v) }
|
||||
page := parseIntDefault(c.Query("page"), 1)
|
||||
size := parseIntDefault(c.Query("page_size"), 50)
|
||||
if size > 200 { size = 200 }
|
||||
var total int64
|
||||
_ = q.Count(&total).Error
|
||||
_ = q.Order("created_at DESC").Offset((page-1)*size).Limit(size).Find(&items).Error
|
||||
// Preload reports counts
|
||||
ids := make([]uint, 0, len(items))
|
||||
for _, it := range items { ids = append(ids, it.ID) }
|
||||
repCounts := map[uint]int{}
|
||||
if len(ids) > 0 {
|
||||
type pr struct{ CommentID uint; Cnt int }
|
||||
var rows []pr
|
||||
_ = cc.DB.Table("comment_reports").Select("comment_id, COUNT(*) as cnt").Where("comment_id IN ?", ids).Group("comment_id").Scan(&rows).Error
|
||||
for _, r := range rows { repCounts[r.CommentID] = r.Cnt }
|
||||
}
|
||||
out := make([]commentOutput, 0, len(items))
|
||||
for _, r := range items { co := toOutput(r); if v, ok := repCounts[r.ID]; ok { co.Reports = v }; out = append(out, co) }
|
||||
c.JSON(http.StatusOK, gin.H{"items": out, "total": total, "page": page, "page_size": size})
|
||||
}
|
||||
|
||||
// Admin: update comment status (visible|hidden)
|
||||
func (cc *CommentController) AdminUpdateStatus(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var body struct{ Status string `json:"status"` }
|
||||
if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error":"Invalid"}); return }
|
||||
if body.Status != "visible" && body.Status != "hidden" { c.JSON(http.StatusBadRequest, gin.H{"error":"Invalid status"}); return }
|
||||
if err := cc.DB.Model(&models.Comment{}).Where("id = ?", id).Update("status", body.Status).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed"}); return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// Admin: ban user for period
|
||||
func (cc *CommentController) AdminBanUser(c *gin.Context) {
|
||||
var body struct { UserID uint `json:"user_id"`; Reason string `json:"reason"`; DurationHours int `json:"duration_hours"` }
|
||||
if err := c.ShouldBindJSON(&body); err != nil || body.UserID == 0 { c.JSON(http.StatusBadRequest, gin.H{"error":"Invalid"}); return }
|
||||
var until *time.Time
|
||||
if body.DurationHours > 0 { t := time.Now().Add(time.Duration(body.DurationHours) * time.Hour); until = &t }
|
||||
uid, _ := c.Get("userID")
|
||||
ban := models.CommentBan{ UserID: body.UserID, Reason: strings.TrimSpace(body.Reason), Until: until, CreatedByID: uid.(uint) }
|
||||
if err := cc.DB.Create(&ban).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error":"Failed"}); return }
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// Create unban request (auth)
|
||||
func (cc *CommentController) CreateUnbanRequest(c *gin.Context) {
|
||||
var body struct { Message string `json:"message"` }
|
||||
if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error":"Invalid"}); return }
|
||||
uid, _ := c.Get("userID")
|
||||
req := models.UnbanRequest{ UserID: uid.(uint), Message: strings.TrimSpace(body.Message) }
|
||||
if err := cc.DB.Create(&req).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error":"Failed"}); return }
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// Admin: list unban requests
|
||||
func (cc *CommentController) AdminListUnban(c *gin.Context) {
|
||||
var items []models.UnbanRequest
|
||||
_ = cc.DB.Order("created_at DESC").Find(&items).Error
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
}
|
||||
|
||||
// Admin: resolve unban request
|
||||
func (cc *CommentController) AdminResolveUnban(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var body struct { Action string `json:"action"` }
|
||||
if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error":"Invalid"}); return }
|
||||
uid, _ := c.Get("userID")
|
||||
var req models.UnbanRequest
|
||||
if err := cc.DB.First(&req, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error":"Not found"}); return }
|
||||
if body.Action != "approve" && body.Action != "reject" { c.JSON(http.StatusBadRequest, gin.H{"error":"Invalid action"}); return }
|
||||
status := map[string]string{"approve":"approved","reject":"rejected"}[body.Action]
|
||||
now := time.Now()
|
||||
if err := cc.DB.Model(&req).Updates(map[string]interface{}{"status": status, "resolved_by_id": uid.(uint), "resolved_at": &now}).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error":"Failed"}); return }
|
||||
// If approved, remove bans (set until = now)
|
||||
if status == "approved" {
|
||||
_ = cc.DB.Model(&models.CommentBan{}).Where("user_id = ? AND (until IS NULL OR until > ?)", req.UserID, time.Now()).Update("until", now).Error
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func NewCommentController(db *gorm.DB) *CommentController { return &CommentController{DB: db} }
|
||||
|
||||
var allowedTargetTypes = map[string]bool{
|
||||
"article": true,
|
||||
"event": true,
|
||||
"gallery_album": true,
|
||||
"youtube_video": true,
|
||||
}
|
||||
|
||||
type commentOutput struct {
|
||||
ID uint `json:"id"`
|
||||
TargetType string `json:"target_type"`
|
||||
TargetID string `json:"target_id"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Content string `json:"content"`
|
||||
Status string `json:"status"`
|
||||
IsEdited bool `json:"is_edited"`
|
||||
EditedAt *time.Time `json:"edited_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
User userSlim `json:"user"`
|
||||
Reactions map[string]int `json:"reactions"`
|
||||
MyReaction string `json:"my_reaction,omitempty"`
|
||||
SpamScore float32 `json:"spam_score,omitempty"`
|
||||
SpamRules []string `json:"spam_rules,omitempty"`
|
||||
Reports int `json:"reports,omitempty"`
|
||||
}
|
||||
|
||||
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"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
}
|
||||
|
||||
func toOutput(c models.Comment) commentOutput {
|
||||
out := commentOutput{
|
||||
ID: c.ID,
|
||||
TargetType: c.TargetType,
|
||||
TargetID: c.TargetID,
|
||||
ParentID: c.ParentID,
|
||||
Content: c.Content,
|
||||
Status: c.Status,
|
||||
IsEdited: c.IsEdited,
|
||||
EditedAt: c.EditedAt,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
User: userSlim{
|
||||
ID: c.User.ID,
|
||||
FirstName: c.User.FirstName,
|
||||
LastName: c.User.LastName,
|
||||
Email: c.User.Email,
|
||||
Role: c.User.Role,
|
||||
},
|
||||
SpamScore: c.SpamScore,
|
||||
}
|
||||
if strings.TrimSpace(c.SpamRules) != "" {
|
||||
var arr []string
|
||||
if err := json.Unmarshal([]byte(c.SpamRules), &arr); err == nil { out.SpamRules = arr }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// GetComments (public): list comments for a target with pagination
|
||||
// GET /comments?target_type=...&target_id=...&page=1&page_size=20
|
||||
func (cc *CommentController) GetComments(c *gin.Context) {
|
||||
// Ensure table exists (best-effort)
|
||||
_ = cc.DB.AutoMigrate(&models.Comment{})
|
||||
|
||||
targetType := strings.TrimSpace(c.Query("target_type"))
|
||||
targetID := strings.TrimSpace(c.Query("target_id"))
|
||||
if !allowedTargetTypes[targetType] || targetID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing or invalid target_type/target_id"})
|
||||
return
|
||||
}
|
||||
|
||||
page := parseIntDefault(c.Query("page"), 1)
|
||||
pageSize := parseIntDefault(c.Query("page_size"), 20)
|
||||
if pageSize > 100 { pageSize = 100 }
|
||||
if page < 1 { page = 1 }
|
||||
|
||||
var total int64
|
||||
q := cc.DB.Model(&models.Comment{}).Where("target_type = ? AND target_id = ? AND status = ?", targetType, targetID, "visible")
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
||||
return
|
||||
}
|
||||
|
||||
var rows []models.Comment
|
||||
if err := cc.DB.Preload("User").Where("target_type = ? AND target_id = ? AND status = ?", targetType, targetID, "visible").
|
||||
Order("created_at ASC").
|
||||
Offset((page-1)*pageSize).Limit(pageSize).
|
||||
Find(&rows).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
||||
return
|
||||
}
|
||||
|
||||
// Build reactions map per comment
|
||||
out := make([]commentOutput, 0, len(rows))
|
||||
ids := make([]uint, 0, len(rows))
|
||||
userIDs := make([]uint, 0, len(rows))
|
||||
for _, r := range rows { ids = append(ids, r.ID) }
|
||||
seenU := map[uint]bool{}
|
||||
for _, r := range rows { if r.UserID != 0 && !seenU[r.UserID] { userIDs = append(userIDs, r.UserID); seenU[r.UserID] = true } }
|
||||
reactionCounts := make(map[uint]map[string]int)
|
||||
if len(ids) > 0 {
|
||||
type rc struct{ CommentID uint; Type string; Cnt int }
|
||||
var agg []rc
|
||||
// aggregate per type
|
||||
if err := cc.DB.Table("comment_reactions").Select("comment_id, type, COUNT(*) as cnt").
|
||||
Where("comment_id IN ?", ids).Group("comment_id, type").Scan(&agg).Error; err == nil {
|
||||
for _, a := range agg {
|
||||
if reactionCounts[a.CommentID] == nil { reactionCounts[a.CommentID] = map[string]int{} }
|
||||
reactionCounts[a.CommentID][a.Type] = a.Cnt
|
||||
}
|
||||
}
|
||||
}
|
||||
var myReactions map[uint]string
|
||||
if uid, ok := c.Get("userID"); ok {
|
||||
var rs []models.CommentReaction
|
||||
if err := cc.DB.Where("user_id = ? AND comment_id IN ?", uid, ids).Find(&rs).Error; err == nil {
|
||||
myReactions = make(map[uint]string, len(rs))
|
||||
for _, r := range rs { myReactions[r.CommentID] = r.Type }
|
||||
}
|
||||
}
|
||||
// Preload user profiles for avatar (prefer animated when available)
|
||||
avatarByUser := map[uint]string{}
|
||||
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
|
||||
for _, p := range profs {
|
||||
if strings.TrimSpace(p.AnimatedAvatarURL) != "" {
|
||||
avatarByUser[p.UserID] = p.AnimatedAvatarURL
|
||||
} else {
|
||||
avatarByUser[p.UserID] = p.AvatarURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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 } }
|
||||
out = append(out, co)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"items": out,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
type createCommentInput struct {
|
||||
TargetType string `json:"target_type"`
|
||||
TargetID string `json:"target_id"`
|
||||
Content string `json:"content"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
}
|
||||
|
||||
// CreateComment (auth required)
|
||||
func (cc *CommentController) CreateComment(c *gin.Context) {
|
||||
// Ensure table exists (best-effort)
|
||||
_ = cc.DB.AutoMigrate(&models.Comment{})
|
||||
|
||||
var in createCommentInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
in.TargetType = strings.TrimSpace(in.TargetType)
|
||||
in.TargetID = strings.TrimSpace(in.TargetID)
|
||||
content := strings.TrimSpace(in.Content)
|
||||
if !allowedTargetTypes[in.TargetType] || in.TargetID == "" || len(content) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing or invalid fields"})
|
||||
return
|
||||
}
|
||||
if len(content) < 6 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Komentář je příliš krátký (min. 6 znaků)"})
|
||||
return
|
||||
}
|
||||
if len(content) > 2000 { // hard cap
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Komentář je příliš dlouhý (max 2000 znaků)"})
|
||||
return
|
||||
}
|
||||
|
||||
userIDv, _ := c.Get("userID")
|
||||
userID := userIDv.(uint)
|
||||
|
||||
// Check active bans
|
||||
var activeBan models.CommentBan
|
||||
if err := cc.DB.Where("user_id = ? AND (until IS NULL OR until > ?)", userID, time.Now()).Order("created_at DESC").First(&activeBan).Error; err == nil && activeBan.ID != 0 {
|
||||
// User is banned
|
||||
until := "trvale"
|
||||
if activeBan.Until != nil { until = activeBan.Until.Format(time.RFC3339) }
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Váš účet má omezené komentování.", "until": until})
|
||||
return
|
||||
}
|
||||
|
||||
// Spam evaluation and bad words filtering
|
||||
score, rules := services.EvaluateSpamScore(content)
|
||||
filtered, _ := services.FilterBadWords(content)
|
||||
status := "visible"
|
||||
// Moderation only if sensitive terms detected
|
||||
if ok, _ := services.ContainsSensitiveWords(filtered); ok {
|
||||
status = "hidden"
|
||||
}
|
||||
rulesJSON, _ := json.Marshal(rules)
|
||||
|
||||
cm := models.Comment{
|
||||
TargetType: in.TargetType,
|
||||
TargetID: in.TargetID,
|
||||
UserID: userID,
|
||||
ParentID: in.ParentID,
|
||||
Content: filtered,
|
||||
Status: status,
|
||||
SpamScore: float32(score),
|
||||
SpamRules: string(rulesJSON),
|
||||
}
|
||||
if err := cc.DB.Create(&cm).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create comment"})
|
||||
return
|
||||
}
|
||||
|
||||
// Award engagement points for visible comment
|
||||
if status == "visible" {
|
||||
svc := services.NewEngagementService(cc.DB)
|
||||
_, _ = svc.AwardPoints(userID, 5, "comment_create", map[string]interface{}{"comment_id": cm.ID})
|
||||
_ = svc.CheckAndAwardAchievements(userID)
|
||||
}
|
||||
|
||||
// Reload with user
|
||||
var out models.Comment
|
||||
_ = cc.DB.Preload("User").First(&out, cm.ID).Error
|
||||
c.JSON(http.StatusCreated, toOutput(out))
|
||||
}
|
||||
|
||||
type updateCommentInput struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// UpdateComment (auth: owner or admin)
|
||||
func (cc *CommentController) UpdateComment(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var cm models.Comment
|
||||
if err := cc.DB.Preload("User").First(&cm, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Permission: owner or admin
|
||||
role, _ := c.Get("userRole")
|
||||
uidv, _ := c.Get("userID")
|
||||
if role != "admin" && uidv.(uint) != cm.UserID {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
var in updateCommentInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
content := strings.TrimSpace(in.Content)
|
||||
if len(content) == 0 || len(content) > 2000 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Neplatný obsah"})
|
||||
return
|
||||
}
|
||||
|
||||
// Re-check ban
|
||||
var activeBan models.CommentBan
|
||||
if err := cc.DB.Where("user_id = ? AND (until IS NULL OR until > ?)", cm.UserID, time.Now()).First(&activeBan).Error; err == nil && activeBan.ID != 0 {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Váš účet má omezené komentování."})
|
||||
return
|
||||
}
|
||||
|
||||
// Filter & re-evaluate basic spam (do not auto-hide unless sensitive)
|
||||
score, rules := services.EvaluateSpamScore(content)
|
||||
filtered, _ := services.FilterBadWords(content)
|
||||
now := time.Now()
|
||||
cm.Content = filtered
|
||||
cm.IsEdited = true
|
||||
cm.EditedAt = &now
|
||||
cm.SpamScore = float32(score)
|
||||
if b, err := json.Marshal(rules); err == nil { cm.SpamRules = string(b) }
|
||||
|
||||
if err := cc.DB.Save(&cm).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update comment"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, toOutput(cm))
|
||||
}
|
||||
|
||||
// DeleteComment (auth: owner or admin)
|
||||
func (cc *CommentController) DeleteComment(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var cm models.Comment
|
||||
if err := cc.DB.First(&cm, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Permission: owner or admin
|
||||
role, _ := c.Get("userRole")
|
||||
uidv, _ := c.Get("userID")
|
||||
if role != "admin" && uidv.(uint) != cm.UserID {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := cc.DB.Delete(&cm).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete comment"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// helpers
|
||||
func parseIntDefault(s string, def int) int {
|
||||
if s == "" { return def }
|
||||
n := 0
|
||||
for _, ch := range s { if ch < '0' || ch > '9' { return def } }
|
||||
for i := 0; i < len(s); i++ { n = n*10 + int(s[i]-'0') }
|
||||
if n <= 0 { return def }
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user