请求日志按日 JSONL;page_views 不进主库,避免统计数据撑大 jiang13.db。 Co-authored-by: Cursor <cursoragent@cursor.com>
181 lines
5.7 KiB
Go
181 lines
5.7 KiB
Go
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(), requestOrigin(c)); 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(requestOrigin(c))
|
||
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.CommunityConfigForRequest(communityRequestOrigin(c))
|
||
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
|
||
}
|
||
origin := communityRequestOrigin(c)
|
||
cfg := h.Settings.CommunityConfigForRequest(origin)
|
||
out := gin.H{
|
||
"message": "社区设置已保存",
|
||
"community": cfg,
|
||
}
|
||
if cfg.ReportEnabled && !cfg.HubEnabled && h.Community != nil {
|
||
if err := h.Community.SendHeartbeatOnce(origin); err != nil {
|
||
out["message"] = "社区设置已保存,但心跳未成功"
|
||
out["heartbeat_error"] = err.Error()
|
||
// 刷新 site_url(可能已由 Origin 持久化)
|
||
out["community"] = h.Settings.CommunityConfigForRequest(origin)
|
||
}
|
||
}
|
||
c.JSON(http.StatusOK, out)
|
||
}
|
||
|
||
// APIAdminUpdateShowcaseEntry 更新开源展柜入口展示位置(左栏 / 右栏 / 页脚)
|
||
func (h *Handlers) APIAdminUpdateShowcaseEntry(c *gin.Context) {
|
||
var req struct {
|
||
NavShowShowcase *bool `json:"nav_show_showcase"`
|
||
FooterShowShowcase *bool `json:"footer_show_showcase"`
|
||
AsideShowShowcase *bool `json:"aside_show_showcase"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||
return
|
||
}
|
||
if req.NavShowShowcase == nil && req.FooterShowShowcase == nil && req.AsideShowShowcase == nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||
return
|
||
}
|
||
if req.NavShowShowcase != nil {
|
||
if err := h.Settings.SetNavShowShowcase(*req.NavShowShowcase); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
}
|
||
if req.FooterShowShowcase != nil {
|
||
if err := h.Settings.SetFooterShowShowcase(*req.FooterShowShowcase); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
}
|
||
if req.AsideShowShowcase != nil {
|
||
if err := h.Settings.SetAsideShowcaseEnabled(*req.AsideShowShowcase); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "展柜入口已保存",
|
||
"nav_show_showcase": h.Settings.NavShowShowcase(),
|
||
"footer_show_showcase": h.Settings.FooterShowShowcase(),
|
||
"aside_show_showcase": h.Settings.AsideShowShowcase(),
|
||
})
|
||
}
|
||
|
||
// 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)
|
||
}
|