mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
dev day #90 🥳
This commit is contained in:
@@ -5,9 +5,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"fotbal-club/internal/models"
|
||||
"fotbal-club/internal/services"
|
||||
@@ -24,7 +27,54 @@ func (cc *CommentController) AdminListBans(c *gin.Context) {
|
||||
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})
|
||||
// Load users
|
||||
uids := make([]uint, 0, len(bans))
|
||||
seen := map[uint]bool{}
|
||||
for _, b := range bans { if !seen[b.UserID] { uids = append(uids, b.UserID); seen[b.UserID] = true } }
|
||||
type userRow struct { ID uint; FirstName string; LastName string; Email string; Role string }
|
||||
users := map[uint]userRow{}
|
||||
if len(uids) > 0 {
|
||||
var rows []userRow
|
||||
_ = cc.DB.Table("users").Select("id, first_name, last_name, email, role").Where("id IN ?", uids).Scan(&rows).Error
|
||||
for _, r := range rows { users[r.ID] = r }
|
||||
}
|
||||
usernameByID := map[uint]string{}
|
||||
if len(uids) > 0 {
|
||||
type prof struct{ UserID uint; Username string }
|
||||
var profs []prof
|
||||
_ = cc.DB.Table("user_profiles").Select("user_id, username").Where("user_id IN ?", uids).Scan(&profs).Error
|
||||
for _, p := range profs { if strings.TrimSpace(p.Username) != "" { usernameByID[p.UserID] = p.Username } }
|
||||
}
|
||||
type banOut struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Reason string `json:"reason"`
|
||||
Until *time.Time `json:"until"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedByID uint `json:"created_by_id"`
|
||||
User 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"`
|
||||
} `json:"user"`
|
||||
}
|
||||
out := make([]banOut, 0, len(bans))
|
||||
for _, b := range bans {
|
||||
o := banOut{ ID: b.ID, UserID: b.UserID, Reason: b.Reason, Until: b.Until, CreatedAt: b.CreatedAt, CreatedByID: b.CreatedByID }
|
||||
if u, ok := users[b.UserID]; ok {
|
||||
o.User.ID = u.ID
|
||||
o.User.FirstName = u.FirstName
|
||||
o.User.LastName = u.LastName
|
||||
o.User.Email = u.Email
|
||||
o.User.Role = u.Role
|
||||
}
|
||||
if v, ok := usernameByID[b.UserID]; ok { o.User.Username = v }
|
||||
out = append(out, o)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": out})
|
||||
}
|
||||
|
||||
// Admin: lift a ban early by setting until = now
|
||||
@@ -74,11 +124,12 @@ func (cc *CommentController) React(c *gin.Context) {
|
||||
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
|
||||
// Upsert reaction to ensure exactly one reaction per (comment_id,user_id)
|
||||
r := models.CommentReaction{ CommentID: cm.ID, UserID: uid.(uint), Type: strings.TrimSpace(body.Type) }
|
||||
if err := cc.DB.Create(&r).Error; err != nil {
|
||||
if err := cc.DB.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "comment_id"}, {Name: "user_id"}},
|
||||
DoUpdates: clause.Assignments(map[string]interface{}{"type": r.Type, "updated_at": time.Now()}),
|
||||
}).Create(&r).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to react"})
|
||||
return
|
||||
}
|
||||
@@ -140,8 +191,71 @@ func (cc *CommentController) AdminList(c *gin.Context) {
|
||||
Scan(&rows).Error
|
||||
for _, r := range rows { adminLiked[r.CommentID] = true }
|
||||
}
|
||||
// Prepare target labels (titles) for admin visibility: articles and events
|
||||
articleIDs := make([]uint, 0)
|
||||
eventIDs := make([]uint, 0)
|
||||
for _, it := range items {
|
||||
switch it.TargetType {
|
||||
case "article":
|
||||
if v, err := strconv.ParseUint(strings.TrimSpace(it.TargetID), 10, 64); err == nil {
|
||||
articleIDs = append(articleIDs, uint(v))
|
||||
}
|
||||
case "event":
|
||||
if v, err := strconv.ParseUint(strings.TrimSpace(it.TargetID), 10, 64); err == nil {
|
||||
eventIDs = append(eventIDs, uint(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
articleTitleByID := map[uint]string{}
|
||||
if len(articleIDs) > 0 {
|
||||
type row struct{ ID uint; Title string }
|
||||
var rows []row
|
||||
_ = cc.DB.Table("articles").Select("id, title").Where("id IN ?", articleIDs).Scan(&rows).Error
|
||||
for _, r := range rows { articleTitleByID[r.ID] = r.Title }
|
||||
}
|
||||
eventTitleByID := map[uint]string{}
|
||||
if len(eventIDs) > 0 {
|
||||
type row struct{ ID uint; Title string }
|
||||
var rows []row
|
||||
_ = cc.DB.Table("events").Select("id, title").Where("id IN ?", eventIDs).Scan(&rows).Error
|
||||
for _, r := range rows { eventTitleByID[r.ID] = r.Title }
|
||||
}
|
||||
out := make([]commentOutput, 0, len(items))
|
||||
for _, r := range items { co := toOutput(r); if v, ok := repCounts[r.ID]; ok { co.Reports = v }; if adminLiked[r.ID] { co.AdminLiked = true }; out = append(out, co) }
|
||||
for _, r := range items {
|
||||
co := toOutput(r)
|
||||
if v, ok := repCounts[r.ID]; ok { co.Reports = v }
|
||||
if adminLiked[r.ID] { co.AdminLiked = true }
|
||||
// Compose human label for target
|
||||
switch r.TargetType {
|
||||
case "article":
|
||||
if v, err := strconv.ParseUint(strings.TrimSpace(r.TargetID), 10, 64); err == nil {
|
||||
if t, ok := articleTitleByID[uint(v)]; ok && strings.TrimSpace(t) != "" {
|
||||
co.TargetLabel = fmt.Sprintf("Článek: %s (#%s)", t, r.TargetID)
|
||||
} else {
|
||||
co.TargetLabel = fmt.Sprintf("Článek #%s", r.TargetID)
|
||||
}
|
||||
} else {
|
||||
co.TargetLabel = "Článek"
|
||||
}
|
||||
case "event":
|
||||
if v, err := strconv.ParseUint(strings.TrimSpace(r.TargetID), 10, 64); err == nil {
|
||||
if t, ok := eventTitleByID[uint(v)]; ok && strings.TrimSpace(t) != "" {
|
||||
co.TargetLabel = fmt.Sprintf("Aktivita: %s (#%s)", t, r.TargetID)
|
||||
} else {
|
||||
co.TargetLabel = fmt.Sprintf("Aktivita #%s", r.TargetID)
|
||||
}
|
||||
} else {
|
||||
co.TargetLabel = "Aktivita"
|
||||
}
|
||||
case "gallery_album":
|
||||
co.TargetLabel = fmt.Sprintf("Galerie album #%s", r.TargetID)
|
||||
case "youtube_video":
|
||||
co.TargetLabel = fmt.Sprintf("YouTube video %s", r.TargetID)
|
||||
default:
|
||||
co.TargetLabel = fmt.Sprintf("%s #%s", r.TargetType, r.TargetID)
|
||||
}
|
||||
out = append(out, co)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": out, "total": total, "page": page, "page_size": size})
|
||||
}
|
||||
|
||||
@@ -181,9 +295,60 @@ func (cc *CommentController) CreateUnbanRequest(c *gin.Context) {
|
||||
|
||||
// Admin: list unban requests
|
||||
func (cc *CommentController) AdminListUnban(c *gin.Context) {
|
||||
// Only pending requests
|
||||
var items []models.UnbanRequest
|
||||
_ = cc.DB.Order("created_at DESC").Find(&items).Error
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
_ = cc.DB.Where("status = ?", "pending").Order("created_at DESC").Find(&items).Error
|
||||
// Load users and usernames
|
||||
uids := make([]uint, 0, len(items))
|
||||
seen := map[uint]bool{}
|
||||
for _, it := range items { if !seen[it.UserID] { uids = append(uids, it.UserID); seen[it.UserID] = true } }
|
||||
type userRow struct { ID uint; FirstName string; LastName string; Email string; Role string }
|
||||
users := map[uint]userRow{}
|
||||
if len(uids) > 0 {
|
||||
var rows []userRow
|
||||
_ = cc.DB.Table("users").Select("id, first_name, last_name, email, role").Where("id IN ?", uids).Scan(&rows).Error
|
||||
for _, r := range rows { users[r.ID] = r }
|
||||
}
|
||||
usernameByID := map[uint]string{}
|
||||
if len(uids) > 0 {
|
||||
type prof struct{ UserID uint; Username string }
|
||||
var profs []prof
|
||||
_ = cc.DB.Table("user_profiles").Select("user_id, username").Where("user_id IN ?", uids).Scan(&profs).Error
|
||||
for _, p := range profs { if strings.TrimSpace(p.Username) != "" { usernameByID[p.UserID] = p.Username } }
|
||||
}
|
||||
type unbanOut struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ResolvedByID *uint `json:"resolved_by_id,omitempty"`
|
||||
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
||||
User 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"`
|
||||
} `json:"user"`
|
||||
}
|
||||
out := make([]unbanOut, 0, len(items))
|
||||
for _, it := range items {
|
||||
var u userRow
|
||||
if r, ok := users[it.UserID]; ok { u = r }
|
||||
o := unbanOut{
|
||||
ID: it.ID, UserID: it.UserID, Message: it.Message, Status: it.Status, CreatedAt: it.CreatedAt, ResolvedByID: it.ResolvedByID, ResolvedAt: it.ResolvedAt,
|
||||
}
|
||||
o.User.ID = u.ID
|
||||
o.User.FirstName = u.FirstName
|
||||
o.User.LastName = u.LastName
|
||||
o.User.Email = u.Email
|
||||
o.User.Role = u.Role
|
||||
if v, ok := usernameByID[it.UserID]; ok { o.User.Username = v }
|
||||
out = append(out, o)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": out})
|
||||
}
|
||||
|
||||
// Admin: resolve unban request
|
||||
@@ -218,6 +383,7 @@ type commentOutput struct {
|
||||
ID uint `json:"id"`
|
||||
TargetType string `json:"target_type"`
|
||||
TargetID string `json:"target_id"`
|
||||
TargetLabel string `json:"target_label,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Content string `json:"content"`
|
||||
Status string `json:"status"`
|
||||
|
||||
Reference in New Issue
Block a user