Diff in /workspace/src/main.rs:1:
-use std::{collections::HashMap, env, net::{IpAddr, SocketAddr}, path::{Component, Path, PathBuf}, sync::{Arc, Mutex, OnceLock}, time::{Duration as StdDuration, Instant, SystemTime, UNIX_EPOCH}};
+use std::{
+    collections::HashMap,
+    env,
+    net::{IpAddr, SocketAddr},
+    path::{Component, Path, PathBuf},
+    sync::{Arc, Mutex, OnceLock},
+    time::{Duration as StdDuration, Instant, SystemTime, UNIX_EPOCH},
+};
 
 use anyhow::{Context, Result};
 use axum::{
Diff in /workspace/src/main.rs:15:
 use serde_json::{json, Value};
 use sha2::{Digest, Sha256};
 use sqlx::{postgres::PgPoolOptions, PgPool, Row};
-use tower_http::{cors::{AllowOrigin, CorsLayer}, services::ServeDir, trace::TraceLayer};
+use tower_http::{
+    cors::{AllowOrigin, CorsLayer},
+    services::ServeDir,
+    trace::TraceLayer,
+};
 use tracing::{error, info, warn};
 use uuid::Uuid;
 
Diff in /workspace/src/main.rs:34:
     seq: u16,
 }
 
