fix: allow deleting desktop providers

This commit is contained in:
boos
2026-06-29 15:22:40 +08:00
parent 7dd099b2e9
commit 0515b90b6c
4 changed files with 122 additions and 17 deletions
@@ -5,9 +5,12 @@ use std::collections::HashSet;
use crate::crypto::{decrypt_key, encrypt_key};
use crate::entity::{builtin_model_deletions, models, provider_keys, providers};
use crate::error::{BoosApiError, Result};
use crate::repo::settings;
use crate::types::*;
use crate::utils::{gen_id, now_ts};
const DELETED_BUILTIN_PROVIDERS_SETTING: &str = "deleted_builtin_providers";
fn parse_provider_type(s: &str) -> ProviderType {
match s {
"openai" => ProviderType::OpenAI,
@@ -286,6 +289,47 @@ fn key_prefix(raw_key: &str) -> String {
}
}
async fn list_deleted_builtin_provider_ids(db: &DatabaseConnection) -> Result<HashSet<String>> {
let Some(raw) = settings::get_setting(db, DELETED_BUILTIN_PROVIDERS_SETTING).await? else {
return Ok(HashSet::new());
};
Ok(serde_json::from_str::<Vec<String>>(&raw)
.unwrap_or_default()
.into_iter()
.filter(|id| !id.trim().is_empty())
.collect())
}
async fn save_deleted_builtin_provider_ids(
db: &DatabaseConnection,
deleted_ids: &HashSet<String>,
) -> Result<()> {
let mut values: Vec<String> = deleted_ids.iter().cloned().collect();
values.sort();
let raw = serde_json::to_string(&values).unwrap_or_else(|_| "[]".to_string());
settings::set_setting(db, DELETED_BUILTIN_PROVIDERS_SETTING, &raw).await
}
pub async fn mark_builtin_provider_deleted(
db: &DatabaseConnection,
builtin_id: &str,
) -> Result<()> {
if !crate::db::get_builtin_providers()
.iter()
.any(|provider| provider.builtin_id == builtin_id)
{
return Err(BoosApiError::NotFound(format!(
"Built-in provider {}",
builtin_id
)));
}
let mut deleted_ids = list_deleted_builtin_provider_ids(db).await?;
deleted_ids.insert(builtin_id.to_string());
save_deleted_builtin_provider_ids(db, &deleted_ids).await
}
pub async fn import_provider_from_deep_link(
db: &DatabaseConnection,
master_key: &[u8; 32],
@@ -354,11 +398,20 @@ pub async fn import_provider_from_deep_link(
}
pub async fn delete_provider(db: &DatabaseConnection, id: &str) -> Result<()> {
let builtin_id = providers::Entity::find_by_id(id)
.one(db)
.await?
.ok_or_else(|| BoosApiError::NotFound(format!("Provider {}", id)))?
.builtin_id;
let result = providers::Entity::delete_by_id(id).exec(db).await?;
if result.rows_affected == 0 {
return Err(BoosApiError::NotFound(format!("Provider {}", id)));
}
if let Some(builtin_id) = builtin_id {
mark_builtin_provider_deleted(db, &builtin_id).await?;
}
Ok(())
}
@@ -833,6 +886,7 @@ async fn sync_missing_builtin_models(
pub async fn list_providers_merged(db: &DatabaseConnection) -> Result<Vec<ProviderConfig>> {
let mut db_providers = list_providers(db).await?;
let builtins = crate::db::get_builtin_providers();
let deleted_builtin_ids = list_deleted_builtin_provider_ids(db).await?;
if sync_missing_builtin_models(db, &builtins, &db_providers).await? {
db_providers = list_providers(db).await?;
}
@@ -840,6 +894,10 @@ pub async fn list_providers_merged(db: &DatabaseConnection) -> Result<Vec<Provid
let mut result = Vec::new();
for bp in &builtins {
if deleted_builtin_ids.contains(bp.builtin_id) {
continue;
}
if let Some(db_prov) = db_providers
.iter()
.find(|p| p.builtin_id.as_deref() == Some(bp.builtin_id))
@@ -1106,6 +1164,27 @@ mod tests {
);
}
#[tokio::test]
async fn deleted_builtin_provider_stays_hidden_from_merged_list() {
let h = create_test_pool().await.unwrap();
let db = &h.conn;
let providers = list_providers_merged(db).await.unwrap();
assert!(providers
.iter()
.any(|provider| provider.builtin_id.as_deref() == Some("boosapi")));
mark_builtin_provider_deleted(db, "boosapi").await.unwrap();
let providers = list_providers_merged(db).await.unwrap();
assert!(
!providers
.iter()
.any(|provider| provider.builtin_id.as_deref() == Some("boosapi")),
"deleted built-in provider should not reappear after refresh"
);
}
#[tokio::test]
async fn provider_deep_link_import_creates_provider_and_key() {
let h = create_test_pool().await.unwrap();