首页右栏接入单页入口与公告置顶/全部页;Markdown 编辑器支持可视化时间线与 Git 导入;隐藏块改为 [hide]/[timeline] 命名闭合,并修复单页/公告 GORM 更新只改 updated_at 的问题。 Co-authored-by: Cursor <cursoragent@cursor.com>
151 lines
4.5 KiB
Go
151 lines
4.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"errors"
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/freefire/jiang13-bbs/markdown"
|
||
"github.com/freefire/jiang13-bbs/middleware"
|
||
"github.com/freefire/jiang13-bbs/model"
|
||
"github.com/freefire/jiang13-bbs/service"
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// ===== 公开接口 =====
|
||
|
||
// AnnouncementsList 已发布公告列表(分页:page/size,默认 size=20)
|
||
func (h *Handlers) AnnouncementsList(c *gin.Context) {
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||
list, total, err := h.Announcement.ListPublishedPage(page, size)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公告失败"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"announcements": nonNilSlice(list),
|
||
"total": total,
|
||
"page": page,
|
||
})
|
||
}
|
||
|
||
// AnnouncementDetail 已发布公告详情(按读者能力脱敏 [hide])
|
||
func (h *Handlers) AnnouncementDetail(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||
return
|
||
}
|
||
a, err := h.Announcement.GetPublished(uint(id))
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公告失败"})
|
||
return
|
||
}
|
||
caps := markdown.ViewerCaps{}
|
||
if claims := middleware.CurrentUser(c); claims != nil {
|
||
caps.LoggedIn = true
|
||
if model.RoleLevel(model.Role(claims.Role)) >= model.RoleLevel(model.RoleAdmin) {
|
||
caps.Bypass = true
|
||
}
|
||
}
|
||
// 公告不可评论:回复块若有残留也永不解锁(保存期已拒收)
|
||
sanitized, _ := markdown.SanitizeForViewer(a.Content, caps)
|
||
out := *a
|
||
out.Content = sanitized
|
||
c.JSON(http.StatusOK, gin.H{"announcement": out})
|
||
}
|
||
|
||
// ===== 管理接口(RequireStaff + PermAnnouncements,前端不做权限判定) =====
|
||
|
||
// AdminListAnnouncements 全部公告(含草稿)
|
||
func (h *Handlers) AdminListAnnouncements(c *gin.Context) {
|
||
list, err := h.Announcement.ListAll()
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公告失败"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"announcements": nonNilSlice(list)})
|
||
}
|
||
|
||
// AdminCreateAnnouncement 新建公告
|
||
func (h *Handlers) AdminCreateAnnouncement(c *gin.Context) {
|
||
var in service.AnnouncementInput
|
||
if err := c.ShouldBindJSON(&in); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||
return
|
||
}
|
||
a, err := h.Announcement.Create(&in)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusCreated, gin.H{"announcement": a})
|
||
}
|
||
|
||
// AdminUpdateAnnouncement 编辑公告
|
||
func (h *Handlers) AdminUpdateAnnouncement(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||
return
|
||
}
|
||
var in service.AnnouncementInput
|
||
if err := c.ShouldBindJSON(&in); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||
return
|
||
}
|
||
a, err := h.Announcement.Update(uint(id), &in)
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"announcement": a})
|
||
}
|
||
|
||
// AdminToggleAnnouncementPin 切换公告置顶
|
||
func (h *Handlers) AdminToggleAnnouncementPin(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||
return
|
||
}
|
||
a, err := h.Announcement.TogglePin(uint(id))
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"announcement": a})
|
||
}
|
||
|
||
// AdminDeleteAnnouncement 删除公告
|
||
func (h *Handlers) AdminDeleteAnnouncement(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||
return
|
||
}
|
||
if err := h.Announcement.Delete(uint(id)); err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||
}
|