use chrono::{DateTime, Utc}; use sqlx::{FromRow, SqlitePool}; use uuid::Uuid; use crate::{ error::{ApiError, ApiResult}, python_mirrors::{ model::{DefaultPythonMirror, PythonMirrorResponse}, service::ValidatedMirror, }, }; #[derive(Debug, FromRow)] struct MirrorRow { id: String, name: String, index_url: String, trusted_host: Option, enabled: bool, is_default: bool, created_at: DateTime, updated_at: DateTime, } impl From for PythonMirrorResponse { fn from(row: MirrorRow) -> Self { Self { id: row.id, name: row.name, index_url: row.index_url, trusted_host: row.trusted_host, enabled: row.enabled, is_default: row.is_default, created_at: row.created_at, updated_at: row.updated_at, } } } pub async fn list(pool: &SqlitePool) -> ApiResult> { let rows = sqlx::query_as::<_, MirrorRow>( "SELECT id, name, index_url, trusted_host, enabled, is_default, created_at, updated_at FROM python_mirrors ORDER BY is_default DESC, enabled DESC, name COLLATE NOCASE ASC", ) .fetch_all(pool) .await?; Ok(rows.into_iter().map(Into::into).collect()) } pub async fn get(pool: &SqlitePool, id: &str) -> ApiResult { let row = sqlx::query_as::<_, MirrorRow>( "SELECT id, name, index_url, trusted_host, enabled, is_default, created_at, updated_at FROM python_mirrors WHERE id = ?", ) .bind(id) .fetch_optional(pool) .await? .ok_or_else(|| ApiError::mirror_not_found(format!("镜像 {id} 不存在")))?; Ok(row.into()) } pub async fn create(pool: &SqlitePool, input: ValidatedMirror) -> ApiResult { if index_url_exists(pool, &input.index_url, None).await? { return Err(ApiError::mirror_conflict("该索引地址已经存在")); } let id = Uuid::new_v4().simple().to_string(); let now = Utc::now(); sqlx::query( "INSERT INTO python_mirrors (id, name, index_url, trusted_host, enabled, is_default, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 0, ?, ?)", ) .bind(&id) .bind(&input.name) .bind(&input.index_url) .bind(input.trusted_host.as_deref()) .bind(input.enabled) .bind(now) .bind(now) .execute(pool) .await .map_err(map_mirror_error)?; get(pool, &id).await } pub async fn update( pool: &SqlitePool, id: &str, input: ValidatedMirror, ) -> ApiResult { if index_url_exists(pool, &input.index_url, Some(id)).await? { return Err(ApiError::mirror_conflict("该索引地址已经存在")); } let now = Utc::now(); let result = sqlx::query( "UPDATE python_mirrors SET name = ?, index_url = ?, trusted_host = ?, enabled = ?, is_default = CASE WHEN ? = 0 THEN 0 ELSE is_default END, updated_at = ? WHERE id = ?", ) .bind(&input.name) .bind(&input.index_url) .bind(input.trusted_host.as_deref()) .bind(input.enabled) .bind(input.enabled) .bind(now) .bind(id) .execute(pool) .await .map_err(map_mirror_error)?; if result.rows_affected() == 0 { return Err(ApiError::mirror_not_found(format!("镜像 {id} 不存在"))); } get(pool, id).await } pub async fn delete(pool: &SqlitePool, id: &str) -> ApiResult<()> { let result = sqlx::query("DELETE FROM python_mirrors WHERE id = ?") .bind(id) .execute(pool) .await?; if result.rows_affected() == 0 { return Err(ApiError::mirror_not_found(format!("镜像 {id} 不存在"))); } Ok(()) } /// 设为默认:同一事务里先清掉旧默认,再把目标设为默认并启用。 pub async fn set_default(pool: &SqlitePool, id: &str) -> ApiResult { let now = Utc::now(); let mut transaction = pool.begin().await?; let exists: Option<(i64,)> = sqlx::query_as("SELECT 1 FROM python_mirrors WHERE id = ?") .bind(id) .fetch_optional(&mut *transaction) .await?; if exists.is_none() { transaction.rollback().await?; return Err(ApiError::mirror_not_found(format!("镜像 {id} 不存在"))); } sqlx::query("UPDATE python_mirrors SET is_default = 0, updated_at = ? WHERE is_default = 1") .bind(now) .execute(&mut *transaction) .await?; sqlx::query( "UPDATE python_mirrors SET is_default = 1, enabled = 1, updated_at = ? WHERE id = ?", ) .bind(now) .bind(id) .execute(&mut *transaction) .await?; transaction.commit().await?; get(pool, id).await } /// 执行器读取:只返回「启用且是默认」的镜像。 pub async fn default_enabled(pool: &SqlitePool) -> ApiResult> { let row = sqlx::query_as::<_, (String, String, Option)>( "SELECT name, index_url, trusted_host FROM python_mirrors WHERE enabled = 1 AND is_default = 1 LIMIT 1", ) .fetch_optional(pool) .await?; Ok( row.map(|(name, index_url, trusted_host)| DefaultPythonMirror { name, index_url, trusted_host, }), ) } async fn index_url_exists( pool: &SqlitePool, index_url: &str, excluding_id: Option<&str>, ) -> ApiResult { let existing: Option<(String,)> = match excluding_id { Some(id) => { sqlx::query_as( "SELECT id FROM python_mirrors WHERE lower(index_url) = lower(?) AND id <> ?", ) .bind(index_url) .bind(id) .fetch_optional(pool) .await? } None => { sqlx::query_as("SELECT id FROM python_mirrors WHERE lower(index_url) = lower(?)") .bind(index_url) .fetch_optional(pool) .await? } }; Ok(existing.is_some()) } fn map_mirror_error(error: sqlx::Error) -> ApiError { if let sqlx::Error::Database(database_error) = &error && database_error.is_unique_violation() { return ApiError::mirror_conflict("该索引地址已经存在"); } ApiError::from(error) }