chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
// Copyright 2026 tamakyi. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tamakyi/TamaBox/internal/dbutil"
|
||||
)
|
||||
|
||||
var VoteOptions VoteOptionsStore
|
||||
|
||||
type VoteOptionsStore interface {
|
||||
Create(ctx context.Context, opts CreateVoteOptionOptions) (*VoteOption, error)
|
||||
BulkCreate(ctx context.Context, voteID uint, contents []string) ([]*VoteOption, error)
|
||||
ListByVoteID(ctx context.Context, voteID uint) ([]*VoteOption, error)
|
||||
ReplaceByVoteID(ctx context.Context, voteID uint, contents []string) error
|
||||
DeleteByVoteID(ctx context.Context, voteID uint) error
|
||||
DeleteByID(ctx context.Context, id uint) error
|
||||
CountByVoteID(ctx context.Context, voteID uint) (int64, error)
|
||||
CountByVoteIDs(ctx context.Context, voteIDs []uint) (map[uint]int64, error)
|
||||
}
|
||||
|
||||
func NewVoteOptionsStore(db *gorm.DB) VoteOptionsStore {
|
||||
return &voteOptions{db}
|
||||
}
|
||||
|
||||
type voteOptions struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
// VoteOption 选项表:单选/多选/评分/排序共用。
|
||||
type VoteOption struct {
|
||||
dbutil.Model
|
||||
VoteID uint `gorm:"index:idx_option_vote" json:"vote_id"`
|
||||
Content string `gorm:"size:200" json:"content"`
|
||||
SortOrder int `gorm:"default:0" json:"sort_order"`
|
||||
}
|
||||
|
||||
type CreateVoteOptionOptions struct {
|
||||
VoteID uint
|
||||
Content string
|
||||
SortOrder int
|
||||
}
|
||||
|
||||
func (db *voteOptions) Create(ctx context.Context, opts CreateVoteOptionOptions) (*VoteOption, error) {
|
||||
option := &VoteOption{
|
||||
VoteID: opts.VoteID,
|
||||
Content: opts.Content,
|
||||
SortOrder: opts.SortOrder,
|
||||
}
|
||||
if err := db.WithContext(ctx).Create(option).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "create vote option")
|
||||
}
|
||||
return option, nil
|
||||
}
|
||||
|
||||
// BulkCreate 批量创建选项(用于箱主一次提交全部选项)。
|
||||
func (db *voteOptions) BulkCreate(ctx context.Context, voteID uint, contents []string) ([]*VoteOption, error) {
|
||||
options := make([]*VoteOption, 0, len(contents))
|
||||
for i, content := range contents {
|
||||
options = append(options, &VoteOption{
|
||||
VoteID: voteID,
|
||||
Content: content,
|
||||
SortOrder: i,
|
||||
})
|
||||
}
|
||||
if len(options) == 0 {
|
||||
return options, nil
|
||||
}
|
||||
if err := db.WithContext(ctx).Create(&options).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "bulk create vote options")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// ListByVoteID 获取某投票的全部选项(按 SortOrder 升序,保证选项顺序稳定)。
|
||||
func (db *voteOptions) ListByVoteID(ctx context.Context, voteID uint) ([]*VoteOption, error) {
|
||||
var options []*VoteOption
|
||||
if err := db.WithContext(ctx).
|
||||
Where("vote_id = ?", voteID).
|
||||
Order("sort_order ASC, id ASC").
|
||||
Find(&options).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "list vote options by vote id")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// ReplaceByVoteID 在单个事务内替换投票的全部选项(先删后建),保证原子性。
|
||||
// 仅在草稿状态编辑时调用(发布后不可改选项,避免破坏历史投票归因)。
|
||||
func (db *voteOptions) ReplaceByVoteID(ctx context.Context, voteID uint, contents []string) error {
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("vote_id = ?", voteID).Delete(&VoteOption{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(contents) == 0 {
|
||||
return nil
|
||||
}
|
||||
options := make([]*VoteOption, 0, len(contents))
|
||||
for i, content := range contents {
|
||||
options = append(options, &VoteOption{VoteID: voteID, Content: content, SortOrder: i})
|
||||
}
|
||||
return tx.Create(&options).Error
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByVoteID 删除投票时级联清理全部选项。
|
||||
func (db *voteOptions) DeleteByVoteID(ctx context.Context, voteID uint) error {
|
||||
if err := db.WithContext(ctx).Where("vote_id = ?", voteID).Delete(&VoteOption{}).Error; err != nil {
|
||||
return errors.Wrap(err, "delete vote options by vote id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *voteOptions) DeleteByID(ctx context.Context, id uint) error {
|
||||
res := db.WithContext(ctx).Delete(&VoteOption{}, id)
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(res.Error, "delete vote option")
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return ErrVoteOptionNotExist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrVoteOptionNotExist = errors.New("投票选项不存在")
|
||||
|
||||
func (db *voteOptions) CountByVoteID(ctx context.Context, voteID uint) (int64, error) {
|
||||
var count int64
|
||||
if err := db.WithContext(ctx).Model(&VoteOption{}).Where("vote_id = ?", voteID).Count(&count).Error; err != nil {
|
||||
return 0, errors.Wrap(err, "count vote options by vote id")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByVoteIDs 一次查询批量统计多个投票的选项数(替代逐投票 N 次 COUNT,消除 N+1)。
|
||||
func (db *voteOptions) CountByVoteIDs(ctx context.Context, voteIDs []uint) (map[uint]int64, error) {
|
||||
out := make(map[uint]int64, len(voteIDs))
|
||||
if len(voteIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
type row struct {
|
||||
VoteID uint
|
||||
Cnt int64
|
||||
}
|
||||
var rows []row
|
||||
if err := db.WithContext(ctx).Model(&VoteOption{}).
|
||||
Select("vote_id, COUNT(id) AS cnt").
|
||||
Where("vote_id IN ?", voteIDs).
|
||||
Group("vote_id").
|
||||
Find(&rows).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "count vote options by vote ids")
|
||||
}
|
||||
for _, r := range rows {
|
||||
out[r.VoteID] = r.Cnt
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user