开源前加固认证与部署契约:加密随机 JWT、改密事务吊销、登录 CSRF,并禁止生产回落 localhost。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,10 +7,8 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/markdown"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -68,20 +66,6 @@ func InitDB(dsn string) error {
|
||||
return fmt.Errorf("自动迁移失败: %w", err)
|
||||
}
|
||||
|
||||
// 旧帖整帖可见性 → 正文 [hide] 块(幂等)
|
||||
if err := migratePostContentAccessToHideBlocks(db); err != nil {
|
||||
return fmt.Errorf("正文隐藏块迁移失败: %w", err)
|
||||
}
|
||||
// 旧式 :::hide / :::timeline → 行级 BBCode(幂等)
|
||||
if err := migrateLegacyDirectiveSyntax(db); err != nil {
|
||||
return fmt.Errorf("短代码语法迁移失败: %w", err)
|
||||
}
|
||||
|
||||
// 一次性:用签到累计回填 User.Points(仅余额仍为 0 且有签到积分的用户)
|
||||
if err := backfillPointsFromCheckin(db); err != nil {
|
||||
return fmt.Errorf("积分余额回填失败: %w", err)
|
||||
}
|
||||
|
||||
// RBAC:把初始管理员(id 最小的 admin,通常即首个注册账号)升级为站长;
|
||||
// 已存在 owner 时不动数据,保证幂等
|
||||
if err := ensureOwnerRole(db); err != nil {
|
||||
@@ -310,46 +294,6 @@ func ensureOwnerRole(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// backfillPointsFromCheckin 把历史签到积分写入 User.Points(幂等:仅 points=0 且有签到)
|
||||
func backfillPointsFromCheckin(db *gorm.DB) error {
|
||||
type row struct {
|
||||
UserID uint
|
||||
Total int
|
||||
}
|
||||
var rows []row
|
||||
if err := db.Raw(`
|
||||
SELECT c.user_id, COALESCE(SUM(c.points), 0)::int AS total
|
||||
FROM checkins c
|
||||
INNER JOIN users u ON u.id = c.user_id AND u.deleted_at IS NULL AND u.points = 0
|
||||
GROUP BY c.user_id
|
||||
HAVING COALESCE(SUM(c.points), 0) > 0
|
||||
`).Scan(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range rows {
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&User{}).Where("id = ? AND points = 0", r.UserID).
|
||||
Update("points", r.Total).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&PointLedger{
|
||||
UserID: r.UserID,
|
||||
Delta: r.Total,
|
||||
Balance: r.Total,
|
||||
Reason: PointReasonMigrateCheckin,
|
||||
Note: "历史签到积分回填",
|
||||
}).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
log.Printf("[model] 已回填 %d 名用户的签到积分余额", len(rows))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// seedDefaultBoards 写入默认板块
|
||||
func seedDefaultBoards(db *gorm.DB) {
|
||||
defaults := []Board{
|
||||
@@ -455,134 +399,3 @@ func ensureDefaultChatMemberships(db *gorm.DB) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migratePostContentAccessToHideBlocks 将旧帖整帖可见性包进 [hide] 块(幂等)。
|
||||
func migratePostContentAccessToHideBlocks(db *gorm.DB) error {
|
||||
var posts []Post
|
||||
if err := db.Unscoped().
|
||||
Where("content_access IN ? AND deleted_at IS NULL",
|
||||
[]string{ContentAccessLogin, ContentAccessReply, ContentAccessPoints}).
|
||||
Find(&posts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
n := 0
|
||||
for i := range posts {
|
||||
p := &posts[i]
|
||||
if markdown.HasHideBlocks(p.Content) {
|
||||
continue
|
||||
}
|
||||
kind := NormalizeContentAccess(p.ContentAccess)
|
||||
pts := 0
|
||||
if kind == ContentAccessPoints {
|
||||
pts = p.AccessPoints
|
||||
if pts <= 0 {
|
||||
pts = 1
|
||||
}
|
||||
}
|
||||
wrapped := markdown.WrapContentAsHide(kind, pts, p.Content)
|
||||
if err := db.Model(&Post{}).Where("id = ?", p.ID).
|
||||
Update("content", wrapped).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
n++
|
||||
}
|
||||
if n > 0 {
|
||||
log.Printf("[model] 旧帖可见性已迁入隐藏块:%d 篇", n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateLegacyDirectiveSyntax 将正文中的 :::hide / :::timeline 改写为 BBCode(幂等)。
|
||||
func migrateLegacyDirectiveSyntax(db *gorm.DB) error {
|
||||
type row struct {
|
||||
ID uint
|
||||
Content string
|
||||
}
|
||||
rewriteTable := func(table string, rows []row) (int, error) {
|
||||
n := 0
|
||||
for _, r := range rows {
|
||||
if !strings.Contains(r.Content, ":::") {
|
||||
continue
|
||||
}
|
||||
next := markdown.RewriteLegacyDirectives(r.Content)
|
||||
if next == r.Content {
|
||||
continue
|
||||
}
|
||||
if err := db.Table(table).Where("id = ?", r.ID).
|
||||
Update("content", next).Error; err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var posts []row
|
||||
if err := db.Model(&Post{}).Unscoped().Select("id", "content").
|
||||
Where("content LIKE ?", "%:::%").Find(&posts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := rewriteTable("posts", posts); err != nil {
|
||||
return err
|
||||
} else if n > 0 {
|
||||
log.Printf("[model] 帖子短代码改写:%d 篇", n)
|
||||
}
|
||||
|
||||
var comments []row
|
||||
if err := db.Model(&Comment{}).Unscoped().Select("id", "content").
|
||||
Where("content LIKE ?", "%:::%").Find(&comments).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := rewriteTable("comments", comments); err != nil {
|
||||
return err
|
||||
} else if n > 0 {
|
||||
log.Printf("[model] 评论短代码改写:%d 条", n)
|
||||
}
|
||||
|
||||
var anns []row
|
||||
if err := db.Model(&Announcement{}).Unscoped().Select("id", "content").
|
||||
Where("content LIKE ?", "%:::%").Find(&anns).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := rewriteTable("announcements", anns); err != nil {
|
||||
return err
|
||||
} else if n > 0 {
|
||||
log.Printf("[model] 公告短代码改写:%d 条", n)
|
||||
}
|
||||
|
||||
var pages []row
|
||||
if err := db.Model(&SitePage{}).Unscoped().Select("id", "content").
|
||||
Where("content LIKE ?", "%:::%").Find(&pages).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := rewriteTable("site_pages", pages); err != nil {
|
||||
return err
|
||||
} else if n > 0 {
|
||||
log.Printf("[model] 站点页短代码改写:%d 篇", n)
|
||||
}
|
||||
|
||||
var hist []struct {
|
||||
ID uint
|
||||
OldContent string
|
||||
}
|
||||
if err := db.Model(&CommentEditHistory{}).Select("id", "old_content").
|
||||
Where("old_content LIKE ?", "%:::%").Find(&hist).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
nHist := 0
|
||||
for _, r := range hist {
|
||||
next := markdown.RewriteLegacyDirectives(r.OldContent)
|
||||
if next == r.OldContent {
|
||||
continue
|
||||
}
|
||||
if err := db.Model(&CommentEditHistory{}).Where("id = ?", r.ID).
|
||||
Update("old_content", next).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
nHist++
|
||||
}
|
||||
if nHist > 0 {
|
||||
log.Printf("[model] 评论历史短代码改写:%d 条", nHist)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user