refactor: Gitea 式目录改组,移除本分支 SPA 与杂项产物

将 model/service/handler/middleware 迁至 models/services/routers/api/modules/auth,并删除 frontend、embed_static、scripts 及误入库缓存/二进制。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 03:54:56 +08:00
parent 1414c71dec
commit 9fe299a45f
449 changed files with 0 additions and 52779 deletions

150
services/bounty.go Normal file
View File

@@ -0,0 +1,150 @@
package service
import (
"errors"
"git.iioio.com/freefire/jiang13-forum/model"
"gorm.io/gorm"
)
var (
ErrBountyNotOpen = errors.New("悬赏已结束或已退回")
ErrBountySelfAward = errors.New("不能采纳自己的回复")
ErrBountyInvalidPoint = errors.New("悬赏积分至少为 1")
ErrBountyRefundBlocked = errors.New("已有用户回复,无法自行取消悬赏,请采纳优质回复或联系管理员")
)
const bountyRefundBlockReason = "已有用户回复,无法自行取消悬赏,请采纳优质回复或联系管理员"
// CountEligibleBountyReplies 统计他人已发布的有效回复数(不含楼主)
func CountEligibleBountyReplies(db *gorm.DB, postID, authorID uint) (int64, error) {
if db == nil {
db = model.DB
}
var n int64
err := db.Model(&model.Comment{}).
Where("post_id = ? AND status = ? AND user_id != ?", postID, model.ContentStatusPublished, authorID).
Count(&n).Error
return n, err
}
// CanRefundBounty 当前查看者是否可取消悬赏(管理员始终可强制取消)
func CanRefundBounty(post *model.Post, viewerIsAdmin bool) (bool, string) {
if post == nil || post.PostType != model.PostTypeBounty {
return false, ""
}
if post.BountyStatus != model.BountyStatusOpen || post.BountyPoints < 1 {
return false, ""
}
if viewerIsAdmin {
return true, ""
}
n, err := CountEligibleBountyReplies(model.DB, post.ID, post.UserID)
if err != nil {
return false, ""
}
if n > 0 {
return false, bountyRefundBlockReason
}
return true, ""
}
// EscrowBounty 发帖时托管悬赏积分
func EscrowBounty(tx *gorm.DB, userID, postID uint, points int) error {
if points < 1 {
return ErrBountyInvalidPoint
}
_, err := AdjustPointsTx(tx, userID, -points, model.PointReasonBountyEscrow, "post", postID, "发布悬赏帖")
return err
}
// AwardBounty 采纳评论并发放悬赏
func AwardBounty(postID, operatorID uint, isAdmin bool, commentID uint) error {
var post model.Post
if err := model.DB.First(&post, postID).Error; err != nil {
return ErrPostNotFound
}
if post.PostType != model.PostTypeBounty {
return errors.New("非悬赏帖")
}
if !isAdmin && post.UserID != operatorID {
return ErrPermissionDenied
}
if post.BountyStatus != model.BountyStatusOpen || post.BountyPoints < 1 {
return ErrBountyNotOpen
}
var comment model.Comment
if err := model.DB.First(&comment, commentID).Error; err != nil {
return errors.New("评论不存在")
}
if comment.PostID != postID || comment.Status != model.ContentStatusPublished {
return errors.New("评论无效")
}
if comment.UserID == post.UserID {
return ErrBountySelfAward
}
points := post.BountyPoints
return model.DB.Transaction(func(tx *gorm.DB) error {
if _, err := AdjustPointsTx(tx, comment.UserID, points, model.PointReasonBountyAward, "post", postID, "悬赏采纳"); err != nil {
return err
}
return tx.Model(&post).Updates(map[string]interface{}{
"bounty_status": model.BountyStatusAwarded,
"bounty_comment_id": commentID,
}).Error
})
}
// RefundBounty 取消悬赏并退回积分
func RefundBounty(postID, operatorID uint, isAdmin bool) error {
var post model.Post
if err := model.DB.First(&post, postID).Error; err != nil {
return ErrPostNotFound
}
if post.PostType != model.PostTypeBounty {
return errors.New("非悬赏帖")
}
if !isAdmin && post.UserID != operatorID {
return ErrPermissionDenied
}
if post.BountyStatus != model.BountyStatusOpen || post.BountyPoints < 1 {
return ErrBountyNotOpen
}
if !isAdmin {
n, err := CountEligibleBountyReplies(model.DB, post.ID, post.UserID)
if err != nil {
return err
}
if n > 0 {
return ErrBountyRefundBlocked
}
}
points := post.BountyPoints
return model.DB.Transaction(func(tx *gorm.DB) error {
if _, err := AdjustPointsTx(tx, post.UserID, points, model.PointReasonBountyRefund, "post", postID, "悬赏退回"); err != nil {
return err
}
return tx.Model(&post).Updates(map[string]interface{}{
"bounty_status": model.BountyStatusRefunded,
"bounty_points": 0,
}).Error
})
}
// RefundBountyIfOpen 删帖时自动退回未采纳悬赏
func RefundBountyIfOpen(tx *gorm.DB, post *model.Post) error {
if post == nil || post.PostType != model.PostTypeBounty {
return nil
}
if post.BountyStatus != model.BountyStatusOpen || post.BountyPoints < 1 {
return nil
}
points := post.BountyPoints
if _, err := AdjustPointsTx(tx, post.UserID, points, model.PointReasonBountyRefund, "post", post.ID, "删帖退回悬赏"); err != nil {
return err
}
return tx.Model(post).Updates(map[string]interface{}{
"bounty_status": model.BountyStatusRefunded,
"bounty_points": 0,
}).Error
}