-static ID_STATE: Mutex<IdState> = Mutex::new(IdState { last_millis: 0, seq: ID_MIN_SEQ });
+static ID_STATE: Mutex<IdState> = Mutex::new(IdState {
+    last_millis: 0,
+    seq: ID_MIN_SEQ,
+});
 static ID_WORKER_ID: OnceLock<u64> = OnceLock::new();
 
 struct LoginAttemptState {
Diff in /workspace/src/main.rs:66:
 
 impl<T: Serialize> ApiResponse<T> {
     fn ok(data: T) -> Json<Self> {
-        Json(Self { code: 0, data, msg: String::new() })
+        Json(Self {
+            code: 0,
+            data,
+            msg: String::new(),
+        })
     }
 }
 
Diff in /workspace/src/main.rs:73:
 fn api_error(code: i32, msg: impl Into<String>) -> Json<ApiResponse<Value>> {
-    Json(ApiResponse { code, data: Value::Null, msg: msg.into() })
+    Json(ApiResponse {
+        code,
+        data: Value::Null,
+        msg: msg.into(),
+    })
 }
 
 fn load_cors_origins() -> Result<Vec<HeaderValue>> {
Diff in /workspace/src/main.rs:81:
     let origins = raw
         .split(|c: char| c == ',' || c == ';' || c.is_whitespace())
         .filter(|entry| !entry.is_empty())
-        .map(|entry| entry.parse::<HeaderValue>().with_context(|| format!("invalid CORS_ALLOWED_ORIGINS entry: {entry}")))
+        .map(|entry| {
+            entry
+                .parse::<HeaderValue>()
+                .with_context(|| format!("invalid CORS_ALLOWED_ORIGINS entry: {entry}"))
+        })
         .collect::<Result<Vec<_>>>()?;
     if origins.is_empty() {
         anyhow::bail!("CORS_ALLOWED_ORIGINS must contain at least one origin");
Diff in /workspace/src/main.rs:97:
             if let Ok(net) = entry.parse::<IpNet>() {
                 return Ok(net);
             }
-            let ip: IpAddr = entry.parse().with_context(|| format!("invalid IP_ALLOWLIST entry: {entry}"))?;
+            let ip: IpAddr = entry
+                .parse()
+                .with_context(|| format!("invalid IP_ALLOWLIST entry: {entry}"))?;
             Ok(IpNet::from(ip))
         })
         .collect()
Diff in /workspace/src/main.rs:105:
 
 fn parse_forwarded_ip(value: &str) -> Option<IpAddr> {
     let value = value.trim();
-    value.parse::<IpAddr>().ok().or_else(|| value.parse::<SocketAddr>().ok().map(|addr| addr.ip()))
+    value
+        .parse::<IpAddr>()
+        .ok()
+        .or_else(|| value.parse::<SocketAddr>().ok().map(|addr| addr.ip()))
 }
 
 fn request_client_ip(headers: &HeaderMap, peer: SocketAddr) -> IpAddr {
Diff in /workspace/src/main.rs:133:
 
 fn login_retry_after_seconds(state: &AppState, client_ip: IpAddr) -> Option<u64> {
     let now = Instant::now();
-    let mut attempts = state.login_attempts.lock().expect("login attempts mutex poisoned");
-    let Some(entry) = attempts.get_mut(&client_ip) else { return None; };
+    let mut attempts = state
+        .login_attempts
+        .lock()
+        .expect("login attempts mutex poisoned");
+    let Some(entry) = attempts.get_mut(&client_ip) else {
+        return None;
+    };
 
     if let Some(blocked_until) = entry.blocked_until {
         if blocked_until > now {
Diff in /workspace/src/main.rs:151:
 
 fn record_login_failure(state: &AppState, client_ip: IpAddr) {
     let now = Instant::now();
-    let mut attempts = state.login_attempts.lock().expect("login attempts mutex poisoned");
+    let mut attempts = state
+        .login_attempts
+        .lock()
+        .expect("login attempts mutex poisoned");
     let entry = attempts.entry(client_ip).or_insert(LoginAttemptState {
         failures: 0,
         window_started: now,
Diff in /workspace/src/main.rs:176:
 }
 
 fn clear_login_failures(state: &AppState, client_ip: IpAddr) {
-    state.login_attempts.lock().expect("login attempts mutex poisoned").remove(&client_ip);
+    state
+        .login_attempts
+        .lock()
+        .expect("login attempts mutex poisoned")
+        .remove(&client_ip);
 }
 
 async fn enforce_ip_allowlist(
Diff in /workspace/src/main.rs:190:
     }
 
     let client_ip = request_client_ip(request.headers(), peer);
-    if client_ip.is_loopback() || state.ip_allowlist.iter().any(|network| network.contains(&client_ip)) {
+    if client_ip.is_loopback()
+        || state
+            .ip_allowlist
+            .iter()
+            .any(|network| network.contains(&client_ip))
+    {
         return next.run(request).await;
     }
 
Diff in /workspace/src/main.rs:207:
         let mut headers = HeaderMap::new();
         headers.insert("x-forwarded-for", "203.0.113.10:54321".parse().unwrap());
         let peer: SocketAddr = "127.0.0.1:40000".parse().unwrap();
-        assert_eq!(request_client_ip(&headers, peer), "203.0.113.10".parse::<IpAddr>().unwrap());
+        assert_eq!(
+            request_client_ip(&headers, peer),
+            "203.0.113.10".parse::<IpAddr>().unwrap()
+        );
     }
 
     #[test]
Diff in /workspace/src/main.rs:215:
         let mut headers = HeaderMap::new();
         headers.insert("x-forwarded-for", "203.0.113.10".parse().unwrap());
         let peer: SocketAddr = "198.51.100.20:40000".parse().unwrap();
-        assert_eq!(request_client_ip(&headers, peer), "198.51.100.20".parse::<IpAddr>().unwrap());
+        assert_eq!(
+            request_client_ip(&headers, peer),
+            "198.51.100.20".parse::<IpAddr>().unwrap()
+        );
     }
 
     #[test]
Diff in /workspace/src/main.rs:222:
     fn reads_refresh_cookie_without_exposing_other_cookies() {
         let mut headers = HeaderMap::new();
-        headers.insert(header::COOKIE, "theme=dark; design_refresh=test-token; locale=zh".parse().unwrap());
-        assert_eq!(cookie_value(&headers, REFRESH_COOKIE_NAME), Some("test-token"));
+        headers.insert(
+            header::COOKIE,
+            "theme=dark; design_refresh=test-token; locale=zh"
+                .parse()
+                .unwrap(),
+        );
+        assert_eq!(
+            cookie_value(&headers, REFRESH_COOKIE_NAME),
+            Some("test-token")
+        );
     }
 
     #[test]
Diff in /workspace/src/main.rs:238:
 }
 
 fn current_millis() -> u64 {
-    SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as u64
+    SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .unwrap_or_default()
+        .as_millis() as u64
 }
 
 fn id_worker_id() -> u64 {
Diff in /workspace/src/main.rs:245:
     *ID_WORKER_ID.get_or_init(|| {
-        env::var("ID_WORKER_ID").ok().and_then(|v| v.parse::<u64>().ok())
-            .filter(|v| *v < (1 << ID_WORKER_BITS)).unwrap_or(1)
+        env::var("ID_WORKER_ID")
+            .ok()
+            .and_then(|v| v.parse::<u64>().ok())
+            .filter(|v| *v < (1 << ID_WORKER_BITS))
+            .unwrap_or(1)
     })
 }
 
Diff in /workspace/src/main.rs:258:
             if state.seq >= ID_MAX_SEQ {
                 let last = state.last_millis;
                 drop(state);
-                while current_millis() <= last { std::hint::spin_loop(); }
+                while current_millis() <= last {
+                    std::hint::spin_loop();
+                }
                 continue;
             }
             state.seq += 1;
Diff in /workspace/src/main.rs:268:
         }
 
         let delta = logical_millis.saturating_sub(ID_BASE_TIME_MS);
-        let id = (delta << (ID_WORKER_BITS + ID_SEQ_BITS)) | (id_worker_id() << ID_SEQ_BITS) | u64::from(state.seq);
+        let id = (delta << (ID_WORKER_BITS + ID_SEQ_BITS))
+            | (id_worker_id() << ID_SEQ_BITS)
+            | u64::from(state.seq);
         return id as i64;
     }
 }
Diff in /workspace/src/main.rs:275:
 
 fn bearer(headers: &HeaderMap) -> Option<&str> {
-    headers.get(header::AUTHORIZATION)?.to_str().ok()?.strip_prefix("Bearer ")
+    headers
+        .get(header::AUTHORIZATION)?
+        .to_str()
+        .ok()?
+        .strip_prefix("Bearer ")
 }
 
-async fn optional_user_id(state: &AppState, headers: &HeaderMap) -> Result<Option<i64>, Json<ApiResponse<Value>>> {
+async fn optional_user_id(
+    state: &AppState,
+    headers: &HeaderMap,
+) -> Result<Option<i64>, Json<ApiResponse<Value>>> {
     #[cfg(feature = "e2e-test-auth")]
     if env::var("E2E_TEST_AUTH").as_deref() == Ok("1") {
-        if let Some(raw) = headers.get("x-design-screenshot-e2e-user").and_then(|v| v.to_str().ok()) {
+        if let Some(raw) = headers
+            .get("x-design-screenshot-e2e-user")
+            .and_then(|v| v.to_str().ok())
+        {
             if let Ok(user_id) = raw.parse::<i64>() {
-                let exists = sqlx::query_scalar::<_, bool>("SELECT EXISTS(SELECT 1 FROM system_users WHERE id=$1 AND deleted=0)")
-                    .bind(user_id)
-                    .fetch_one(&state.db)
-                    .await
-                    .map_err(|e| { error!(?e, "e2e user lookup failed"); api_error(500, "数据库错误") })?;
-                if exists { return Ok(Some(user_id)); }
+                let exists = sqlx::query_scalar::<_, bool>(
+                    "SELECT EXISTS(SELECT 1 FROM system_users WHERE id=$1 AND deleted=0)",
+                )
+                .bind(user_id)
+                .fetch_one(&state.db)
+                .await
+                .map_err(|e| {
+                    error!(?e, "e2e user lookup failed");
+                    api_error(500, "数据库错误")
+                })?;
+                if exists {
+                    return Ok(Some(user_id));
+                }
             }
             return Err(api_error(401, "E2E 测试用户无效"));
         }
Diff in /workspace/src/main.rs:294:
     }
 
-    let Some(token) = bearer(headers) else { return Ok(None); };
+    let Some(token) = bearer(headers) else {
+        return Ok(None);
+    };
     let row = sqlx::query("SELECT user_id, expires_time FROM system_oauth2_access_token WHERE access_token = $1 AND user_id = 1 AND deleted = 0 LIMIT 1")
         .bind(token)
         .fetch_optional(&state.db)
Diff in /workspace/src/main.rs:300:
         .await
         .map_err(|e| { error!(?e, "token query failed"); api_error(500, "数据库错误") })?;
-    let Some(row) = row else { return Err(api_error(401, "访问令牌不存在")); };
-    let expires: NaiveDateTime = row.try_get("expires_time").map_err(|_| api_error(401, "访问令牌无效"))?;
-    if expires < Utc::now().naive_utc() { return Err(api_error(401, "访问令牌已过期")); }
-    row.try_get("user_id").map(Some).map_err(|_| api_error(401, "访问令牌无效"))
+    let Some(row) = row else {
+        return Err(api_error(401, "访问令牌不存在"));
+    };
+    let expires: NaiveDateTime = row
+        .try_get("expires_time")
+        .map_err(|_| api_error(401, "访问令牌无效"))?;
+    if expires < Utc::now().naive_utc() {
+        return Err(api_error(401, "访问令牌已过期"));
+    }
+    row.try_get("user_id")
+        .map(Some)
+        .map_err(|_| api_error(401, "访问令牌无效"))
 }
 
-async fn authenticated_user_id(state: &AppState, headers: &HeaderMap) -> Result<i64, Json<ApiResponse<Value>>> {
-    optional_user_id(state, headers).await?.ok_or_else(|| api_error(401, "未登录"))
+async fn authenticated_user_id(
+    state: &AppState,
+    headers: &HeaderMap,
+) -> Result<i64, Json<ApiResponse<Value>>> {
+    optional_user_id(state, headers)
+        .await?
+        .ok_or_else(|| api_error(401, "未登录"))
 }
 
 async fn health() -> impl IntoResponse {
Diff in /workspace/src/main.rs:315:
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-struct LoginReq { username: String, password: String }
+struct LoginReq {
+    username: String,
+    password: String,
+}
 
 struct CreatedTokens {
     access: String,
Diff in /workspace/src/main.rs:332:
 }
 
 fn cookie_value<'a>(headers: &'a HeaderMap, name: &str) -> Option<&'a str> {
-    headers.get(header::COOKIE)?.to_str().ok()?.split(';').find_map(|part| {
-        let (cookie_name, value) = part.trim().split_once('=')?;
-        (cookie_name == name).then_some(value)
-    })
+    headers
+        .get(header::COOKIE)?
+        .to_str()
+        .ok()?
+        .split(';')
+        .find_map(|part| {
+            let (cookie_name, value) = part.trim().split_once('=')?;
+            (cookie_name == name).then_some(value)
+        })
 }
 
 fn refresh_cookie(token: &str, expires: NaiveDateTime, secure: bool) -> HeaderValue {
Diff in /workspace/src/main.rs:350:
     let secure_attr = if secure { "; Secure" } else { "" };
     HeaderValue::from_str(&format!(
         "{REFRESH_COOKIE_NAME}=; Path=/; Max-Age=0; HttpOnly; SameSite=Strict{secure_attr}"
-    )).expect("clear refresh cookie header is valid")
+    ))
+    .expect("clear refresh cookie header is valid")
 }
 
 fn auth_response(body: Json<ApiResponse<Value>>, cookie: HeaderValue) -> Response {
Diff in /workspace/src/main.rs:357:
     let mut response = body.into_response();
     response.headers_mut().insert(header::SET_COOKIE, cookie);
-    response.headers_mut().insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
     response
+        .headers_mut()
+        .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
+    response
 }
 
 async fn login(
Diff in /workspace/src/main.rs:368:
 ) -> Response {
     let client_ip = request_client_ip(&headers, peer);
     if let Some(retry_after) = login_retry_after_seconds(&state, client_ip) {
-        return api_error(429, format!("登录尝试过多，请在 {retry_after} 秒后重试")).into_response();
+        return api_error(429, format!("登录尝试过多，请在 {retry_after} 秒后重试"))
+            .into_response();
     }
 
     if req.username != state.admin_username || req.password != state.admin_password {
Diff in /workspace/src/main.rs:380:
     match create_tokens(&state.db, 1, state.access_token_ttl_seconds).await {
         Ok(tokens) => auth_response(
             ApiResponse::ok(tokens.response_value()),
-            refresh_cookie(&tokens.refresh, tokens.refresh_expires, state.auth_cookie_secure),
+            refresh_cookie(
+                &tokens.refresh,
+                tokens.refresh_expires,
+                state.auth_cookie_secure,
+            ),
         ),
         Err(e) => {
             error!(?e, "create token failed");
Diff in /workspace/src/main.rs:392:
 async fn default_oauth_ttls(db: &PgPool) -> Result<(i64, i64)> {
     let row = sqlx::query("SELECT access_token_validity_seconds,refresh_token_validity_seconds FROM system_oauth2_client WHERE client_id='default' AND deleted=0 AND status=0 LIMIT 1")
         .fetch_optional(db).await?.context("default OAuth2 client not found or disabled")?;
-    Ok((i64::from(row.try_get::<i32, _>("access_token_validity_seconds")?), i64::from(row.try_get::<i32, _>("refresh_token_validity_seconds")?)))
+    Ok((
+        i64::from(row.try_get::<i32, _>("access_token_validity_seconds")?),
+        i64::from(row.try_get::<i32, _>("refresh_token_validity_seconds")?),
+    ))
 }
 
 async fn create_tokens(db: &PgPool, user_id: i64, access_ttl: i64) -> Result<CreatedTokens> {
Diff in /workspace/src/main.rs:408:
     sqlx::query("INSERT INTO system_oauth2_access_token (id, access_token, refresh_token, user_id, user_type, client_id, scopes, expires_time, create_time, update_time, creator, updater, deleted) VALUES ($1,$2,$3,$4,2,'default','[]',$5,$6,$6,'rust-cookie-v1','rust-cookie-v1',0)")
         .bind(new_id()).bind(&access).bind(&refresh).bind(user_id).bind(access_expires).bind(now).execute(&mut *tx).await?;
     tx.commit().await?;
-    Ok(CreatedTokens { access, refresh, user_id, access_expires, refresh_expires })
+    Ok(CreatedTokens {
+        access,
+        refresh,
+        user_id,
+        access_expires,
+        refresh_expires,
+    })
 }
 
 async fn refresh_token(State(state): State<AppState>, headers: HeaderMap) -> Response {
Diff in /workspace/src/main.rs:421:
         Err(e) => { error!(?e); return api_error(500, "数据库错误").into_response(); }
     };
     // Do not clear the cookie for an unknown token: another tab may have just rotated it.
-    let Some(row) = row else { return api_error(401, "无效的刷新令牌").into_response(); };
-    let expires: NaiveDateTime = row.try_get("expires_time").unwrap_or_else(|_| Utc::now().naive_utc() - Duration::seconds(1));
+    let Some(row) = row else {
+        return api_error(401, "无效的刷新令牌").into_response();
+    };
+    let expires: NaiveDateTime = row
+        .try_get("expires_time")
+        .unwrap_or_else(|_| Utc::now().naive_utc() - Duration::seconds(1));
     if expires < Utc::now().naive_utc() {
-        let _ = sqlx::query("DELETE FROM system_oauth2_access_token WHERE refresh_token=$1").bind(&old_refresh).execute(&state.db).await;
-        let _ = sqlx::query("DELETE FROM system_oauth2_refresh_token WHERE refresh_token=$1").bind(&old_refresh).execute(&state.db).await;
-        return auth_response(api_error(401, "刷新令牌已过期"), clear_refresh_cookie(state.auth_cookie_secure));
+        let _ = sqlx::query("DELETE FROM system_oauth2_access_token WHERE refresh_token=$1")
+            .bind(&old_refresh)
+            .execute(&state.db)
+            .await;
+        let _ = sqlx::query("DELETE FROM system_oauth2_refresh_token WHERE refresh_token=$1")
+            .bind(&old_refresh)
+            .execute(&state.db)
+            .await;
+        return auth_response(
+            api_error(401, "刷新令牌已过期"),
+            clear_refresh_cookie(state.auth_cookie_secure),
+        );
     }
-    let token_id: i64 = match row.try_get("id") { Ok(v) => v, Err(_) => return api_error(500, "令牌数据异常").into_response() };
-    let user_id: i64 = match row.try_get("user_id") { Ok(v) => v, Err(_) => return api_error(500, "令牌数据异常").into_response() };
+    let token_id: i64 = match row.try_get("id") {
+        Ok(v) => v,
+        Err(_) => return api_error(500, "令牌数据异常").into_response(),
+    };
+    let user_id: i64 = match row.try_get("user_id") {
+        Ok(v) => v,
+        Err(_) => return api_error(500, "令牌数据异常").into_response(),
+    };
     let access_ttl = state.access_token_ttl_seconds;
     let now = Utc::now().naive_utc();
     let access = Uuid::new_v4().simple().to_string();
Diff in /workspace/src/main.rs:437:
     let access_expires = now + Duration::seconds(access_ttl);
     let mut tx = match state.db.begin().await {
         Ok(v) => v,
-        Err(e) => { error!(?e); return api_error(500, "数据库错误").into_response(); }
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误").into_response();
+        }
     };
-    if let Err(e) = sqlx::query("DELETE FROM system_oauth2_access_token WHERE refresh_token=$1").bind(&old_refresh).execute(&mut *tx).await {
-        error!(?e); return api_error(500, "刷新令牌失败").into_response();
+    if let Err(e) = sqlx::query("DELETE FROM system_oauth2_access_token WHERE refresh_token=$1")
+        .bind(&old_refresh)
+        .execute(&mut *tx)
+        .await
+    {
+        error!(?e);
+        return api_error(500, "刷新令牌失败").into_response();
     }
     let updated = match sqlx::query("UPDATE system_oauth2_refresh_token SET refresh_token=$1,update_time=$2 WHERE id=$3 AND refresh_token=$4 AND user_id=1 AND deleted=0")
         .bind(&new_refresh).bind(now).bind(token_id).bind(&old_refresh).execute(&mut *tx).await {
Diff in /workspace/src/main.rs:456:
         error!(?e); return api_error(500, "刷新令牌失败").into_response();
     }
     if let Err(e) = tx.commit().await {
-        error!(?e); return api_error(500, "刷新令牌失败").into_response();
+        error!(?e);
+        return api_error(500, "刷新令牌失败").into_response();
     }
     auth_response(
-        ApiResponse::ok(json!({"accessToken": access, "userId": user_id, "expiresTime": access_expires})),
+        ApiResponse::ok(
+            json!({"accessToken": access, "userId": user_id, "expiresTime": access_expires}),
+        ),
         refresh_cookie(&new_refresh, expires, state.auth_cookie_secure),
     )
 }
Diff in /workspace/src/main.rs:473:
             .bind(token).fetch_optional(&state.db).await {
             refresh_to_delete = row.try_get::<String, _>("refresh_token").ok().or(refresh_to_delete);
         }
-        let _ = sqlx::query("DELETE FROM system_oauth2_access_token WHERE access_token=$1 AND user_id=1").bind(token).execute(&state.db).await;
+        let _ = sqlx::query(
+            "DELETE FROM system_oauth2_access_token WHERE access_token=$1 AND user_id=1",
+        )
+        .bind(token)
+        .execute(&state.db)
+        .await;
     }
     if let Some(refresh) = refresh_to_delete {
-        let _ = sqlx::query("DELETE FROM system_oauth2_access_token WHERE refresh_token=$1 AND user_id=1").bind(&refresh).execute(&state.db).await;
-        let _ = sqlx::query("DELETE FROM system_oauth2_refresh_token WHERE refresh_token=$1 AND user_id=1").bind(&refresh).execute(&state.db).await;
+        let _ = sqlx::query(
+            "DELETE FROM system_oauth2_access_token WHERE refresh_token=$1 AND user_id=1",
+        )
+        .bind(&refresh)
+        .execute(&state.db)
+        .await;
+        let _ = sqlx::query(
+            "DELETE FROM system_oauth2_refresh_token WHERE refresh_token=$1 AND user_id=1",
+        )
+        .bind(&refresh)
+        .execute(&state.db)
+        .await;
     }
 
-    auth_response(ApiResponse::ok(json!(true)), clear_refresh_cookie(state.auth_cookie_secure))
+    auth_response(
+        ApiResponse::ok(json!(true)),
+        clear_refresh_cookie(state.auth_cookie_secure),
+    )
 }
 
 async fn user_info(State(state): State<AppState>, headers: HeaderMap) -> Json<ApiResponse<Value>> {
Diff in /workspace/src/main.rs:487:
-    let user_id = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
+    let user_id = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
     let sql = "SELECT nickname,email,mobile,avatar FROM system_users WHERE id=$1 AND deleted=0";
-    match sqlx::query(sql).bind(user_id).fetch_optional(&state.db).await {
+    match sqlx::query(sql)
+        .bind(user_id)
+        .fetch_optional(&state.db)
+        .await
+    {
         Ok(Some(row)) => ApiResponse::ok(json!({
             "id": user_id,
             "username": state.admin_username,
Diff in /workspace/src/main.rs:496:
             "avatar": row.try_get::<Option<String>, _>("avatar").ok().flatten(),
         })),
         Ok(None) => api_error(404, "管理员用户不存在"),
-        Err(e) => { error!(?e); api_error(500, "数据库错误") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "数据库错误")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:504:
 
 #[derive(Deserialize, Default)]
 #[serde(rename_all = "camelCase")]
-struct AppListQuery { platform: Option<String>, r#type: Option<i32> }
+struct AppListQuery {
+    platform: Option<String>,
+    r#type: Option<i32>,
+}
 
 fn visibility_mode(kind: Option<i32>, user_id: Option<i64>) -> Option<i32> {
     match kind {
Diff in /workspace/src/main.rs:514:
     }
 }
 
-async fn app_get_all(State(state): State<AppState>, headers: HeaderMap) -> Json<ApiResponse<Value>> {
-    let user_id = match optional_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let mode = if user_id == Some(1) { 4 } else if user_id.is_some() { 5 } else { 0 };
+async fn app_get_all(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+) -> Json<ApiResponse<Value>> {
+    let user_id = match optional_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
+    let mode = if user_id == Some(1) {
+        4
+    } else if user_id.is_some() {
+        5
+    } else {
+        0
+    };
     let sql = format!("SELECT {} FROM mse_app a WHERE a.deleted=0 AND (($1=0 AND a.status=1) OR $1=4 OR ($1=5 AND a.status=1 AND a.user_id IN (1,$2))) ORDER BY a.create_time DESC", APP_JSON);
-    match sqlx::query_scalar::<_, Value>(&sql).bind(mode).bind(user_id.unwrap_or(-1)).fetch_all(&state.db).await {
+    match sqlx::query_scalar::<_, Value>(&sql)
+        .bind(mode)
+        .bind(user_id.unwrap_or(-1))
+        .fetch_all(&state.db)
+        .await
+    {
         Ok(v) => ApiResponse::ok(json!(v)),
-        Err(e) => { error!(?e, sql=%sql); api_error(500, "数据库错误") }
+        Err(e) => {
+            error!(?e, sql=%sql);
+            api_error(500, "数据库错误")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:527:
-async fn app_list(State(state): State<AppState>, headers: HeaderMap, Query(q): Query<AppListQuery>) -> Json<ApiResponse<Value>> {
-    let user_id = match optional_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let Some(mode) = visibility_mode(q.r#type, user_id) else { return ApiResponse::ok(json!([])); };
+async fn app_list(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<AppListQuery>,
+) -> Json<ApiResponse<Value>> {
+    let user_id = match optional_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
+    let Some(mode) = visibility_mode(q.r#type, user_id) else {
+        return ApiResponse::ok(json!([]));
+    };
     let sql = format!("SELECT {} FROM mse_app a WHERE a.deleted=0 AND ($1::text IS NULL OR a.platform=$1) AND (($2=0 AND a.status=1 AND a.user_id=1) OR ($2=1 AND a.user_id IN (1,$3)) OR ($2=2 AND a.user_id=$3) OR ($2=3 AND a.user_id IN (1,$3) AND EXISTS (SELECT 1 FROM mse_user_favorite_app f WHERE f.app_id=a.id AND f.user_id=$3 AND f.deleted=0))) ORDER BY a.create_time DESC", APP_JSON);
-    match sqlx::query_scalar::<_, Value>(&sql).bind(q.platform).bind(mode).bind(user_id.unwrap_or(-1)).fetch_all(&state.db).await {
+    match sqlx::query_scalar::<_, Value>(&sql)
+        .bind(q.platform)
+        .bind(mode)
+        .bind(user_id.unwrap_or(-1))
+        .fetch_all(&state.db)
+        .await
+    {
         Ok(v) => ApiResponse::ok(json!(v)),
-        Err(e) => { error!(?e, sql=%sql); api_error(500, "数据库错误") }
+        Err(e) => {
+            error!(?e, sql=%sql);
+            api_error(500, "数据库错误")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:548:
     r#type: Option<i32>,
 }
 
-async fn app_page(State(state): State<AppState>, headers: HeaderMap, Query(q): Query<PageQuery>) -> Json<ApiResponse<Value>> {
+async fn app_page(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<PageQuery>,
+) -> Json<ApiResponse<Value>> {
     let page_no = q.page_no.unwrap_or(1).max(1);
     let page_size = q.page_size.unwrap_or(20).clamp(1, 100);
     let offset = (page_no - 1) * page_size;
Diff in /workspace/src/main.rs:555:
-    let user_id = match optional_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
+    let user_id = match optional_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
     let Some(mode) = visibility_mode(q.r#type, user_id) else {
         return ApiResponse::ok(json!({"list": [], "total": 0}));
     };
Diff in /workspace/src/main.rs:569:
           OR ($6=2 AND a.user_id=$7)
           OR ($6=3 AND a.user_id IN (1,$7) AND EXISTS (SELECT 1 FROM mse_user_favorite_app f WHERE f.app_id=a.id AND f.user_id=$7 AND f.deleted=0)))";
     let count_sql = format!("SELECT COUNT(*) FROM mse_app a WHERE {where_sql}");
-    let list_sql = format!("SELECT {} FROM mse_app a WHERE {where_sql} ORDER BY a.id ASC LIMIT $8 OFFSET $9", APP_JSON);
+    let list_sql = format!(
+        "SELECT {} FROM mse_app a WHERE {where_sql} ORDER BY a.id ASC LIMIT $8 OFFSET $9",
+        APP_JSON
+    );
     let uid = user_id.unwrap_or(-1);
     let total = match sqlx::query_scalar::<_, i64>(&count_sql)
-        .bind(&q.platform).bind(&q.name).bind(&q.description).bind(&q.app_category).bind(category_id).bind(mode).bind(uid)
-        .fetch_one(&state.db).await {
+        .bind(&q.platform)
+        .bind(&q.name)
+        .bind(&q.description)
+        .bind(&q.app_category)
+        .bind(category_id)
+        .bind(mode)
+        .bind(uid)
+        .fetch_one(&state.db)
+        .await
+    {
         Ok(v) => v,
-        Err(e) => { error!(?e, sql=%count_sql); return api_error(500, "数据库错误"); }
+        Err(e) => {
+            error!(?e, sql=%count_sql);
+            return api_error(500, "数据库错误");
+        }
     };
     match sqlx::query_scalar::<_, Value>(&list_sql)
-        .bind(q.platform).bind(q.name).bind(q.description).bind(q.app_category).bind(category_id).bind(mode).bind(uid).bind(page_size).bind(offset)
-        .fetch_all(&state.db).await {
+        .bind(q.platform)
+        .bind(q.name)
+        .bind(q.description)
+        .bind(q.app_category)
+        .bind(category_id)
+        .bind(mode)
+        .bind(uid)
+        .bind(page_size)
+        .bind(offset)
+        .fetch_all(&state.db)
+        .await
+    {
         Ok(list) => ApiResponse::ok(json!({"list": list, "total": total})),
-        Err(e) => { error!(?e, sql=%list_sql); api_error(500, "数据库错误") }
+        Err(e) => {
+            error!(?e, sql=%list_sql);
+            api_error(500, "数据库错误")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:588:
-#[derive(Deserialize)] struct AppDetailsQuery { #[serde(rename="appId")] app_id:i64 }
-async fn app_details(State(state): State<AppState>, Query(q): Query<AppDetailsQuery>) -> Json<ApiResponse<Value>> {
+#[derive(Deserialize)]
+struct AppDetailsQuery {
+    #[serde(rename = "appId")]
+    app_id: i64,
+}
+async fn app_details(
+    State(state): State<AppState>,
+    Query(q): Query<AppDetailsQuery>,
+) -> Json<ApiResponse<Value>> {
     let sql="SELECT jsonb_build_object('id',ai.id,'url',ai.url,'appName',ai.app_name,'platform',ai.platform,'externalAppId',ai.external_app_id,'externalVersionId',ai.external_version_id,'appId',ai.app_id,'versionId',ai.version_id,'appLogoUrl',COALESCE(NULLIF(ai.app_logo_url,''),a.app_logo_url),'appTagline',ai.app_tagline,'companyHqRegion',ai.company_hq_region,'companyStage',ai.company_stage,'previewScreens',COALESCE(ai.preview_screens,'[]'::jsonb),'allAppCategories',COALESCE(ai.all_app_categories,'[]'::jsonb),'previewVideoUrl',ai.preview_video_url,'createTime',ai.create_time,'appSections',COALESCE((SELECT jsonb_agg(jsonb_build_object('id',s.id,'name',s.name,'actions',s.actions,'orders',s.orders,'videoUrl',s.video_url,'parentAppSectionId',s.parent_app_section_id,'appInfoId',s.app_info_id,'parentId',s.parent_id,'appSectionId',s.app_section_id,'screens',COALESCE((SELECT jsonb_agg(jsonb_build_object('id',sc.id,'appSectionId',sc.app_section_id,'orders',sc.orders,'pageUrl',sc.page_url,'hotspotX',sc.hotspot_x,'hotspotY',sc.hotspot_y,'metadataWidth',sc.metadata_width,'metadataHeight',sc.metadata_height,'pageType',sc.page_type,'screenId',sc.screen_id,'screenUrl',sc.screen_url,'hotspotType',sc.hotspot_type,'hotspotWidth',sc.hotspot_width,'hotspotHeight',sc.hotspot_height,'screenElements',sc.screen_elements,'screenPatterns',sc.screen_patterns,'videoTimestamp',sc.video_timestamp,'createTime',sc.create_time) ORDER BY sc.orders) FROM mse_screen sc WHERE sc.app_section_id=s.id AND sc.deleted=0),'[]'::jsonb)) ORDER BY s.orders) FROM mse_app_section s WHERE s.app_info_id=ai.id AND s.deleted=0),'[]'::jsonb)) FROM mse_app_info ai LEFT JOIN mse_app a ON a.id=ai.app_id AND a.deleted=0 WHERE ai.app_id=$1 AND ai.deleted=0 ORDER BY ai.create_time DESC";
-    match sqlx::query_scalar::<_,Value>(sql).bind(q.app_id).fetch_all(&state.db).await { Ok(v)=>ApiResponse::ok(json!(v)), Err(e)=>{error!(?e);api_error(500,"数据库错误")} }
+    match sqlx::query_scalar::<_, Value>(sql)
+        .bind(q.app_id)
+        .fetch_all(&state.db)
+        .await
+    {
+        Ok(v) => ApiResponse::ok(json!(v)),
+        Err(e) => {
+            error!(?e);
+            api_error(500, "数据库错误")
+        }
+    }
 }
 
-async fn json_body_insert_app(state: &AppState, body: &Value, id: i64, user_id: i64, external_app_id: &str) -> Result<()> {
+async fn json_body_insert_app(
+    state: &AppState,
+    body: &Value,
+    id: i64,
+    user_id: i64,
+    external_app_id: &str,
+) -> Result<()> {
     sqlx::query("INSERT INTO mse_app (id,name,description,status,external_app_id,app_category,app_logo_url,app_tagline,app_category_id,company_hq_region,company_stage,platform,preview_screens,all_app_categories,preview_video_url,all_app_categories_ids,user_id,process,create_time,update_time,creator,updater,deleted) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,NOW(),NOW(),$19,$19,0)")
         .bind(id)
         .bind(body.get("name").and_then(Value::as_str).unwrap_or(""))
Diff in /workspace/src/main.rs:616:
     Ok(())
 }
 
-async fn app_create(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    let user_id = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let name = body.get("name").and_then(Value::as_str).unwrap_or("").trim();
-    let platform = body.get("platform").and_then(Value::as_str).unwrap_or("").trim();
-    if name.is_empty() || platform.is_empty() { return api_error(400, "应用名称和平台不能为空"); }
+async fn app_create(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    let user_id = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
+    let name = body
+        .get("name")
+        .and_then(Value::as_str)
+        .unwrap_or("")
+        .trim();
+    let platform = body
+        .get("platform")
+        .and_then(Value::as_str)
+        .unwrap_or("")
+        .trim();
+    if name.is_empty() || platform.is_empty() {
+        return api_error(400, "应用名称和平台不能为空");
+    }
     let duplicate = sqlx::query_scalar::<_, bool>("SELECT EXISTS(SELECT 1 FROM mse_app WHERE name=$1 AND platform=$2 AND user_id=$3 AND deleted=0)")
         .bind(name).bind(platform).bind(user_id).fetch_one(&state.db).await;
     match duplicate {
Diff in /workspace/src/main.rs:627:
         Ok(true) => return api_error(400, "应用已存在"),
         Ok(false) => {}
-        Err(e) => { error!(?e); return api_error(500, "数据库错误"); }
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
     }
     let id = new_id();
     let external_app_id = Uuid::new_v4().to_string();
Diff in /workspace/src/main.rs:633:
     match json_body_insert_app(&state, &body, id, user_id, &external_app_id).await {
         Ok(_) => ApiResponse::ok(json!(id)),
-        Err(e) => { error!(?e); api_error(500, "创建应用失败") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "创建应用失败")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:639:
-async fn app_update(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    if let Err(e) = authenticated_user_id(&state, &headers).await { return e; }
-    let Some(id) = body.get("id").and_then(Value::as_i64) else { return api_error(400, "缺少 id"); };
-    let mut tx = match state.db.begin().await { Ok(v) => v, Err(e) => { error!(?e); return api_error(500, "数据库错误"); } };
+async fn app_update(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    }
+    let Some(id) = body.get("id").and_then(Value::as_i64) else {
+        return api_error(400, "缺少 id");
+    };
+    let mut tx = match state.db.begin().await {
+        Ok(v) => v,
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
+    };
     let result = sqlx::query("UPDATE mse_app SET name=COALESCE($2,name),description=COALESCE($3,description),status=COALESCE($4,status),app_category=COALESCE($5,app_category),company_hq_region=COALESCE($6,company_hq_region),company_stage=COALESCE($7,company_stage),platform=COALESCE($8,platform),app_logo_url=COALESCE($9,app_logo_url),app_tagline=COALESCE($10,app_tagline),preview_screens=COALESCE($11,preview_screens),all_app_categories=COALESCE($12,all_app_categories),preview_video_url=COALESCE($13,preview_video_url),app_category_id=COALESCE($14,app_category_id),external_app_id=COALESCE($15,external_app_id),process=COALESCE($16,process),all_app_categories_ids=COALESCE($17,all_app_categories_ids),update_time=NOW() WHERE id=$1 AND deleted=0")
         .bind(id)
         .bind(body.get("name").and_then(Value::as_str))
Diff in /workspace/src/main.rs:662:
     let updated = match result {
         Ok(v) if v.rows_affected() > 0 => v,
         Ok(_) => return api_error(404, "应用不存在"),
-        Err(e) => { error!(?e); return api_error(500, "更新应用失败"); }
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "更新应用失败");
+        }
     };
     let _ = updated;
-    if body.get("name").is_some() || body.get("platform").is_some() || body.get("appLogoUrl").is_some() || body.get("appTagline").is_some() || body.get("allAppCategories").is_some() {
+    if body.get("name").is_some()
+        || body.get("platform").is_some()
+        || body.get("appLogoUrl").is_some()
+        || body.get("appTagline").is_some()
+        || body.get("allAppCategories").is_some()
+    {
         if let Err(e) = sqlx::query("UPDATE mse_app_info SET app_name=COALESCE($2,app_name),platform=COALESCE($3,platform),app_logo_url=COALESCE($4,app_logo_url),app_tagline=COALESCE($5,app_tagline),all_app_categories=COALESCE($6,all_app_categories),update_time=NOW() WHERE app_id=$1 AND deleted=0")
             .bind(id).bind(body.get("name").and_then(Value::as_str)).bind(body.get("platform").and_then(Value::as_str)).bind(body.get("appLogoUrl").and_then(Value::as_str)).bind(body.get("appTagline").and_then(Value::as_str)).bind(body.get("allAppCategories").cloned()).execute(&mut *tx).await {
             error!(?e); return api_error(500, "同步应用版本失败");
Diff in /workspace/src/main.rs:672:
         }
     }
-    if let Err(e) = tx.commit().await { error!(?e); return api_error(500, "更新应用失败"); }
+    if let Err(e) = tx.commit().await {
+        error!(?e);
+        return api_error(500, "更新应用失败");
+    }
     ApiResponse::ok(json!(true))
 }
 
Diff in /workspace/src/main.rs:678:
-#[derive(Deserialize)] struct IdQuery { id: i64 }
-async fn app_delete(State(state): State<AppState>, headers: HeaderMap, Query(q): Query<IdQuery>) -> Json<ApiResponse<Value>> {
-    if let Err(e) = authenticated_user_id(&state, &headers).await { return e; }
-    match sqlx::query("UPDATE mse_app SET deleted=1,update_time=NOW() WHERE id=$1 AND deleted=0").bind(q.id).execute(&state.db).await {
+#[derive(Deserialize)]
+struct IdQuery {
+    id: i64,
+}
+async fn app_delete(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<IdQuery>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    }
+    match sqlx::query("UPDATE mse_app SET deleted=1,update_time=NOW() WHERE id=$1 AND deleted=0")
+        .bind(q.id)
+        .execute(&state.db)
+        .await
+    {
         Ok(v) if v.rows_affected() > 0 => ApiResponse::ok(json!(true)),
         Ok(_) => api_error(404, "应用不存在"),
-        Err(e) => { error!(?e); api_error(500, "删除失败") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "删除失败")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:688:
-async fn categories(State(state):State<AppState>)->Json<ApiResponse<Value>>{let sql="SELECT jsonb_build_object('id',id,'parentId',parent_id,'platformType',platform_type,'slug',slug,'displayName',display_name,'definition',definition,'orderNum',order_num,'hidden',hidden,'synonyms',COALESCE(synonyms,'[]'::jsonb),'contentCountsJson',COALESCE(content_counts_json,'{}'::jsonb),'exampleScreensJson',COALESCE(example_screens_json,'[]'::jsonb),'route',route,'createTime',create_time) FROM mse_app_category WHERE deleted=0 ORDER BY order_num,id";match sqlx::query_scalar::<_,Value>(sql).fetch_all(&state.db).await{Ok(v)=>ApiResponse::ok(json!(v)),Err(e)=>{error!(?e);api_error(500,"数据库错误")}}}
+async fn categories(State(state): State<AppState>) -> Json<ApiResponse<Value>> {
+    let sql="SELECT jsonb_build_object('id',id,'parentId',parent_id,'platformType',platform_type,'slug',slug,'displayName',display_name,'definition',definition,'orderNum',order_num,'hidden',hidden,'synonyms',COALESCE(synonyms,'[]'::jsonb),'contentCountsJson',COALESCE(content_counts_json,'{}'::jsonb),'exampleScreensJson',COALESCE(example_screens_json,'[]'::jsonb),'route',route,'createTime',create_time) FROM mse_app_category WHERE deleted=0 ORDER BY order_num,id";
+    match sqlx::query_scalar::<_, Value>(sql)
+        .fetch_all(&state.db)
+        .await
+    {
+        Ok(v) => ApiResponse::ok(json!(v)),
+        Err(e) => {
+            error!(?e);
+            api_error(500, "数据库错误")
+        }
+    }
+}
 
 async fn insert_or_get_section_node(
     tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
Diff in /workspace/src/main.rs:694:
     parent_id: Option<i64>,
     user_id: i64,
 ) -> Result<i64> {
-    let name = body.get("name").and_then(Value::as_str).unwrap_or("").trim();
-    if name.is_empty() { anyhow::bail!("section name is required"); }
-    if let Some(id) = sqlx::query_scalar::<_, i64>("SELECT id FROM mse_app_section WHERE app_info_id=$1 AND name=$2 AND deleted=0 LIMIT 1")
-        .bind(app_info_id).bind(name).fetch_optional(&mut **tx).await? {
+    let name = body
+        .get("name")
+        .and_then(Value::as_str)
+        .unwrap_or("")
+        .trim();
+    if name.is_empty() {
+        anyhow::bail!("section name is required");
+    }
+    if let Some(id) = sqlx::query_scalar::<_, i64>(
+        "SELECT id FROM mse_app_section WHERE app_info_id=$1 AND name=$2 AND deleted=0 LIMIT 1",
+    )
+    .bind(app_info_id)
+    .bind(name)
+    .fetch_optional(&mut **tx)
+    .await?
+    {
         return Ok(id);
     }
     let id = new_id();
Diff in /workspace/src/main.rs:713:
     Ok(id)
 }
 
-async fn section_create(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    let user_id = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let Some(app_info_id) = body.get("appInfoId").and_then(Value::as_i64) else { return api_error(400, "缺少 appInfoId"); };
-    let exists = match sqlx::query_scalar::<_, bool>("SELECT EXISTS(SELECT 1 FROM mse_app_info WHERE id=$1 AND deleted=0)").bind(app_info_id).fetch_one(&state.db).await {
+async fn section_create(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    let user_id = match authenticated_user_id(&state, &headers).await {
         Ok(v) => v,
-        Err(e) => { error!(?e); return api_error(500, "数据库错误"); }
+        Err(e) => return e,
     };
-    if !exists { return api_error(404, "应用版本不存在"); }
-    let mut tx = match state.db.begin().await { Ok(v) => v, Err(e) => { error!(?e); return api_error(500, "数据库错误"); } };
-    let root_parent = body.get("parentId").and_then(Value::as_i64);
-    let root_id = match insert_or_get_section_node(&mut tx, &body, app_info_id, root_parent, user_id).await {
+    let Some(app_info_id) = body.get("appInfoId").and_then(Value::as_i64) else {
+        return api_error(400, "缺少 appInfoId");
+    };
+    let exists = match sqlx::query_scalar::<_, bool>(
+        "SELECT EXISTS(SELECT 1 FROM mse_app_info WHERE id=$1 AND deleted=0)",
+    )
+    .bind(app_info_id)
+    .fetch_one(&state.db)
+    .await
+    {
         Ok(v) => v,
-        Err(e) => { error!(?e); return api_error(500, "创建流程失败"); }
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
     };
-    let mut stack: Vec<(Value, i64)> = body.get("appSections").and_then(Value::as_array)
-        .map(|items| items.iter().cloned().map(|child| (child, root_id)).collect()).unwrap_or_default();
+    if !exists {
+        return api_error(404, "应用版本不存在");
+    }
+    let mut tx = match state.db.begin().await {
+        Ok(v) => v,
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
+    };
+    let root_parent = body.get("parentId").and_then(Value::as_i64);
+    let root_id =
+        match insert_or_get_section_node(&mut tx, &body, app_info_id, root_parent, user_id).await {
+            Ok(v) => v,
+            Err(e) => {
+                error!(?e);
+                return api_error(500, "创建流程失败");
+            }
+        };
+    let mut stack: Vec<(Value, i64)> = body
+        .get("appSections")
+        .and_then(Value::as_array)
+        .map(|items| {
+            items
+                .iter()
+                .cloned()
+                .map(|child| (child, root_id))
+                .collect()
+        })
+        .unwrap_or_default();
     while let Some((child, parent_id)) = stack.pop() {
-        let child_id = match insert_or_get_section_node(&mut tx, &child, app_info_id, Some(parent_id), user_id).await {
+        let child_id = match insert_or_get_section_node(
+            &mut tx,
+            &child,
+            app_info_id,
+            Some(parent_id),
+            user_id,
+        )
+        .await
+        {
             Ok(v) => v,
-            Err(e) => { error!(?e); return api_error(500, "创建子流程失败"); }
+            Err(e) => {
+                error!(?e);
+                return api_error(500, "创建子流程失败");
+            }
         };
         if let Some(children) = child.get("appSections").and_then(Value::as_array) {
             stack.extend(children.iter().cloned().map(|node| (node, child_id)));
Diff in /workspace/src/main.rs:739:
         }
     }
-    if let Err(e) = tx.commit().await { error!(?e); return api_error(500, "创建流程失败"); }
+    if let Err(e) = tx.commit().await {
+        error!(?e);
+        return api_error(500, "创建流程失败");
+    }
     ApiResponse::ok(json!({"id": root_id}))
 }
-async fn section_update(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    if let Err(e) = authenticated_user_id(&state, &headers).await { return e; }
-    let Some(id) = body.get("id").and_then(Value::as_i64) else { return api_error(400, "缺少 id"); };
+async fn section_update(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    }
+    let Some(id) = body.get("id").and_then(Value::as_i64) else {
+        return api_error(400, "缺少 id");
+    };
     let r = sqlx::query("UPDATE mse_app_section SET name=COALESCE($2,name),actions=COALESCE($3,actions),orders=COALESCE($4,orders),video_url=COALESCE($5,video_url),parent_app_section_id=COALESCE($6,parent_app_section_id),app_info_id=COALESCE($7,app_info_id),parent_id=COALESCE($8,parent_id),app_section_id=COALESCE($9,app_section_id),update_time=NOW() WHERE id=$1 AND deleted=0")
         .bind(id)
         .bind(body.get("name").and_then(Value::as_str))
Diff in /workspace/src/main.rs:758:
     match r {
         Ok(v) if v.rows_affected() > 0 => ApiResponse::ok(json!(true)),
         Ok(_) => api_error(404, "流程不存在"),
-        Err(e) => { error!(?e); api_error(500, "更新流程失败") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "更新流程失败")
+        }
     }
 }
-async fn section_delete(State(state):State<AppState>,headers:HeaderMap,Query(q):Query<IdQuery>)->Json<ApiResponse<Value>>{if let Err(e)=authenticated_user_id(&state,&headers).await{return e};match sqlx::query("UPDATE mse_app_section SET deleted=1,update_time=NOW() WHERE id=$1").bind(q.id).execute(&state.db).await{Ok(_)=>ApiResponse::ok(json!(true)),Err(e)=>{error!(?e);api_error(500,"删除失败")}}}
+async fn section_delete(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<IdQuery>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    };
+    match sqlx::query("UPDATE mse_app_section SET deleted=1,update_time=NOW() WHERE id=$1")
+        .bind(q.id)
+        .execute(&state.db)
+        .await
+    {
+        Ok(_) => ApiResponse::ok(json!(true)),
+        Err(e) => {
+            error!(?e);
+            api_error(500, "删除失败")
+        }
+    }
+}
 
 #[derive(Deserialize, Default)]
 #[serde(rename_all = "camelCase")]
Diff in /workspace/src/main.rs:768:
-struct FavoritePageQuery { page_no: Option<i64>, page_size: Option<i64> }
+struct FavoritePageQuery {
+    page_no: Option<i64>,
+    page_size: Option<i64>,
+}
 
-async fn favorite_sections(State(state): State<AppState>, headers: HeaderMap, Query(q): Query<FavoritePageQuery>) -> Json<ApiResponse<Value>> {
-    let uid = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
+async fn favorite_sections(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<FavoritePageQuery>,
+) -> Json<ApiResponse<Value>> {
+    let uid = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
     let pn = q.page_no.unwrap_or(1).max(1);
     let ps = q.page_size.unwrap_or(20).clamp(1, 100);
     let count_sql = "SELECT COUNT(*) FROM mse_user_favorite_app_section f JOIN mse_app_section s ON s.id=f.app_section_id AND s.deleted=0 WHERE f.user_id=$1 AND f.deleted=0";
Diff in /workspace/src/main.rs:775:
-    let total = match sqlx::query_scalar::<_, i64>(count_sql).bind(uid).fetch_one(&state.db).await {
+    let total = match sqlx::query_scalar::<_, i64>(count_sql)
+        .bind(uid)
+        .fetch_one(&state.db)
+        .await
+    {
         Ok(v) => v,
-        Err(e) => { error!(?e); return api_error(500, "数据库错误"); }
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
     };
     let sql = "SELECT jsonb_build_object(
         'id',ai.id,'url',ai.url,'appName',ai.app_name,'platform',ai.platform,'externalAppId',ai.external_app_id,
Diff in /workspace/src/main.rs:798:
         JOIN mse_app_info ai ON ai.id=s.app_info_id AND ai.deleted=0
         WHERE f.user_id=$1 AND f.deleted=0
         ORDER BY f.create_time DESC LIMIT $2 OFFSET $3";
-    match sqlx::query_scalar::<_, Value>(sql).bind(uid).bind(ps).bind((pn - 1) * ps).fetch_all(&state.db).await {
+    match sqlx::query_scalar::<_, Value>(sql)
+        .bind(uid)
+        .bind(ps)
+        .bind((pn - 1) * ps)
+        .fetch_all(&state.db)
+        .await
+    {
         Ok(v) => ApiResponse::ok(json!({"list": v, "total": total})),
-        Err(e) => { error!(?e); api_error(500, "数据库错误") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "数据库错误")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:807:
-async fn favorite_app(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    let uid = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let Some(app_id) = body.get("appId").and_then(Value::as_i64) else { return api_error(400, "缺少 appId"); };
+async fn favorite_app(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    let uid = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
+    let Some(app_id) = body.get("appId").and_then(Value::as_i64) else {
+        return api_error(400, "缺少 appId");
+    };
     let ty = body.get("type").and_then(Value::as_i64).unwrap_or(1);
     if ty == 0 {
         return match sqlx::query("UPDATE mse_user_favorite_app SET deleted=1,update_time=NOW() WHERE user_id=$1 AND app_id=$2 AND deleted=0")
Diff in /workspace/src/main.rs:834:
     }
 }
 
-async fn favorite_section(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    let uid = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let Some(sid) = body.get("appSectionId").and_then(Value::as_i64) else { return api_error(400, "缺少 appSectionId"); };
+async fn favorite_section(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    let uid = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
+    let Some(sid) = body.get("appSectionId").and_then(Value::as_i64) else {
+        return api_error(400, "缺少 appSectionId");
+    };
     let ty = body.get("type").and_then(Value::as_i64).unwrap_or(1);
     if ty == 0 {
         return match sqlx::query("UPDATE mse_user_favorite_app_section SET deleted=1,update_time=NOW() WHERE user_id=$1 AND app_section_id=$2 AND deleted=0")
Diff in /workspace/src/main.rs:878:
     screen_pattern_id: Option<String>,
 }
 
-async fn screen_page(State(state): State<AppState>, Query(q): Query<ScreenPageQuery>) -> Json<ApiResponse<Value>> {
+async fn screen_page(
+    State(state): State<AppState>,
+    Query(q): Query<ScreenPageQuery>,
+) -> Json<ApiResponse<Value>> {
     let pn = q.page_no.unwrap_or(1).max(1);
     let ps = q.page_size.unwrap_or(20).clamp(1, 100);
     let category_id = q.category_id.as_deref().and_then(|v| v.parse::<i64>().ok());
Diff in /workspace/src/main.rs:885:
     let category_name = if let Some(id) = category_id {
-        match sqlx::query_scalar::<_, String>("SELECT display_name FROM mse_app_category WHERE id=$1 AND deleted=0")
-            .bind(id).fetch_optional(&state.db).await {
+        match sqlx::query_scalar::<_, String>(
+            "SELECT display_name FROM mse_app_category WHERE id=$1 AND deleted=0",
+        )
+        .bind(id)
+        .fetch_optional(&state.db)
+        .await
+        {
             Ok(v) => v,
-            Err(e) => { error!(?e); return api_error(500, "读取分类失败"); }
+            Err(e) => {
+                error!(?e);
+                return api_error(500, "读取分类失败");
+            }
         }
-    } else { None };
-    if category_id.is_some() && category_name.is_none() { return ApiResponse::ok(json!({"list": [], "total": 0})); }
+    } else {
+        None
+    };
+    if category_id.is_some() && category_name.is_none() {
+        return ApiResponse::ok(json!({"list": [], "total": 0}));
+    }
 
-    let app_category_id = q.app_category_id.as_deref().and_then(|v| v.parse::<i64>().ok());
-    let screen_element_id = q.screen_element_id.as_deref().and_then(|v| v.parse::<i64>().ok());
-    let screen_pattern_id = q.screen_pattern_id.as_deref().and_then(|v| v.parse::<i64>().ok());
+    let app_category_id = q
+        .app_category_id
+        .as_deref()
+        .and_then(|v| v.parse::<i64>().ok());
+    let screen_element_id = q
+        .screen_element_id
+        .as_deref()
+        .and_then(|v| v.parse::<i64>().ok());
+    let screen_pattern_id = q
+        .screen_pattern_id
+        .as_deref()
+        .and_then(|v| v.parse::<i64>().ok());
     let screen_element_name = if let Some(id) = screen_element_id {
-        match sqlx::query_scalar::<_, String>("SELECT display_name FROM mse_app_category WHERE id=$1 AND deleted=0")
-            .bind(id).fetch_optional(&state.db).await {
+        match sqlx::query_scalar::<_, String>(
+            "SELECT display_name FROM mse_app_category WHERE id=$1 AND deleted=0",
+        )
+        .bind(id)
+        .fetch_optional(&state.db)
+        .await
+        {
             Ok(v) => v,
-            Err(e) => { error!(?e); return api_error(500, "读取 UI 元素失败"); }
+            Err(e) => {
+                error!(?e);
+                return api_error(500, "读取 UI 元素失败");
+            }
         }
-    } else { None };
+    } else {
+        None
+    };
     let screen_pattern_name = if let Some(id) = screen_pattern_id {
-        match sqlx::query_scalar::<_, String>("SELECT display_name FROM mse_app_category WHERE id=$1 AND deleted=0")
-            .bind(id).fetch_optional(&state.db).await {
+        match sqlx::query_scalar::<_, String>(
+            "SELECT display_name FROM mse_app_category WHERE id=$1 AND deleted=0",
+        )
+        .bind(id)
+        .fetch_optional(&state.db)
+        .await
+        {
             Ok(v) => v,
-            Err(e) => { error!(?e); return api_error(500, "读取页面模式失败"); }
+            Err(e) => {
+                error!(?e);
+                return api_error(500, "读取页面模式失败");
+            }
         }
-    } else { None };
-    if screen_element_id.is_some() && screen_element_name.is_none() { return ApiResponse::ok(json!({"list": [], "total": 0})); }
-    if screen_pattern_id.is_some() && screen_pattern_name.is_none() { return ApiResponse::ok(json!({"list": [], "total": 0})); }
+    } else {
+        None
+    };
+    if screen_element_id.is_some() && screen_element_name.is_none() {
+        return ApiResponse::ok(json!({"list": [], "total": 0}));
+    }
+    if screen_pattern_id.is_some() && screen_pattern_name.is_none() {
+        return ApiResponse::ok(json!({"list": [], "total": 0}));
+    }
 
     let filter_sql = "s.deleted=0
         AND ($1::text IS NULL OR a.platform=$1)
Diff in /workspace/src/main.rs:932:
         'appSectionRespVO',jsonb_build_object('id',sec.id,'name',sec.name,'actions',COALESCE(sec.actions,'[]'::jsonb),'orders',sec.orders),
         'appRespVO',jsonb_build_object('id',a.id,'name',a.name,'appName',a.name,'description',a.description,'status',a.status,'externalAppId',a.external_app_id,'appCategory',a.app_category,'appCategoryId',a.app_category_id,'companyHqRegion',a.company_hq_region,'companyStage',a.company_stage,'platform',a.platform,'previewScreens',COALESCE(a.preview_screens,'[]'::jsonb),'allAppCategories',COALESCE(a.all_app_categories,'[]'::jsonb),'previewVideoUrl',a.preview_video_url,'appLogoUrl',a.app_logo_url,'appTagline',a.app_tagline,'createTime',a.create_time)) {from_sql} WHERE {filter_sql} ORDER BY s.create_time DESC LIMIT $8 OFFSET $9");
     let total = match sqlx::query_scalar::<_, i64>(&count_sql)
-        .bind(&q.platform).bind(&q.name).bind(&category_name).bind(q.category_type).bind(app_category_id).bind(&screen_element_name).bind(&screen_pattern_name).fetch_one(&state.db).await {
+        .bind(&q.platform)
+        .bind(&q.name)
+        .bind(&category_name)
+        .bind(q.category_type)
+        .bind(app_category_id)
+        .bind(&screen_element_name)
+        .bind(&screen_pattern_name)
+        .fetch_one(&state.db)
+        .await
+    {
         Ok(v) => v,
-        Err(e) => { error!(?e, sql=%count_sql); return api_error(500, "数据库错误"); }
+        Err(e) => {
+            error!(?e, sql=%count_sql);
+            return api_error(500, "数据库错误");
+        }
     };
     match sqlx::query_scalar::<_, Value>(&list_sql)
-        .bind(q.platform).bind(q.name).bind(category_name).bind(q.category_type).bind(app_category_id).bind(screen_element_name).bind(screen_pattern_name).bind(ps).bind((pn-1)*ps).fetch_all(&state.db).await {
+        .bind(q.platform)
+        .bind(q.name)
+        .bind(category_name)
+        .bind(q.category_type)
+        .bind(app_category_id)
+        .bind(screen_element_name)
+        .bind(screen_pattern_name)
+        .bind(ps)
+        .bind((pn - 1) * ps)
+        .fetch_all(&state.db)
+        .await
+    {
         Ok(v) => ApiResponse::ok(json!({"list": v, "total": total})),
-        Err(e) => { error!(?e, sql=%list_sql); api_error(500, "数据库错误") }
+        Err(e) => {
+            error!(?e, sql=%list_sql);
+            api_error(500, "数据库错误")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:946:
-async fn screen_create(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    let user_id = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
-    let Some(section_id) = body.get("appSectionId").and_then(Value::as_i64) else { return api_error(400, "缺少 appSectionId"); };
+async fn screen_create(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    let user_id = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
+    let Some(section_id) = body.get("appSectionId").and_then(Value::as_i64) else {
+        return api_error(400, "缺少 appSectionId");
+    };
     let orders = body.get("orders").and_then(Value::as_i64).unwrap_or(0) as i32;
-    let exists = match sqlx::query_scalar::<_, bool>("SELECT EXISTS(SELECT 1 FROM mse_app_section WHERE id=$1 AND deleted=0)").bind(section_id).fetch_one(&state.db).await {
-        Ok(v) => v, Err(e) => { error!(?e); return api_error(500, "数据库错误"); }
+    let exists = match sqlx::query_scalar::<_, bool>(
+        "SELECT EXISTS(SELECT 1 FROM mse_app_section WHERE id=$1 AND deleted=0)",
+    )
+    .bind(section_id)
+    .fetch_one(&state.db)
+    .await
+    {
+        Ok(v) => v,
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
     };
-    if !exists { return api_error(404, "流程不存在"); }
-    let mut tx = match state.db.begin().await { Ok(v) => v, Err(e) => { error!(?e); return api_error(500, "数据库错误"); } };
+    if !exists {
+        return api_error(404, "流程不存在");
+    }
+    let mut tx = match state.db.begin().await {
+        Ok(v) => v,
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
+    };
     if let Err(e) = sqlx::query("UPDATE mse_screen SET orders=orders+1,update_time=NOW() WHERE app_section_id=$1 AND orders >= $2 AND deleted=0")
         .bind(section_id).bind(orders).execute(&mut *tx).await {
         error!(?e); return api_error(500, "调整截图顺序失败");
Diff in /workspace/src/main.rs:963:
         .bind(body.get("pageUrl").and_then(Value::as_str)).bind(&screen_id).bind(body.get("screenUrl").and_then(Value::as_str)).bind(body.get("pageType").and_then(Value::as_str))
         .bind(body.get("screenElements").cloned()).bind(body.get("screenPatterns").cloned()).bind(body.get("videoTimestamp").and_then(Value::as_str)).bind(user_id.to_string())
         .execute(&mut *tx).await;
-    if let Err(e) = r { error!(?e); return api_error(500, "创建截图失败"); }
-    if let Err(e) = tx.commit().await { error!(?e); return api_error(500, "创建截图失败"); }
-    ApiResponse::ok(json!({"id": id, "appSectionId": section_id, "orders": orders, "screenId": screen_id}))
+    if let Err(e) = r {
+        error!(?e);
+        return api_error(500, "创建截图失败");
+    }
+    if let Err(e) = tx.commit().await {
+        error!(?e);
+        return api_error(500, "创建截图失败");
+    }
+    ApiResponse::ok(
+        json!({"id": id, "appSectionId": section_id, "orders": orders, "screenId": screen_id}),
+    )
 }
 
-async fn screen_update(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<Value>) -> Json<ApiResponse<Value>> {
-    if let Err(e) = authenticated_user_id(&state, &headers).await { return e; }
-    let Some(id) = body.get("id").and_then(Value::as_i64) else { return api_error(400, "更新截图需要 id"); };
+async fn screen_update(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    }
+    let Some(id) = body.get("id").and_then(Value::as_i64) else {
+        return api_error(400, "更新截图需要 id");
+    };
     let r = sqlx::query("UPDATE mse_screen SET app_section_id=COALESCE($2,app_section_id),orders=COALESCE($3,orders),page_url=COALESCE($4,page_url),screen_url=COALESCE($5,screen_url),page_type=COALESCE($6,page_type),screen_elements=COALESCE($7,screen_elements),screen_patterns=COALESCE($8,screen_patterns),video_timestamp=COALESCE($9,video_timestamp),update_time=NOW() WHERE id=$1 AND deleted=0")
         .bind(id).bind(body.get("appSectionId").and_then(Value::as_i64)).bind(body.get("orders").and_then(Value::as_i64).map(|v| v as i32))
         .bind(body.get("pageUrl").and_then(Value::as_str)).bind(body.get("screenUrl").and_then(Value::as_str)).bind(body.get("pageType").and_then(Value::as_str))
Diff in /workspace/src/main.rs:979:
     match r {
         Ok(v) if v.rows_affected() > 0 => ApiResponse::ok(body),
         Ok(_) => api_error(404, "截图不存在"),
-        Err(e) => { error!(?e); api_error(500, "更新截图失败") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "更新截图失败")
+        }
     }
 }
-async fn screen_delete(State(state):State<AppState>,headers:HeaderMap,Query(q):Query<IdQuery>)->Json<ApiResponse<Value>>{if let Err(e)=authenticated_user_id(&state,&headers).await{return e};match sqlx::query("UPDATE mse_screen SET deleted=1,update_time=NOW() WHERE id=$1").bind(q.id).execute(&state.db).await{Ok(_)=>ApiResponse::ok(json!(true)),Err(e)=>{error!(?e);api_error(500,"删除失败")}}}
+async fn screen_delete(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<IdQuery>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    };
+    match sqlx::query("UPDATE mse_screen SET deleted=1,update_time=NOW() WHERE id=$1")
+        .bind(q.id)
+        .execute(&state.db)
+        .await
+    {
+        Ok(_) => ApiResponse::ok(json!(true)),
+        Err(e) => {
+            error!(?e);
+            api_error(500, "删除失败")
+        }
+    }
+}
 
-async fn app_info_create(State(state):State<AppState>,headers:HeaderMap,Json(body):Json<Value>)->Json<ApiResponse<Value>>{if let Err(e)=authenticated_user_id(&state,&headers).await{return e};let app_id=body.get("appId").and_then(Value::as_i64).unwrap_or(0);let app=match sqlx::query("SELECT * FROM mse_app WHERE id=$1 AND deleted=0").bind(app_id).fetch_optional(&state.db).await{Ok(Some(v))=>v,Ok(None)=>return api_error(404,"应用不存在"),Err(e)=>{error!(?e);return api_error(500,"数据库错误")}};let id=new_id();let external_version_id=Uuid::new_v4().to_string();let r=sqlx::query("INSERT INTO mse_app_info(id,app_name,platform,external_app_id,external_version_id,app_id,app_logo_url,app_tagline,company_hq_region,company_stage,preview_screens,all_app_categories,preview_video_url,create_time,update_time,creator,updater,deleted) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,NOW(),NOW(),'','','0')").bind(id).bind(app.try_get::<String,_>("name").unwrap_or_default()).bind(app.try_get::<String,_>("platform").unwrap_or_default()).bind(app.try_get::<String,_>("external_app_id").unwrap_or_default()).bind(external_version_id).bind(app_id).bind(app.try_get::<Option<String>,_>("app_logo_url").ok().flatten()).bind(app.try_get::<Option<String>,_>("app_tagline").ok().flatten()).bind(app.try_get::<Option<String>,_>("company_hq_region").ok().flatten()).bind(app.try_get::<Option<String>,_>("company_stage").ok().flatten()).bind(app.try_get::<Value,_>("preview_screens").unwrap_or_else(|_|json!([]))).bind(app.try_get::<Value,_>("all_app_categories").unwrap_or_else(|_|json!([]))).bind(app.try_get::<Option<String>,_>("preview_video_url").ok().flatten()).execute(&state.db).await;match r{Ok(_)=>ApiResponse::ok(json!(id)),Err(e)=>{error!(?e);api_error(500,"创建应用版本失败")}}}
+async fn app_info_create(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Json(body): Json<Value>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    };
+    let app_id = body.get("appId").and_then(Value::as_i64).unwrap_or(0);
+    let app = match sqlx::query("SELECT * FROM mse_app WHERE id=$1 AND deleted=0")
+        .bind(app_id)
+        .fetch_optional(&state.db)
+        .await
+    {
+        Ok(Some(v)) => v,
+        Ok(None) => return api_error(404, "应用不存在"),
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
+    };
+    let id = new_id();
+    let external_version_id = Uuid::new_v4().to_string();
+    let r=sqlx::query("INSERT INTO mse_app_info(id,app_name,platform,external_app_id,external_version_id,app_id,app_logo_url,app_tagline,company_hq_region,company_stage,preview_screens,all_app_categories,preview_video_url,create_time,update_time,creator,updater,deleted) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,NOW(),NOW(),'','','0')").bind(id).bind(app.try_get::<String,_>("name").unwrap_or_default()).bind(app.try_get::<String,_>("platform").unwrap_or_default()).bind(app.try_get::<String,_>("external_app_id").unwrap_or_default()).bind(external_version_id).bind(app_id).bind(app.try_get::<Option<String>,_>("app_logo_url").ok().flatten()).bind(app.try_get::<Option<String>,_>("app_tagline").ok().flatten()).bind(app.try_get::<Option<String>,_>("company_hq_region").ok().flatten()).bind(app.try_get::<Option<String>,_>("company_stage").ok().flatten()).bind(app.try_get::<Value,_>("preview_screens").unwrap_or_else(|_|json!([]))).bind(app.try_get::<Value,_>("all_app_categories").unwrap_or_else(|_|json!([]))).bind(app.try_get::<Option<String>,_>("preview_video_url").ok().flatten()).execute(&state.db).await;
+    match r {
+        Ok(_) => ApiResponse::ok(json!(id)),
+        Err(e) => {
+            error!(?e);
+            api_error(500, "创建应用版本失败")
+        }
+    }
+}
 
-fn sha256_hex(data: &[u8]) -> String { hex::encode(Sha256::digest(data)) }
+fn sha256_hex(data: &[u8]) -> String {
+    hex::encode(Sha256::digest(data))
+}
 
 fn normalize_relative_path(value: &str) -> Result<String> {
     let normalized = value.replace('\\', "/");
Diff in /workspace/src/main.rs:993:
     let normalized = normalized.trim_start_matches('/');
-    if normalized.is_empty() { anyhow::bail!("file path must not be empty"); }
+    if normalized.is_empty() {
+        anyhow::bail!("file path must not be empty");
+    }
 
     let mut parts = Vec::new();
     for component in Path::new(normalized).components() {
Diff in /workspace/src/main.rs:998:
         match component {
             Component::Normal(part) => {
                 let part = part.to_str().context("file path must be valid UTF-8")?;
-                if part.is_empty() || part.chars().any(|ch| ch.is_control() || matches!(ch, '?' | '#')) {
+                if part.is_empty()
+                    || part
+                        .chars()
+                        .any(|ch| ch.is_control() || matches!(ch, '?' | '#'))
+                {
                     anyhow::bail!("file path contains unsupported characters");
                 }
                 parts.push(part);
Diff in /workspace/src/main.rs:1005:
             }
             Component::CurDir => {}
-            Component::ParentDir | Component::RootDir | Component::Prefix(_) => anyhow::bail!("file path escapes storage root"),
+            Component::ParentDir | Component::RootDir | Component::Prefix(_) => {
+                anyhow::bail!("file path escapes storage root")
+            }
         }
     }
-    if parts.is_empty() { anyhow::bail!("file path must not be empty"); }
+    if parts.is_empty() {
+        anyhow::bail!("file path must not be empty");
+    }
     Ok(parts.join("/"))
 }
 
Diff in /workspace/src/main.rs:1018:
 
 fn upload_object_key(data: &[u8], filename: &str, requested_path: Option<&str>) -> Result<String> {
     let normalized_filename = filename.replace('\\', "/");
-    let safe_filename = normalized_filename.rsplit('/').next().filter(|value| !value.is_empty()).unwrap_or("upload.bin");
-    if let Some(path) = requested_path.map(str::trim).filter(|value| !value.is_empty()) {
+    let safe_filename = normalized_filename
+        .rsplit('/')
+        .next()
+        .filter(|value| !value.is_empty())
+        .unwrap_or("upload.bin");
+    if let Some(path) = requested_path
+        .map(str::trim)
+        .filter(|value| !value.is_empty())
+    {
         return normalize_relative_path(&format!("{path}{safe_filename}"));
     }
 
Diff in /workspace/src/main.rs:1027:
     let extension = Path::new(safe_filename)
         .extension()
         .and_then(|value| value.to_str())
-        .filter(|value| !value.is_empty() && value.len() <= 16 && value.chars().all(|ch| ch.is_ascii_alphanumeric()))
+        .filter(|value| {
+            !value.is_empty()
+                && value.len() <= 16
+                && value.chars().all(|ch| ch.is_ascii_alphanumeric())
+        })
         .map(str::to_ascii_lowercase);
-    Ok(extension.map(|ext| format!("{digest}.{ext}")).unwrap_or(digest))
+    Ok(extension
+        .map(|ext| format!("{digest}.{ext}"))
+        .unwrap_or(digest))
 }
 
 async fn write_local_file(root: &Path, object_key: &str, data: &[u8]) -> Result<()> {
Diff in /workspace/src/main.rs:1036:
     let path = storage_path(root, object_key)?;
-    if let Some(parent) = path.parent() { tokio::fs::create_dir_all(parent).await.with_context(|| format!("create upload directory {}", parent.display()))?; }
-    tokio::fs::write(&path, data).await.with_context(|| format!("write upload {}", path.display()))?;
+    if let Some(parent) = path.parent() {
+        tokio::fs::create_dir_all(parent)
+            .await
+            .with_context(|| format!("create upload directory {}", parent.display()))?;
+    }
+    tokio::fs::write(&path, data)
+        .await
+        .with_context(|| format!("write upload {}", path.display()))?;
     Ok(())
 }
 
Diff in /workspace/src/main.rs:1049:
 }
 
 fn public_file_url(base_url: &str, object_key: &str) -> String {
-    format!("{}/{}", base_url.trim_end_matches('/'), object_key.trim_start_matches('/'))
+    format!(
+        "{}/{}",
+        base_url.trim_end_matches('/'),
+        object_key.trim_start_matches('/')
+    )
 }
 
-async fn upload_url(State(state): State<AppState>, headers: HeaderMap, mut multipart: Multipart) -> Json<ApiResponse<Value>> {
-    let user_id = match authenticated_user_id(&state, &headers).await { Ok(v) => v, Err(e) => return e };
+async fn upload_url(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    mut multipart: Multipart,
+) -> Json<ApiResponse<Value>> {
+    let user_id = match authenticated_user_id(&state, &headers).await {
+        Ok(v) => v,
+        Err(e) => return e,
+    };
     let mut bytes = None;
     let mut filename = "upload.bin".to_string();
     let mut content_type = "application/octet-stream".to_string();
Diff in /workspace/src/main.rs:1061:
     while let Ok(Some(field)) = multipart.next_field().await {
         match field.name() {
             Some("file") => {
-                if let Some(n) = field.file_name() { filename = n.to_string(); }
-                if let Some(ct) = field.content_type() { content_type = ct.to_string(); }
-                match field.bytes().await { Ok(v) => bytes = Some(v), Err(_) => return api_error(400, "读取上传文件失败") }
+                if let Some(n) = field.file_name() {
+                    filename = n.to_string();
+                }
+                if let Some(ct) = field.content_type() {
+                    content_type = ct.to_string();
+                }
+                match field.bytes().await {
+                    Ok(v) => bytes = Some(v),
+                    Err(_) => return api_error(400, "读取上传文件失败"),
+                }
             }
             Some("path") => {
                 requested_path = field.text().await.ok();
Diff in /workspace/src/main.rs:1071:
             _ => {}
         }
     }
-    let Some(data) = bytes else { return api_error(400, "缺少 file"); };
+    let Some(data) = bytes else {
+        return api_error(400, "缺少 file");
+    };
     let object_key = match upload_object_key(&data, &filename, requested_path.as_deref()) {
         Ok(value) => value,
-        Err(e) => { warn!(?e, "rejected upload path"); return api_error(400, "文件路径不合法"); }
+        Err(e) => {
+            warn!(?e, "rejected upload path");
+            return api_error(400, "文件路径不合法");
+        }
     };
     if let Err(e) = write_local_file(&state.file_storage_dir, &object_key, &data).await {
-        error!(?e, "local file upload failed"); return api_error(500, "文件上传失败");
+        error!(?e, "local file upload failed");
+        return api_error(500, "文件上传失败");
     }
     let url = public_file_url(&state.file_public_base_url, &object_key);
     let id = new_id();
Diff in /workspace/src/main.rs:1091:
     ApiResponse::ok(json!({"url": url, "id": id}))
 }
 
-async fn delete_file(State(state): State<AppState>, headers: HeaderMap, Query(q): Query<IdQuery>) -> Json<ApiResponse<Value>> {
-    if let Err(e) = authenticated_user_id(&state, &headers).await { return e; }
-    let row = match sqlx::query("SELECT config_id,path FROM infra_file WHERE id=$1 AND deleted=0").bind(q.id).fetch_optional(&state.db).await {
+async fn delete_file(
+    State(state): State<AppState>,
+    headers: HeaderMap,
+    Query(q): Query<IdQuery>,
+) -> Json<ApiResponse<Value>> {
+    if let Err(e) = authenticated_user_id(&state, &headers).await {
+        return e;
+    }
+    let row = match sqlx::query("SELECT config_id,path FROM infra_file WHERE id=$1 AND deleted=0")
+        .bind(q.id)
+        .fetch_optional(&state.db)
+        .await
+    {
         Ok(Some(v)) => v,
         Ok(None) => return api_error(404, "文件不存在"),
-        Err(e) => { error!(?e); return api_error(500, "数据库错误"); }
+        Err(e) => {
+            error!(?e);
+            return api_error(500, "数据库错误");
+        }
     };
-    let config_id: i64 = match row.try_get("config_id") { Ok(v) => v, Err(_) => return api_error(500, "文件配置异常") };
-    let path: String = match row.try_get("path") { Ok(v) => v, Err(_) => return api_error(500, "文件路径异常") };
-    if config_id != state.file_config_id { return api_error(409, "该文件不属于当前本地存储，无法物理删除"); }
-    if let Err(e) = remove_local_file(&state.file_storage_dir, &path).await { error!(?e); return api_error(500, "删除存储文件失败"); }
-    match sqlx::query("UPDATE infra_file SET deleted=1,update_time=NOW() WHERE id=$1").bind(q.id).execute(&state.db).await {
+    let config_id: i64 = match row.try_get("config_id") {
+        Ok(v) => v,
+        Err(_) => return api_error(500, "文件配置异常"),
+    };
+    let path: String = match row.try_get("path") {
+        Ok(v) => v,
+        Err(_) => return api_error(500, "文件路径异常"),
+    };
+    if config_id != state.file_config_id {
+        return api_error(409, "该文件不属于当前本地存储，无法物理删除");
+    }
+    if let Err(e) = remove_local_file(&state.file_storage_dir, &path).await {
+        error!(?e);
+        return api_error(500, "删除存储文件失败");
+    }
+    match sqlx::query("UPDATE infra_file SET deleted=1,update_time=NOW() WHERE id=$1")
+        .bind(q.id)
+        .execute(&state.db)
+        .await
+    {
         Ok(_) => ApiResponse::ok(json!(true)),
-        Err(e) => { error!(?e); api_error(500, "删除文件记录失败") }
+        Err(e) => {
+            error!(?e);
+            api_error(500, "删除文件记录失败")
+        }
     }
 }
 
Diff in /workspace/src/main.rs:1111:
 #[tokio::main]
 async fn main() -> Result<()> {
     dotenvy::dotenv().ok();
-    tracing_subscriber::fmt().with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_|"design_screenshot_api=info,tower_http=info".into())).init();
-    let database_url=env::var("DATABASE_URL").context("DATABASE_URL is required")?;
-    let admin_username=env::var("ADMIN_USERNAME").unwrap_or_else(|_|"admin".into()).trim().to_string();
-    let admin_password=env::var("ADMIN_PASSWORD").context("ADMIN_PASSWORD is required")?;
-    if admin_username.is_empty() { anyhow::bail!("ADMIN_USERNAME must not be empty"); }
-    if admin_password.len() < 8 { anyhow::bail!("ADMIN_PASSWORD must contain at least 8 characters"); }
-    if admin_password.len() < 20 { warn!("ADMIN_PASSWORD is shorter than the recommended 20 characters"); }
-    let port:u16=env::var("PORT").ok().and_then(|v|v.parse().ok()).unwrap_or(7778);
-    let bind_ip:IpAddr=env::var("BIND_ADDR").unwrap_or_else(|_|"127.0.0.1".into()).parse().context("BIND_ADDR must be an IP address")?;
-    let file_storage_dir=PathBuf::from(env::var("FILE_STORAGE_DIR").unwrap_or_else(|_|"/root/DesignScreenshot/uploads".into()));
-    let file_public_base_url=env::var("FILE_PUBLIC_BASE_URL").unwrap_or_else(|_|"https://api.beenglish.eu.org/DesignScreenshot/files".into()).trim_end_matches('/').to_string();
-    if !(file_public_base_url.starts_with("https://") || file_public_base_url.starts_with("http://")) { anyhow::bail!("FILE_PUBLIC_BASE_URL must start with http:// or https://"); }
-    let file_config_id=env::var("FILE_CONFIG_ID").ok().map(|v|v.parse::<i64>().context("FILE_CONFIG_ID must be an integer")).transpose()?.unwrap_or(7);
-    tokio::fs::create_dir_all(&file_storage_dir).await.with_context(|| format!("create FILE_STORAGE_DIR {}", file_storage_dir.display()))?;
-    let auth_cookie_secure=env::var("AUTH_COOKIE_SECURE").map(|v| !matches!(v.trim().to_ascii_lowercase().as_str(), "0" | "false" | "no" | "off")).unwrap_or(true);
-    let access_token_ttl_seconds=env::var("ACCESS_TOKEN_TTL_SECONDS").ok().map(|v| v.parse::<i64>().context("ACCESS_TOKEN_TTL_SECONDS must be an integer")).transpose()?.unwrap_or(900);
-    if !(60..=3600).contains(&access_token_ttl_seconds) { anyhow::bail!("ACCESS_TOKEN_TTL_SECONDS must be between 60 and 3600 seconds"); }
-    let cors_origins=load_cors_origins()?;
-    let ip_allowlist=load_ip_allowlist()?;
-    let db=PgPoolOptions::new().max_connections(env::var("DB_MAX_CONNECTIONS").ok().and_then(|v|v.parse().ok()).unwrap_or(10)).min_connections(1).connect(&database_url).await.context("connect postgres")?;
-    sqlx::query("SELECT 1").execute(&db).await.context("database health check")?;
-    let admin_exists=sqlx::query_scalar::<_,bool>("SELECT EXISTS(SELECT 1 FROM system_users WHERE id=1 AND deleted=0)").fetch_one(&db).await.context("validate admin user")?;
-    if !admin_exists { anyhow::bail!("system_users id=1 is required for the configured admin account"); }
+    tracing_subscriber::fmt()
+        .with_env_filter(
+            tracing_subscriber::EnvFilter::try_from_default_env()
+                .unwrap_or_else(|_| "design_screenshot_api=info,tower_http=info".into()),
+        )
+        .init();
+    let database_url = env::var("DATABASE_URL").context("DATABASE_URL is required")?;
+    let admin_username = env::var("ADMIN_USERNAME")
+        .unwrap_or_else(|_| "admin".into())
+        .trim()
+        .to_string();
+    let admin_password = env::var("ADMIN_PASSWORD").context("ADMIN_PASSWORD is required")?;
+    if admin_username.is_empty() {
+        anyhow::bail!("ADMIN_USERNAME must not be empty");
+    }
+    if admin_password.len() < 8 {
+        anyhow::bail!("ADMIN_PASSWORD must contain at least 8 characters");
+    }
+    if admin_password.len() < 20 {
+        warn!("ADMIN_PASSWORD is shorter than the recommended 20 characters");
+    }
+    let port: u16 = env::var("PORT")
+        .ok()
+        .and_then(|v| v.parse().ok())
+        .unwrap_or(7778);
+    let bind_ip: IpAddr = env::var("BIND_ADDR")
+        .unwrap_or_else(|_| "127.0.0.1".into())
+        .parse()
+        .context("BIND_ADDR must be an IP address")?;
+    let file_storage_dir = PathBuf::from(
+        env::var("FILE_STORAGE_DIR").unwrap_or_else(|_| "/root/DesignScreenshot/uploads".into()),
+    );
+    let file_public_base_url = env::var("FILE_PUBLIC_BASE_URL")
+        .unwrap_or_else(|_| "https://api.beenglish.eu.org/DesignScreenshot/files".into())
+        .trim_end_matches('/')
+        .to_string();
+    if !(file_public_base_url.starts_with("https://")
+        || file_public_base_url.starts_with("http://"))
+    {
+        anyhow::bail!("FILE_PUBLIC_BASE_URL must start with http:// or https://");
+    }
+    let file_config_id = env::var("FILE_CONFIG_ID")
+        .ok()
+        .map(|v| {
+            v.parse::<i64>()
+                .context("FILE_CONFIG_ID must be an integer")
+        })
+        .transpose()?
+        .unwrap_or(7);
+    tokio::fs::create_dir_all(&file_storage_dir)
+        .await
+        .with_context(|| format!("create FILE_STORAGE_DIR {}", file_storage_dir.display()))?;
+    let auth_cookie_secure = env::var("AUTH_COOKIE_SECURE")
+        .map(|v| {
+            !matches!(
+                v.trim().to_ascii_lowercase().as_str(),
+                "0" | "false" | "no" | "off"
+            )
+        })
+        .unwrap_or(true);
+    let access_token_ttl_seconds = env::var("ACCESS_TOKEN_TTL_SECONDS")
+        .ok()
+        .map(|v| {
+            v.parse::<i64>()
+                .context("ACCESS_TOKEN_TTL_SECONDS must be an integer")
+        })
+        .transpose()?
+        .unwrap_or(900);
+    if !(60..=3600).contains(&access_token_ttl_seconds) {
+        anyhow::bail!("ACCESS_TOKEN_TTL_SECONDS must be between 60 and 3600 seconds");
+    }
+    let cors_origins = load_cors_origins()?;
+    let ip_allowlist = load_ip_allowlist()?;
+    let db = PgPoolOptions::new()
+        .max_connections(
+            env::var("DB_MAX_CONNECTIONS")
+                .ok()
+                .and_then(|v| v.parse().ok())
+                .unwrap_or(10),
+        )
+        .min_connections(1)
+        .connect(&database_url)
+        .await
+        .context("connect postgres")?;
+    sqlx::query("SELECT 1")
+        .execute(&db)
+        .await
+        .context("database health check")?;
+    let admin_exists = sqlx::query_scalar::<_, bool>(
+        "SELECT EXISTS(SELECT 1 FROM system_users WHERE id=1 AND deleted=0)",
+    )
+    .fetch_one(&db)
+    .await
+    .context("validate admin user")?;
+    if !admin_exists {
+        anyhow::bail!("system_users id=1 is required for the configured admin account");
+    }
     let revoked_access = sqlx::query("DELETE FROM system_oauth2_access_token WHERE user_id=1 AND deleted=0 AND COALESCE(creator,'') <> 'rust-cookie-v1'")
         .execute(&db).await.context("revoke legacy admin access tokens")?.rows_affected();
     let revoked_refresh = sqlx::query("DELETE FROM system_oauth2_refresh_token WHERE user_id=1 AND deleted=0 AND COALESCE(creator,'') <> 'rust-cookie-v1'")
Diff in /workspace/src/main.rs:1140:
         .execute(&db).await.context("revoke legacy admin refresh tokens")?.rows_affected();
     if revoked_access > 0 || revoked_refresh > 0 {
-        info!(revoked_access, revoked_refresh, "revoked legacy admin sessions during cookie-auth migration");
+        info!(
+            revoked_access,
+            revoked_refresh, "revoked legacy admin sessions during cookie-auth migration"
+        );
     }
-    let state=AppState{
+    let state = AppState {
         db,
         ip_allowlist,
         file_storage_dir,
Diff in /workspace/src/main.rs:1151:
         admin_password,
         auth_cookie_secure,
         access_token_ttl_seconds,
-        login_attempts:Arc::new(Mutex::new(HashMap::new())),
+        login_attempts: Arc::new(Mutex::new(HashMap::new())),
     };
-    let api=Router::new()
-        .route("/system/auth/login",post(login)).route("/system/auth/refresh-token",post(refresh_token)).route("/system/auth/logout",post(logout)).route("/system/auth/get-user-info",get(user_info))
-        .route("/admin/app/get-all",get(app_get_all)).route("/admin/app/list",get(app_list)).route("/admin/app/page",get(app_page)).route("/admin/app/get-app-details",get(app_details)).route("/admin/app/create",post(app_create)).route("/admin/app/update",put(app_update)).route("/admin/app/delete",delete(app_delete))
-        .route("/admin/app-category/list-all",get(categories)).route("/admin/app-info/create",post(app_info_create))
-        .route("/admin/app-section/create",post(section_create)).route("/admin/app-section/update",put(section_update)).route("/admin/app-section/delete",delete(section_delete)).route("/admin/app-section/get-favorite",get(favorite_sections))
-        .route("/admin/favorite/create-app-favorite",post(favorite_app)).route("/admin/favorite-section/create",post(favorite_section))
-        .route("/admin/screen/page",get(screen_page)).route("/admin/screen/create",post(screen_create)).route("/admin/screen/update",put(screen_update)).route("/admin/screen/delete",delete(screen_delete))
-        .route("/infra/file/upload-url",post(upload_url)).route("/infra/file/delete",delete(delete_file));
-    let cors=CorsLayer::new()
+    let api = Router::new()
+        .route("/system/auth/login", post(login))
+        .route("/system/auth/refresh-token", post(refresh_token))
+        .route("/system/auth/logout", post(logout))
+        .route("/system/auth/get-user-info", get(user_info))
+        .route("/admin/app/get-all", get(app_get_all))
+        .route("/admin/app/list", get(app_list))
+        .route("/admin/app/page", get(app_page))
+        .route("/admin/app/get-app-details", get(app_details))
+        .route("/admin/app/create", post(app_create))
+        .route("/admin/app/update", put(app_update))
+        .route("/admin/app/delete", delete(app_delete))
+        .route("/admin/app-category/list-all", get(categories))
+        .route("/admin/app-info/create", post(app_info_create))
+        .route("/admin/app-section/create", post(section_create))
+        .route("/admin/app-section/update", put(section_update))
+        .route("/admin/app-section/delete", delete(section_delete))
+        .route("/admin/app-section/get-favorite", get(favorite_sections))
+        .route("/admin/favorite/create-app-favorite", post(favorite_app))
+        .route("/admin/favorite-section/create", post(favorite_section))
+        .route("/admin/screen/page", get(screen_page))
+        .route("/admin/screen/create", post(screen_create))
+        .route("/admin/screen/update", put(screen_update))
+        .route("/admin/screen/delete", delete(screen_delete))
+        .route("/infra/file/upload-url", post(upload_url))
+        .route("/infra/file/delete", delete(delete_file));
+    let cors = CorsLayer::new()
         .allow_origin(AllowOrigin::list(cors_origins))
         .allow_credentials(true)
-        .allow_methods([Method::GET,Method::POST,Method::PUT,Method::DELETE,Method::OPTIONS])
-        .allow_headers([header::AUTHORIZATION,header::CONTENT_TYPE,header::ACCEPT]);
-    let allowlist_enabled=!state.ip_allowlist.is_empty();
-    let allowlist_entries=state.ip_allowlist.len();
-    let configured_admin_username=state.admin_username.clone();
-    let file_storage_dir=state.file_storage_dir.clone();
-    let app=Router::new().route("/health",get(health)).route("/doc.html",get(health)).nest_service("/files",ServeDir::new(file_storage_dir)).nest("/admin-api",api).layer(DefaultBodyLimit::max(100*1024*1024)).layer(cors).layer(TraceLayer::new_for_http()).layer(middleware::from_fn_with_state(state.clone(),enforce_ip_allowlist)).with_state(state);
-    let addr=SocketAddr::new(bind_ip,port); info!(%addr,admin_username=%configured_admin_username,allowlist_enabled,allowlist_entries,"DesignScreenshot Rust API starting"); let listener=tokio::net::TcpListener::bind(addr).await?; axum::serve(listener,app.into_make_service_with_connect_info::<SocketAddr>()).with_graceful_shutdown(async{let _=tokio::signal::ctrl_c().await;}).await?; Ok(())
+        .allow_methods([
+            Method::GET,
+            Method::POST,
+            Method::PUT,
+            Method::DELETE,
+            Method::OPTIONS,
+        ])
+        .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE, header::ACCEPT]);
+    let allowlist_enabled = !state.ip_allowlist.is_empty();
+    let allowlist_entries = state.ip_allowlist.len();
+    let configured_admin_username = state.admin_username.clone();
+    let file_storage_dir = state.file_storage_dir.clone();
+    let app = Router::new()
+        .route("/health", get(health))
+        .route("/doc.html", get(health))
+        .nest_service("/files", ServeDir::new(file_storage_dir))
+        .nest("/admin-api", api)
+        .layer(DefaultBodyLimit::max(100 * 1024 * 1024))
+        .layer(cors)
+        .layer(TraceLayer::new_for_http())
+        .layer(middleware::from_fn_with_state(
+            state.clone(),
+            enforce_ip_allowlist,
+        ))
+        .with_state(state);
+    let addr = SocketAddr::new(bind_ip, port);
+    info!(%addr,admin_username=%configured_admin_username,allowlist_enabled,allowlist_entries,"DesignScreenshot Rust API starting");
+    let listener = tokio::net::TcpListener::bind(addr).await?;
+    axum::serve(
+        listener,
+        app.into_make_service_with_connect_info::<SocketAddr>(),
+    )
+    .with_graceful_shutdown(async {
+        let _ = tokio::signal::ctrl_c().await;
+    })
+    .await?;
+    Ok(())
 }
 
