feat: 类型帖互动、积分经济与发帖体验
补齐问答采纳/重开、投票匿名与有票禁编、悬赏过期退回、抽奖回帖开奖,并接入积分账本与发帖附件可见性。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -39,10 +39,23 @@ func InitDB(dsn string) error {
|
||||
&User{}, &Board{}, &Post{}, &Comment{}, &RefreshToken{}, &Like{}, &Notification{}, &Checkin{},
|
||||
&Announcement{}, &SiteSetting{}, &Attachment{}, &UserBoard{}, &LoginLog{},
|
||||
&ChatRoom{}, &ChatRoomMember{}, &ChatMessage{},
|
||||
&PointLedger{}, &PostContentUnlock{}, &PostAttachment{}, &PostAttachmentUnlock{},
|
||||
&PostPollVote{}, &PostLotteryEntry{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("自动迁移失败: %w", err)
|
||||
}
|
||||
|
||||
// 旧 post_type=normal → discussion
|
||||
if err := db.Exec(`UPDATE posts SET post_type = ? WHERE post_type = ? OR post_type = '' OR post_type IS NULL`,
|
||||
PostTypeDiscussion, PostTypeNormal).Error; 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 {
|
||||
@@ -213,6 +226,46 @@ 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{
|
||||
|
||||
Reference in New Issue
Block a user