优化违禁词

This commit is contained in:
2026-07-06 12:18:09 +08:00
parent 713cca8f67
commit 89e0a9df9f
20 changed files with 412 additions and 94 deletions
+18 -1
View File
@@ -3,6 +3,7 @@ package handler
import (
"net/http"
"strconv"
"strings"
"time"
"backend/internal/model"
@@ -57,7 +58,23 @@ func (h *AdminReadHandler) Logs(c *gin.Context) {
}
}
items, total, stats, err := h.admin.Logs(c.Request.Context(), limit, offset, kind, status, nil, since, "", "", c.Query("source"), false, false, false)
// ?user= — server-side 用户搜索: resolve the term to matching user ids
// (name/email/id contains, case-insensitive) and filter rows to those users.
// A term that matches nobody must return zero rows, not the unfiltered list.
var userIDs []string
if term := strings.TrimSpace(c.Query("user")); term != "" {
ids, uerr := h.admin.MatchUserIDs(c.Request.Context(), term)
if uerr != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
return
}
if len(ids) == 0 {
ids = []string{"__no_match__"}
}
userIDs = ids
}
items, total, stats, err := h.admin.Logs(c.Request.Context(), limit, offset, kind, status, nil, since, "", userIDs, strings.TrimSpace(c.Query("q")), "", c.Query("source"), false, false, false)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
return
@@ -77,6 +77,30 @@ func (h *BannedWordsHandler) Import(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"added": added, "skipped": skipped})
}
// Hits — 违禁词触发列表: who triggered which word and when, newest first,
// with server-side pagination + ?q= search (违禁词/用户名/提示词, 跨页).
func (h *BannedWordsHandler) Hits(c *gin.Context) {
limit := parseInt(c.Query("limit"), 50)
offset := parseInt(c.Query("offset"), 0)
items, total, err := h.words.ListHits(c.Request.Context(), strings.TrimSpace(c.Query("q")), limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load banned word hits"})
return
}
out := make([]gin.H, 0, len(items))
for _, hit := range items {
out = append(out, gin.H{
"id": hit.ID,
"word": hit.Word,
"user_id": hit.UserID,
"user_name": hit.UserName,
"prompt": hit.Prompt,
"created_at": hit.CreatedAt,
})
}
c.JSON(http.StatusOK, gin.H{"data": out, "total": total})
}
func (h *BannedWordsHandler) Delete(c *gin.Context) {
n, err := h.words.Delete(c.Request.Context(), c.Param("id"))
if err != nil {
+2 -2
View File
@@ -79,7 +79,7 @@ func (h *PaymentHandler) MyOrders(c *gin.Context) {
}
limit := parseInt(c.Query("limit"), 20)
offset := parseInt(c.Query("offset"), 0)
orders, total, err := h.pay.ListByUser(c.Request.Context(), user.ID, c.Query("status"), limit, offset)
orders, total, err := h.pay.ListByUser(c.Request.Context(), user.ID, c.Query("status"), strings.TrimSpace(c.Query("q")), limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load orders"})
return
@@ -129,7 +129,7 @@ func (h *PaymentHandler) AdminOrders(c *gin.Context) {
status := c.Query("status")
limit := parseInt(c.Query("limit"), 100)
offset := parseInt(c.Query("offset"), 0)
orders, total, err := h.pay.ListAll(c.Request.Context(), status, limit, offset)
orders, total, err := h.pay.ListAll(c.Request.Context(), status, strings.TrimSpace(c.Query("q")), limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load orders"})
return
@@ -229,7 +229,21 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
// media=1 (画图台 grid): only pending rows or rows with a stored file, so a
// deleted work's blanked row doesn't consume one of the grid's slots.
mediaOnly := c.Query("media") == "1"
items, total, stats, err := h.admin.Logs(c.Request.Context(), limit, offset, kind, status, statuses, nil, userID, excludeSource, source, hasFile, excludeShowcase, mediaOnly)
// ?user= — admin-only 用户搜索 (the 日志管理 page with scope=all). Ignored for
// normal users, whose rows are already pinned to their own userID.
var userIDs []string
if term := strings.TrimSpace(c.Query("user")); term != "" && userID == "" {
ids, uerr := h.admin.MatchUserIDs(c.Request.Context(), term)
if uerr != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
return
}
if len(ids) == 0 {
ids = []string{"__no_match__"}
}
userIDs = ids
}
items, total, stats, err := h.admin.Logs(c.Request.Context(), limit, offset, kind, status, statuses, nil, userID, userIDs, strings.TrimSpace(c.Query("q")), excludeSource, source, hasFile, excludeShowcase, mediaOnly)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
return
@@ -264,13 +278,18 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
} else {
userName = item.UserID
}
var accountName any
if item.AccountID != "" {
if label, ok := accountByID[item.AccountID]; ok {
accountName = label
} else {
accountName = item.AccountID
// Provider account identity is admin-only: normal users must not see
// which upstream account (email) fulfilled their generation.
var accountName, accountID any
if userID == "" {
if item.AccountID != "" {
if label, ok := accountByID[item.AccountID]; ok {
accountName = label
} else {
accountName = item.AccountID
}
}
accountID = emptyStringNil(item.AccountID)
}
out = append(out, gin.H{
"id": item.ID,
@@ -287,7 +306,7 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
"source": emptyStringNil(item.Source),
"user_id": emptyStringNil(item.UserID),
"user_name": userName,
"account_id": emptyStringNil(item.AccountID),
"account_id": accountID,
"account": accountName,
"cost": item.Cost,
"elapsed_ms": item.ElapsedMS,