feat: 可选社区上报与官方精选展柜
默认关闭,仪表盘一键开关;枢纽由运维配置收报,人工精选后展示于 /showcase。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,10 +9,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"git.iioio.com/freefire/jiang13-forum/middleware"
|
||||
"git.iioio.com/freefire/jiang13-forum/model"
|
||||
"git.iioio.com/freefire/jiang13-forum/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// APIMe 当前登录用户
|
||||
@@ -160,12 +160,12 @@ func (h *Handlers) APIAdminDashboard(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"users": userCount, "posts": postCount, "boards": boardCount,
|
||||
"comments": commentCount,
|
||||
"pending_posts": pendingPosts,
|
||||
"pending_comments": pendingComments,
|
||||
"pending_reports": pendingReports,
|
||||
"comments": commentCount,
|
||||
"pending_posts": pendingPosts,
|
||||
"pending_comments": pendingComments,
|
||||
"pending_reports": pendingReports,
|
||||
"pending_friend_links": pendingFriendLinks,
|
||||
"recent_posts": recentPosts,
|
||||
"recent_posts": recentPosts,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -574,6 +574,7 @@ func (h *Handlers) APIAdminSettings(c *gin.Context) {
|
||||
"gitea": h.Settings.GiteaSyncConfigPublic(),
|
||||
"storage": h.Settings.StorageConfigPublic(),
|
||||
"branding": h.Settings.SiteBranding(),
|
||||
"community": h.Settings.CommunityConfig(),
|
||||
"filter_words": filterContent,
|
||||
"filter_word_count": service.CountFilterWords(filterContent),
|
||||
})
|
||||
@@ -994,10 +995,10 @@ func (h *Handlers) APIPosts(c *gin.Context) {
|
||||
h.Badge.AttachBadgeSummaries(users, 2)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"posts": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"posts": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"has_more": int64(page*size) < total,
|
||||
})
|
||||
}
|
||||
|
||||
139
handler/community.go
Normal file
139
handler/community.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/service"
|
||||
)
|
||||
|
||||
// APICommunityHeartbeat 公开心跳入口(仅枢纽开启时写入)
|
||||
func (h *Handlers) APICommunityHeartbeat(c *gin.Context) {
|
||||
if h.Community == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "未启用"})
|
||||
return
|
||||
}
|
||||
var req service.CommunityHeartbeatPayload
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
if err := h.Community.ReceiveHeartbeat(req, c.ClientIP()); err != nil {
|
||||
if errors.Is(err, service.ErrCommunityHubDisabled) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "本站未开启社区枢纽"})
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrCommunityBadPayload) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "心跳参数无效"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// APICommunityShowcase 公开展柜(仅精选)
|
||||
func (h *Handlers) APICommunityShowcase(c *gin.Context) {
|
||||
if h.Community == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"items": []any{}})
|
||||
return
|
||||
}
|
||||
list, err := h.Community.ListShowcase()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": list})
|
||||
}
|
||||
|
||||
// APIAdminCommunityInstances 公网实例列表
|
||||
func (h *Handlers) APIAdminCommunityInstances(c *gin.Context) {
|
||||
if h.Community == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"instances": []any{}, "hub_enabled": false})
|
||||
return
|
||||
}
|
||||
cfg := h.Settings.CommunityConfig()
|
||||
list, err := h.Community.ListInstances()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"hub_enabled": cfg.HubEnabled,
|
||||
"instances": list,
|
||||
})
|
||||
}
|
||||
|
||||
// APIAdminFeatureCommunityInstance 精选 / 取消精选
|
||||
func (h *Handlers) APIAdminFeatureCommunityInstance(c *gin.Context) {
|
||||
if h.Community == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "未启用"})
|
||||
return
|
||||
}
|
||||
var req service.CommunityFeatureInput
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
view, err := h.Community.SetInstanceFeatured(c.Param("id"), req)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrCommunityBadPayload) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "实例无效"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "实例不存在"})
|
||||
return
|
||||
}
|
||||
msg := "已取消精选"
|
||||
if view.Featured {
|
||||
msg = "已设为精选,将出现在公开展柜"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": msg, "instance": view})
|
||||
}
|
||||
|
||||
// APIAdminUpdateCommunitySettings 更新社区上报设置
|
||||
func (h *Handlers) APIAdminUpdateCommunitySettings(c *gin.Context) {
|
||||
var req service.CommunityConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
_, err := h.Settings.UpdateCommunityConfig(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
cfg := h.Settings.CommunityConfig()
|
||||
out := gin.H{
|
||||
"message": "社区设置已保存",
|
||||
"community": cfg,
|
||||
}
|
||||
if cfg.ReportEnabled && h.Community != nil {
|
||||
origin := communityRequestOrigin(c)
|
||||
if err := h.Community.SendHeartbeatOnce(origin); err != nil {
|
||||
out["message"] = "社区设置已保存,但心跳未成功"
|
||||
out["heartbeat_error"] = err.Error()
|
||||
// 刷新 site_url(可能已由 Origin 持久化)
|
||||
out["community"] = h.Settings.CommunityConfig()
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, out)
|
||||
}
|
||||
|
||||
// communityRequestOrigin 优先用浏览器 Origin(Vite 代理时 Host 可能是后端端口)
|
||||
func communityRequestOrigin(c *gin.Context) string {
|
||||
if o := strings.TrimSpace(c.GetHeader("Origin")); o != "" {
|
||||
return o
|
||||
}
|
||||
if ref := strings.TrimSpace(c.GetHeader("Referer")); ref != "" {
|
||||
if u, err := url.Parse(ref); err == nil && u.Scheme != "" && u.Host != "" {
|
||||
return u.Scheme + "://" + u.Host
|
||||
}
|
||||
}
|
||||
return requestOrigin(c)
|
||||
}
|
||||
@@ -8,37 +8,38 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"git.iioio.com/freefire/jiang13-forum/config"
|
||||
"git.iioio.com/freefire/jiang13-forum/middleware"
|
||||
"git.iioio.com/freefire/jiang13-forum/model"
|
||||
"git.iioio.com/freefire/jiang13-forum/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Handlers 聚合所有 HTTP 处理器
|
||||
type Handlers struct {
|
||||
Cfg *config.Config
|
||||
Store *service.UploadStore
|
||||
Auth *service.AuthService
|
||||
User *service.UserService
|
||||
Board *service.BoardService
|
||||
Post *service.PostService
|
||||
Comment *service.CommentService
|
||||
Message *service.MessageService
|
||||
Notify *service.NotifyService
|
||||
Report *service.ReportService
|
||||
Backup *service.BackupService
|
||||
Filter *service.SensitiveFilter
|
||||
Limiter *service.RateLimiter
|
||||
Settings *service.ForumSettingsService
|
||||
Captcha *service.CaptchaService
|
||||
Mail *service.MailService
|
||||
EmailCode *service.EmailCodeService
|
||||
OIDC *service.OIDCService
|
||||
Gitea *service.GiteaService
|
||||
Points *service.PointsService
|
||||
Badge *service.BadgeService
|
||||
SitePage *service.SitePageService
|
||||
Cfg *config.Config
|
||||
Store *service.UploadStore
|
||||
Auth *service.AuthService
|
||||
User *service.UserService
|
||||
Board *service.BoardService
|
||||
Post *service.PostService
|
||||
Comment *service.CommentService
|
||||
Message *service.MessageService
|
||||
Notify *service.NotifyService
|
||||
Report *service.ReportService
|
||||
Backup *service.BackupService
|
||||
Community *service.CommunityService
|
||||
Filter *service.SensitiveFilter
|
||||
Limiter *service.RateLimiter
|
||||
Settings *service.ForumSettingsService
|
||||
Captcha *service.CaptchaService
|
||||
Mail *service.MailService
|
||||
EmailCode *service.EmailCodeService
|
||||
OIDC *service.OIDCService
|
||||
Gitea *service.GiteaService
|
||||
Points *service.PointsService
|
||||
Badge *service.BadgeService
|
||||
SitePage *service.SitePageService
|
||||
FriendLinkApply *service.FriendLinkApplyService
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user