增加违禁词管理 增加多选操作
This commit is contained in:
@@ -29,6 +29,7 @@ func (h *AdminReadHandler) Users(c *gin.Context) {
|
||||
for _, user := range users {
|
||||
row := userPublic(user)
|
||||
row["generation_count"] = user.GenerationCount
|
||||
row["banned_word_hits"] = user.BannedWordHits
|
||||
out = append(out, row)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": out, "stats": stats})
|
||||
@@ -56,7 +57,7 @@ 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)
|
||||
items, total, stats, err := h.admin.Logs(c.Request.Context(), limit, offset, kind, status, nil, since, "", "", c.Query("source"), false, false, false)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
||||
return
|
||||
@@ -158,6 +159,16 @@ func (h *AdminReadHandler) Invites(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"data": items, "stats": stats})
|
||||
}
|
||||
|
||||
// DeleteImage removes one generated file (plus derived stills) and blanks the
|
||||
// log rows referencing it. Admin 图片管理 delete; ?name= is the storage key.
|
||||
func (h *AdminReadHandler) DeleteImage(c *gin.Context) {
|
||||
if err := h.admin.DeleteFile(c.Request.Context(), c.Query("name")); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *AdminReadHandler) Providers(c *gin.Context) {
|
||||
items, err := h.admin.Providers(c.Request.Context())
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"backend/internal/repo"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// BannedWordsHandler — admin 违禁词管理: list / add / delete prompt blocklist
|
||||
// entries. The generation path (V1Service.checkBannedPrompt) enforces them.
|
||||
type BannedWordsHandler struct {
|
||||
words *repo.BannedWordRepository
|
||||
}
|
||||
|
||||
func NewBannedWordsHandler(words *repo.BannedWordRepository) *BannedWordsHandler {
|
||||
return &BannedWordsHandler{words: words}
|
||||
}
|
||||
|
||||
func (h *BannedWordsHandler) List(c *gin.Context) {
|
||||
items, err := h.words.List(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load banned words"})
|
||||
return
|
||||
}
|
||||
out := make([]gin.H, 0, len(items))
|
||||
for _, w := range items {
|
||||
out = append(out, gin.H{
|
||||
"id": w.ID,
|
||||
"word": w.Word,
|
||||
"hits": w.Hits,
|
||||
"created_at": w.CreatedAt,
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": out})
|
||||
}
|
||||
|
||||
func (h *BannedWordsHandler) Create(c *gin.Context) {
|
||||
var body struct {
|
||||
Word string `json:"word"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "invalid request body"})
|
||||
return
|
||||
}
|
||||
item, err := h.words.Create(c.Request.Context(), body.Word)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": gin.H{"id": item.ID, "word": item.Word, "hits": item.Hits, "created_at": item.CreatedAt}})
|
||||
}
|
||||
|
||||
func (h *BannedWordsHandler) Delete(c *gin.Context) {
|
||||
n, err := h.words.Delete(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "delete failed"})
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
@@ -38,6 +38,22 @@ func (h *UserGenerationHandler) MyImages(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"data": items})
|
||||
}
|
||||
|
||||
// DeleteMyFile removes ONE of the caller's own generated files (plus its
|
||||
// thumbnail) and blanks the log rows referencing it, so the 画图台 grid and
|
||||
// 创作记录 gallery stop showing it. ?file= is the storage key (owner/name).
|
||||
func (h *UserGenerationHandler) DeleteMyFile(c *gin.Context) {
|
||||
user := currentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"detail": "未登录或会话已过期"})
|
||||
return
|
||||
}
|
||||
if err := h.admin.DeleteOwnedFile(c.Request.Context(), service.OwnerDir(user), c.Query("file")); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *UserGenerationHandler) Generate(c *gin.Context) {
|
||||
user := currentUser(c)
|
||||
if user == nil {
|
||||
@@ -70,7 +86,7 @@ func (h *UserGenerationHandler) Generate(c *gin.Context) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrUnknownModel):
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
||||
case errors.Is(err, service.ErrUnsupportedParams):
|
||||
case errors.Is(err, service.ErrUnsupportedParams), errors.Is(err, service.ErrBannedPrompt):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
case errors.Is(err, service.ErrInsufficientFunds):
|
||||
c.JSON(http.StatusPaymentRequired, gin.H{"detail": "积分不足"})
|
||||
@@ -132,7 +148,7 @@ func (h *UserGenerationHandler) Test(c *gin.Context) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrUnknownModel):
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
||||
case errors.Is(err, service.ErrUnsupportedParams):
|
||||
case errors.Is(err, service.ErrUnsupportedParams), errors.Is(err, service.ErrBannedPrompt):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
case errors.Is(err, service.ErrProviderQuota):
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{"detail": err.Error()})
|
||||
@@ -206,7 +222,14 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
|
||||
// rows with real media (success + stored file), not failed/pending events.
|
||||
hasFile := c.Query("has_file") == "1" || c.Query("has_file") == "true"
|
||||
|
||||
items, total, stats, err := h.admin.Logs(c.Request.Context(), limit, offset, kind, status, statuses, nil, userID, excludeSource, source, hasFile)
|
||||
// Media views hide homepage showcase files — those belong to the public
|
||||
// landing page, not to the caller's personal works. Galleries imply it via
|
||||
// has_file; the 画图台 grid opts in with exclude_showcase=1.
|
||||
excludeShowcase := hasFile || c.Query("exclude_showcase") == "1"
|
||||
// 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)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to load logs"})
|
||||
return
|
||||
|
||||
@@ -316,7 +316,7 @@ func (h *V1Handler) writeV1Error(c *gin.Context, err error, payload map[string]a
|
||||
switch {
|
||||
case errors.Is(err, service.ErrUnknownModel):
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
||||
case errors.Is(err, service.ErrUnsupportedParams):
|
||||
case errors.Is(err, service.ErrUnsupportedParams), errors.Is(err, service.ErrBannedPrompt):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
case errors.Is(err, service.ErrInsufficientFunds):
|
||||
c.JSON(http.StatusPaymentRequired, gin.H{"detail": err.Error()})
|
||||
|
||||
@@ -28,6 +28,7 @@ type Handlers struct {
|
||||
ConcGroups *handler.ConcurrencyGroupHandler
|
||||
Announcement *handler.AnnouncementHandler
|
||||
Payment *handler.PaymentHandler
|
||||
BannedWords *handler.BannedWordsHandler
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, auth *service.AuthService, handlers Handlers) *gin.Engine {
|
||||
@@ -87,6 +88,7 @@ func New(cfg *config.Config, auth *service.AuthService, handlers Handlers) *gin.
|
||||
userAuthed.POST("/test", handlers.UserGen.Test)
|
||||
userAuthed.GET("/jobs/mine", handlers.UserGen.MyJobs)
|
||||
userAuthed.GET("/my-images", handlers.UserGen.MyImages)
|
||||
userAuthed.DELETE("/my-files", handlers.UserGen.DeleteMyFile)
|
||||
userAuthed.GET("/announcement", handlers.Announcement.Get)
|
||||
userAuthed.POST("/announcement/seen", handlers.Announcement.MarkSeen)
|
||||
userAuthed.GET("/pay/config", handlers.Payment.Config)
|
||||
@@ -137,6 +139,10 @@ func New(cfg *config.Config, auth *service.AuthService, handlers Handlers) *gin.
|
||||
authed.GET("/accounts/:pool/:id/email", handlers.ProviderAdmin.AccountEmail)
|
||||
authed.GET("/providers", handlers.AdminRead.Providers)
|
||||
authed.GET("/images", handlers.AdminRead.Images)
|
||||
authed.DELETE("/images", handlers.AdminRead.DeleteImage)
|
||||
authed.GET("/banned-words", handlers.BannedWords.List)
|
||||
authed.POST("/banned-words", handlers.BannedWords.Create)
|
||||
authed.DELETE("/banned-words/:id", handlers.BannedWords.Delete)
|
||||
authed.GET("/refresh/profiles", handlers.ProviderAdmin.RefreshProfiles)
|
||||
authed.POST("/refresh/profiles/:profile_id/refresh-now", handlers.ProviderAdmin.RefreshNow)
|
||||
authed.PATCH("/refresh/profiles/:profile_id", handlers.ProviderAdmin.RefreshUpdate)
|
||||
|
||||
Reference in New Issue
Block a user