fix(admin): 行业选择500修复 + 管理员切换订阅计划
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
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
- fix(industry): list_industries SQL参数编号错位 — count查询和items查询 共用WHERE子句但参数从$3开始,sqlx bind按$1/$2顺序绑定导致500 - feat(billing): 新增 PUT /admin/accounts/:id/subscription 端点 (super_admin) 验证目标计划 → 取消当前订阅 → 创建新订阅(30天) → 同步配额 - feat(admin-v2): Accounts.tsx 编辑弹窗新增「订阅计划」选择区 显示所有活跃计划,保存时调用admin switch plan API
This commit is contained in:
@@ -15,24 +15,48 @@ pub async fn list_industries(
|
||||
) -> SaasResult<PaginatedResponse<IndustryListItem>> {
|
||||
let (page, page_size, offset) = normalize_pagination(query.page, query.page_size);
|
||||
|
||||
// 动态构建参数化查询 — 所有用户输入通过 $N 绑定
|
||||
let mut where_parts: Vec<String> = vec!["1=1".to_string()];
|
||||
let mut param_idx = 3; // $1=LIMIT, $2=OFFSET, $3+=filters
|
||||
let status_param: Option<String> = query.status.clone();
|
||||
let source_param: Option<String> = query.source.clone();
|
||||
|
||||
// 构建 WHERE 条件 — 每个查询独立的参数编号
|
||||
let mut where_parts: Vec<String> = vec!["1=1".to_string()];
|
||||
|
||||
// count 查询:参数从 $1 开始
|
||||
let mut count_params: Vec<String> = Vec::new();
|
||||
let mut count_idx = 1;
|
||||
if status_param.is_some() {
|
||||
where_parts.push(format!("status = ${}", param_idx));
|
||||
param_idx += 1;
|
||||
count_params.push(format!("status = ${}", count_idx));
|
||||
count_idx += 1;
|
||||
}
|
||||
if source_param.is_some() {
|
||||
where_parts.push(format!("source = ${}", param_idx));
|
||||
param_idx += 1;
|
||||
count_params.push(format!("source = ${}", count_idx));
|
||||
count_idx += 1;
|
||||
}
|
||||
let where_sql = where_parts.join(" AND ");
|
||||
let count_where = if count_params.is_empty() {
|
||||
"1=1".to_string()
|
||||
} else {
|
||||
format!("1=1 AND {}", count_params.join(" AND "))
|
||||
};
|
||||
|
||||
// items 查询:$1=LIMIT, $2=OFFSET, $3+=filters
|
||||
let mut items_params: Vec<String> = Vec::new();
|
||||
let mut items_idx = 3;
|
||||
if status_param.is_some() {
|
||||
items_params.push(format!("status = ${}", items_idx));
|
||||
items_idx += 1;
|
||||
}
|
||||
if source_param.is_some() {
|
||||
items_params.push(format!("source = ${}", items_idx));
|
||||
items_idx += 1;
|
||||
}
|
||||
let items_where = if items_params.is_empty() {
|
||||
"1=1".to_string()
|
||||
} else {
|
||||
format!("1=1 AND {}", items_params.join(" AND "))
|
||||
};
|
||||
|
||||
// count 查询
|
||||
let count_sql = format!("SELECT COUNT(*) FROM industries WHERE {}", where_sql);
|
||||
let count_sql = format!("SELECT COUNT(*) FROM industries WHERE {}", count_where);
|
||||
let mut count_q = sqlx::query_scalar::<_, i64>(&count_sql);
|
||||
if let Some(ref s) = status_param { count_q = count_q.bind(s); }
|
||||
if let Some(ref s) = source_param { count_q = count_q.bind(s); }
|
||||
@@ -44,7 +68,7 @@ pub async fn list_industries(
|
||||
COALESCE(jsonb_array_length(keywords), 0) as keywords_count, \
|
||||
created_at, updated_at \
|
||||
FROM industries WHERE {} ORDER BY source, id LIMIT $1 OFFSET $2",
|
||||
where_sql
|
||||
items_where
|
||||
);
|
||||
let mut items_q = sqlx::query_as::<_, IndustryListItem>(&items_sql)
|
||||
.bind(page_size as i64)
|
||||
|
||||
Reference in New Issue
Block a user