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.
185 lines
6.4 KiB
Go
185 lines
6.4 KiB
Go
// 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 VoteFormFields VoteFormFieldsStore
|
||
|
||
// CustomFieldMarker 参与端"其他/自定义选项"的保留标记值(radio/select/checkbox 字段)。
|
||
// 选中该值时,后端从 field_{id}_custom 读取参与者填写的文本;预设选项不允许使用该保留值。
|
||
const CustomFieldMarker = "__tb_custom_option__"
|
||
|
||
// 表单字段类型(type=form 时使用)
|
||
const (
|
||
VoteFormFieldTypeText = "text" // 单行文本
|
||
VoteFormFieldTypeTextarea = "textarea" // 多行文本
|
||
VoteFormFieldTypeSelect = "select" // 下拉单选
|
||
VoteFormFieldTypeRadio = "radio" // 单选
|
||
VoteFormFieldTypeCheckbox = "checkbox" // 多选
|
||
VoteFormFieldTypeNumber = "number" // 数字
|
||
VoteFormFieldTypeRating = "rating" // 评分
|
||
VoteFormFieldTypeRank = "rank" // 排序(对预置选项排 1..N)
|
||
)
|
||
|
||
type VoteFormFieldsStore interface {
|
||
Create(ctx context.Context, opts CreateVoteFormFieldOptions) (*VoteFormField, error)
|
||
BulkCreate(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) ([]*VoteFormField, error)
|
||
ListByVoteID(ctx context.Context, voteID uint) ([]*VoteFormField, error)
|
||
ReplaceByVoteID(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) error
|
||
CountByVoteID(ctx context.Context, voteID uint) (int64, error)
|
||
DeleteByVoteID(ctx context.Context, voteID uint) error
|
||
DeleteByID(ctx context.Context, id uint) error
|
||
}
|
||
|
||
func NewVoteFormFieldsStore(db *gorm.DB) VoteFormFieldsStore {
|
||
return &voteFormFields{db}
|
||
}
|
||
|
||
type voteFormFields struct {
|
||
*gorm.DB
|
||
}
|
||
|
||
// VoteFormField 表单字段定义表:结构化表单(type=form)的字段配置。
|
||
type VoteFormField struct {
|
||
dbutil.Model
|
||
VoteID uint `gorm:"index:idx_form_field_vote" json:"vote_id"`
|
||
FieldType string `gorm:"size:20" json:"field_type"`
|
||
Label string `gorm:"size:100" json:"label"`
|
||
Required bool `gorm:"default:false" json:"required"`
|
||
MaxLength int `gorm:"default:0" json:"max_length"` // 0 = 不限制(字数限制)
|
||
Options string `gorm:"type:text" json:"options"` // JSON 数组(select/radio/checkbox 用)
|
||
SortOrder int `gorm:"default:0" json:"sort_order"`
|
||
AllowCustom bool `gorm:"default:false" json:"allow_custom"` // 允许参与者补充自定义选项(radio/select/checkbox)
|
||
}
|
||
|
||
type CreateVoteFormFieldOptions struct {
|
||
VoteID uint
|
||
FieldType string
|
||
Label string
|
||
Required bool
|
||
MaxLength int
|
||
Options string
|
||
SortOrder int
|
||
AllowCustom bool
|
||
}
|
||
|
||
func (db *voteFormFields) Create(ctx context.Context, opts CreateVoteFormFieldOptions) (*VoteFormField, error) {
|
||
field := &VoteFormField{
|
||
VoteID: opts.VoteID,
|
||
FieldType: opts.FieldType,
|
||
Label: opts.Label,
|
||
Required: opts.Required,
|
||
MaxLength: opts.MaxLength,
|
||
Options: opts.Options,
|
||
SortOrder: opts.SortOrder,
|
||
AllowCustom: opts.AllowCustom,
|
||
}
|
||
if err := db.WithContext(ctx).Create(field).Error; err != nil {
|
||
return nil, errors.Wrap(err, "create vote form field")
|
||
}
|
||
return field, nil
|
||
}
|
||
|
||
// BulkCreate 批量创建表单字段(箱主一次提交全部字段配置)。
|
||
func (db *voteFormFields) BulkCreate(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) ([]*VoteFormField, error) {
|
||
formFields := make([]*VoteFormField, 0, len(fields))
|
||
for _, f := range fields {
|
||
formFields = append(formFields, &VoteFormField{
|
||
VoteID: voteID,
|
||
FieldType: f.FieldType,
|
||
Label: f.Label,
|
||
Required: f.Required,
|
||
MaxLength: f.MaxLength,
|
||
Options: f.Options,
|
||
SortOrder: f.SortOrder,
|
||
AllowCustom: f.AllowCustom,
|
||
})
|
||
}
|
||
if len(formFields) == 0 {
|
||
return formFields, nil
|
||
}
|
||
if err := db.WithContext(ctx).Create(&formFields).Error; err != nil {
|
||
return nil, errors.Wrap(err, "bulk create vote form fields")
|
||
}
|
||
return formFields, nil
|
||
}
|
||
|
||
// ListByVoteID 获取某投票的全部表单字段(按 SortOrder 升序)。
|
||
func (db *voteFormFields) ListByVoteID(ctx context.Context, voteID uint) ([]*VoteFormField, error) {
|
||
var fields []*VoteFormField
|
||
if err := db.WithContext(ctx).
|
||
Where("vote_id = ?", voteID).
|
||
Order("sort_order ASC, id ASC").
|
||
Find(&fields).Error; err != nil {
|
||
return nil, errors.Wrap(err, "list vote form fields by vote id")
|
||
}
|
||
return fields, nil
|
||
}
|
||
|
||
// CountByVoteID 统计某投票的表单字段数量(发布前完整性校验用)。
|
||
func (db *voteFormFields) CountByVoteID(ctx context.Context, voteID uint) (int64, error) {
|
||
var count int64
|
||
if err := db.WithContext(ctx).Model(&VoteFormField{}).Where("vote_id = ?", voteID).Count(&count).Error; err != nil {
|
||
return 0, errors.Wrap(err, "count vote form fields by vote id")
|
||
}
|
||
return count, nil
|
||
}
|
||
|
||
// ReplaceByVoteID 在单个事务内替换投票的全部表单字段(先删后建),保证原子性。
|
||
// 仅在草稿状态编辑时调用。
|
||
func (db *voteFormFields) ReplaceByVoteID(ctx context.Context, voteID uint, fields []CreateVoteFormFieldOptions) error {
|
||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||
if err := tx.Where("vote_id = ?", voteID).Delete(&VoteFormField{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if len(fields) == 0 {
|
||
return nil
|
||
}
|
||
formFields := make([]*VoteFormField, 0, len(fields))
|
||
for _, f := range fields {
|
||
formFields = append(formFields, &VoteFormField{
|
||
VoteID: voteID,
|
||
FieldType: f.FieldType,
|
||
Label: f.Label,
|
||
Required: f.Required,
|
||
MaxLength: f.MaxLength,
|
||
Options: f.Options,
|
||
SortOrder: f.SortOrder,
|
||
AllowCustom: f.AllowCustom,
|
||
})
|
||
}
|
||
return tx.Create(&formFields).Error
|
||
})
|
||
}
|
||
|
||
// DeleteByVoteID 删除投票时级联清理全部表单字段。
|
||
func (db *voteFormFields) DeleteByVoteID(ctx context.Context, voteID uint) error {
|
||
if err := db.WithContext(ctx).Where("vote_id = ?", voteID).Delete(&VoteFormField{}).Error; err != nil {
|
||
return errors.Wrap(err, "delete vote form fields by vote id")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (db *voteFormFields) DeleteByID(ctx context.Context, id uint) error {
|
||
res := db.WithContext(ctx).Delete(&VoteFormField{}, id)
|
||
if res.Error != nil {
|
||
return errors.Wrap(res.Error, "delete vote form field")
|
||
}
|
||
if res.RowsAffected == 0 {
|
||
return ErrVoteFormFieldNotExist
|
||
}
|
||
return nil
|
||
}
|
||
|
||
var ErrVoteFormFieldNotExist = errors.New("投票表单字段不存在")
|