mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
509 lines
20 KiB
Go
509 lines
20 KiB
Go
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
|
|
}
|