fix(saas): 启动崩溃修复 — config_items 约束 + industry 类型匹配
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- db.rs: config_items INSERT ON CONFLICT (id) → (category, key_path) 匹配实际唯一约束
- db.rs: fix_seed_data category 重命名前先删除冲突行,避免唯一约束冲突
- migration/service.rs: seed_default_config_items + sync push INSERT 同步修复 ON CONFLICT
- industry/types.rs: keywords_count i64→i32 匹配 PostgreSQL INT4 列类型

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-14 18:35:24 +08:00
parent 70229119be
commit d2a0c8efc0
3 changed files with 15 additions and 5 deletions

View File

@@ -742,7 +742,7 @@ async fn seed_demo_data(pool: &PgPool) -> SaasResult<()> {
let id = format!("cfg-{}-{}", cat, key); let id = format!("cfg-{}-{}", cat, key);
sqlx::query( sqlx::query(
"INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, created_at, updated_at) "INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, $8, $8) ON CONFLICT (id) DO NOTHING" VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, $8, $8) ON CONFLICT (category, key_path) DO NOTHING"
).bind(&id).bind(cat).bind(key).bind(vtype).bind(current).bind(default).bind(desc).bind(&ts) ).bind(&id).bind(cat).bind(key).bind(vtype).bind(current).bind(default).bind(desc).bind(&ts)
.execute(pool).await?; .execute(pool).await?;
} }
@@ -854,6 +854,7 @@ async fn fix_seed_data(pool: &PgPool) -> SaasResult<()> {
let admin_ids: Vec<String> = admins.into_iter().map(|(id,)| id).collect(); let admin_ids: Vec<String> = admins.into_iter().map(|(id,)| id).collect();
// 2. 更新 config_items 分类名(旧 → 新) // 2. 更新 config_items 分类名(旧 → 新)
// 先删除目标 (category, key_path) 已存在的旧 category 行,避免唯一约束冲突
let category_mappings = [ let category_mappings = [
("server", "general"), ("server", "general"),
("llm", "model"), ("llm", "model"),
@@ -862,6 +863,13 @@ async fn fix_seed_data(pool: &PgPool) -> SaasResult<()> {
("security", "rate_limit"), ("security", "rate_limit"),
]; ];
for (old_cat, new_cat) in &category_mappings { for (old_cat, new_cat) in &category_mappings {
// 删除旧 category 中与目标 category key_path 冲突的行
sqlx::query(
"DELETE FROM config_items WHERE category = $1 AND key_path IN \
(SELECT key_path FROM config_items WHERE category = $2)"
).bind(old_cat).bind(new_cat)
.execute(pool).await?;
let result = sqlx::query( let result = sqlx::query(
"UPDATE config_items SET category = $1, updated_at = $2 WHERE category = $3" "UPDATE config_items SET category = $1, updated_at = $2 WHERE category = $3"
).bind(new_cat).bind(&now).bind(old_cat) ).bind(new_cat).bind(&now).bind(old_cat)
@@ -889,7 +897,7 @@ async fn fix_seed_data(pool: &PgPool) -> SaasResult<()> {
let id = format!("cfg-{}-{}", cat, key); let id = format!("cfg-{}-{}", cat, key);
sqlx::query( sqlx::query(
"INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, created_at, updated_at) "INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, $8, $8) ON CONFLICT (id) DO NOTHING" VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, $8, $8) ON CONFLICT (category, key_path) DO NOTHING"
).bind(&id).bind(cat).bind(key).bind(vtype).bind(current).bind(default).bind(desc).bind(&now) ).bind(&id).bind(cat).bind(key).bind(vtype).bind(current).bind(default).bind(desc).bind(&now)
.execute(pool).await?; .execute(pool).await?;
} }

View File

@@ -29,7 +29,7 @@ pub struct IndustryListItem {
pub description: String, pub description: String,
pub status: String, pub status: String,
pub source: String, pub source: String,
pub keywords_count: i64, pub keywords_count: i32,
pub created_at: chrono::DateTime<chrono::Utc>, pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>, pub updated_at: chrono::DateTime<chrono::Utc>,
} }

View File

@@ -258,7 +258,8 @@ pub async fn seed_default_config_items(db: &PgPool) -> SaasResult<usize> {
let id = uuid::Uuid::new_v4().to_string(); let id = uuid::Uuid::new_v4().to_string();
sqlx::query( sqlx::query(
"INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at) "INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, false, $8, $8)" VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, false, $8, $8)
ON CONFLICT (category, key_path) DO NOTHING"
) )
.bind(&id).bind(category).bind(key_path).bind(value_type) .bind(&id).bind(category).bind(key_path).bind(value_type)
.bind(current_value).bind(default_value).bind(description).bind(&now) .bind(current_value).bind(default_value).bind(description).bind(&now)
@@ -374,7 +375,8 @@ pub async fn sync_config(
let category = parts.first().unwrap_or(&"general").to_string(); let category = parts.first().unwrap_or(&"general").to_string();
sqlx::query( sqlx::query(
"INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at) "INSERT INTO config_items (id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at)
VALUES ($1, $2, $3, 'string', $4, $4, 'local', '客户端推送', false, $5, $5)" VALUES ($1, $2, $3, 'string', $4, $4, 'local', '客户端推送', false, $5, $5)
ON CONFLICT (category, key_path) DO NOTHING"
) )
.bind(&id).bind(&category).bind(key).bind(val).bind(&now) .bind(&id).bind(&category).bind(key).bind(val).bind(&now)
.execute(db).await?; .execute(db).await?;