fix(设置): 修复站点设置保存失败的问题
问题: 1. Setting.value 字段设置了 NotEmpty() 约束,导致保存空字符串值时验证失败 2. 数据库 settings 表缺少 key 字段的唯一约束,导致 ON CONFLICT 语句执行失败 修复: - 移除 ent/schema/setting.go 中 value 字段的 NotEmpty() 约束 - 新增迁移 015_fix_settings_unique_constraint.sql 添加缺失的唯一约束 - 添加3个回归测试确保空值保存功能正常 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -415,10 +415,6 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// settingDescValue is the schema descriptor for value field.
|
|
||||||
settingDescValue := settingFields[1].Descriptor()
|
|
||||||
// setting.ValueValidator is a validator for the "value" field. It is called by the builders before save.
|
|
||||||
setting.ValueValidator = settingDescValue.Validators[0].(func(string) error)
|
|
||||||
// settingDescUpdatedAt is the schema descriptor for updated_at field.
|
// settingDescUpdatedAt is the schema descriptor for updated_at field.
|
||||||
settingDescUpdatedAt := settingFields[2].Descriptor()
|
settingDescUpdatedAt := settingFields[2].Descriptor()
|
||||||
// setting.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
// setting.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ func (Setting) Fields() []ent.Field {
|
|||||||
NotEmpty().
|
NotEmpty().
|
||||||
Unique(),
|
Unique(),
|
||||||
field.String("value").
|
field.String("value").
|
||||||
NotEmpty().
|
|
||||||
SchemaType(map[string]string{
|
SchemaType(map[string]string{
|
||||||
dialect.Postgres: "text",
|
dialect.Postgres: "text",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ func ValidColumn(column string) bool {
|
|||||||
var (
|
var (
|
||||||
// KeyValidator is a validator for the "key" field. It is called by the builders before save.
|
// KeyValidator is a validator for the "key" field. It is called by the builders before save.
|
||||||
KeyValidator func(string) error
|
KeyValidator func(string) error
|
||||||
// ValueValidator is a validator for the "value" field. It is called by the builders before save.
|
|
||||||
ValueValidator func(string) error
|
|
||||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||||
DefaultUpdatedAt func() time.Time
|
DefaultUpdatedAt func() time.Time
|
||||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||||
|
|||||||
@@ -102,11 +102,6 @@ func (_c *SettingCreate) check() error {
|
|||||||
if _, ok := _c.mutation.Value(); !ok {
|
if _, ok := _c.mutation.Value(); !ok {
|
||||||
return &ValidationError{Name: "value", err: errors.New(`ent: missing required field "Setting.value"`)}
|
return &ValidationError{Name: "value", err: errors.New(`ent: missing required field "Setting.value"`)}
|
||||||
}
|
}
|
||||||
if v, ok := _c.mutation.Value(); ok {
|
|
||||||
if err := setting.ValueValidator(v); err != nil {
|
|
||||||
return &ValidationError{Name: "value", err: fmt.Errorf(`ent: validator failed for field "Setting.value": %w`, err)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := _c.mutation.UpdatedAt(); !ok {
|
if _, ok := _c.mutation.UpdatedAt(); !ok {
|
||||||
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Setting.updated_at"`)}
|
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Setting.updated_at"`)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,11 +110,6 @@ func (_u *SettingUpdate) check() error {
|
|||||||
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "Setting.key": %w`, err)}
|
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "Setting.key": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := _u.mutation.Value(); ok {
|
|
||||||
if err := setting.ValueValidator(v); err != nil {
|
|
||||||
return &ValidationError{Name: "value", err: fmt.Errorf(`ent: validator failed for field "Setting.value": %w`, err)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,11 +249,6 @@ func (_u *SettingUpdateOne) check() error {
|
|||||||
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "Setting.key": %w`, err)}
|
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "Setting.key": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := _u.mutation.Value(); ok {
|
|
||||||
if err := setting.ValueValidator(v); err != nil {
|
|
||||||
return &ValidationError{Name: "value", err: fmt.Errorf(`ent: validator failed for field "Setting.value": %w`, err)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,3 +105,59 @@ func (s *SettingRepoSuite) TestSetMultiple_Upsert() {
|
|||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().Equal("new_val", got2)
|
s.Require().Equal("new_val", got2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSet_EmptyValue 测试保存空字符串值
|
||||||
|
// 这是一个回归测试,确保可选设置(如站点Logo、API端点地址等)可以保存为空字符串
|
||||||
|
func (s *SettingRepoSuite) TestSet_EmptyValue() {
|
||||||
|
// 测试 Set 方法保存空值
|
||||||
|
s.Require().NoError(s.repo.Set(s.ctx, "empty_key", ""), "Set with empty value should succeed")
|
||||||
|
|
||||||
|
got, err := s.repo.GetValue(s.ctx, "empty_key")
|
||||||
|
s.Require().NoError(err, "GetValue for empty value")
|
||||||
|
s.Require().Equal("", got, "empty value should be preserved")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSetMultiple_WithEmptyValues 测试批量保存包含空字符串的设置
|
||||||
|
// 模拟用户保存站点设置时部分字段为空的场景
|
||||||
|
func (s *SettingRepoSuite) TestSetMultiple_WithEmptyValues() {
|
||||||
|
// 模拟保存站点设置,部分字段有值,部分字段为空
|
||||||
|
settings := map[string]string{
|
||||||
|
"site_name": "AICodex2API",
|
||||||
|
"site_subtitle": "Subscription to API",
|
||||||
|
"site_logo": "", // 用户未上传Logo
|
||||||
|
"api_base_url": "", // 用户未设置API地址
|
||||||
|
"contact_info": "", // 用户未设置联系方式
|
||||||
|
"doc_url": "", // 用户未设置文档链接
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Require().NoError(s.repo.SetMultiple(s.ctx, settings), "SetMultiple with empty values should succeed")
|
||||||
|
|
||||||
|
// 验证所有值都正确保存
|
||||||
|
result, err := s.repo.GetMultiple(s.ctx, []string{"site_name", "site_subtitle", "site_logo", "api_base_url", "contact_info", "doc_url"})
|
||||||
|
s.Require().NoError(err, "GetMultiple after SetMultiple with empty values")
|
||||||
|
|
||||||
|
s.Require().Equal("AICodex2API", result["site_name"])
|
||||||
|
s.Require().Equal("Subscription to API", result["site_subtitle"])
|
||||||
|
s.Require().Equal("", result["site_logo"], "empty site_logo should be preserved")
|
||||||
|
s.Require().Equal("", result["api_base_url"], "empty api_base_url should be preserved")
|
||||||
|
s.Require().Equal("", result["contact_info"], "empty contact_info should be preserved")
|
||||||
|
s.Require().Equal("", result["doc_url"], "empty doc_url should be preserved")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSetMultiple_UpdateToEmpty 测试将已有值更新为空字符串
|
||||||
|
// 确保用户可以清空之前设置的值
|
||||||
|
func (s *SettingRepoSuite) TestSetMultiple_UpdateToEmpty() {
|
||||||
|
// 先设置非空值
|
||||||
|
s.Require().NoError(s.repo.Set(s.ctx, "clearable_key", "initial_value"))
|
||||||
|
|
||||||
|
got, err := s.repo.GetValue(s.ctx, "clearable_key")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Equal("initial_value", got)
|
||||||
|
|
||||||
|
// 更新为空值
|
||||||
|
s.Require().NoError(s.repo.SetMultiple(s.ctx, map[string]string{"clearable_key": ""}), "Update to empty should succeed")
|
||||||
|
|
||||||
|
got, err = s.repo.GetValue(s.ctx, "clearable_key")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Equal("", got, "value should be updated to empty string")
|
||||||
|
}
|
||||||
|
|||||||
19
backend/migrations/015_fix_settings_unique_constraint.sql
Normal file
19
backend/migrations/015_fix_settings_unique_constraint.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-- 015_fix_settings_unique_constraint.sql
|
||||||
|
-- 修复 settings 表 key 字段缺失的唯一约束
|
||||||
|
-- 此约束是 ON CONFLICT ("key") DO UPDATE 语句所必需的
|
||||||
|
|
||||||
|
-- 检查并添加唯一约束(如果不存在)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
-- 检查是否已存在唯一约束
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conrelid = 'settings'::regclass
|
||||||
|
AND contype = 'u'
|
||||||
|
AND conname = 'settings_key_key'
|
||||||
|
) THEN
|
||||||
|
-- 添加唯一约束
|
||||||
|
ALTER TABLE settings ADD CONSTRAINT settings_key_key UNIQUE (key);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
Reference in New Issue
Block a user