Files
tamabox/internal/db/vote_form_fields.go
T
tamakyi e3e59e7c69 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.
2026-09-10 10:56:27 +08:00

185 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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("投票表单字段不存在")