mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-29 15:26:05 +02:00
feat: add local api support
This commit is contained in:
@@ -0,0 +1,701 @@
|
||||
use crate::group_manager::GROUP_MANAGER;
|
||||
use crate::profile::manager::ProfileManager;
|
||||
use crate::proxy_manager::PROXY_MANAGER;
|
||||
use crate::tag_manager::TAG_MANAGER;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
routing::{delete, get, post, put},
|
||||
Router,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tauri::Emitter;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tower_http::cors::CorsLayer;
|
||||
|
||||
// API Types
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ApiProfile {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub browser: String,
|
||||
pub version: String,
|
||||
pub proxy_id: Option<String>,
|
||||
pub process_id: Option<u32>,
|
||||
pub last_launch: Option<u64>,
|
||||
pub release_type: String,
|
||||
pub camoufox_config: Option<serde_json::Value>,
|
||||
pub group_id: Option<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub is_running: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ApiProfilesResponse {
|
||||
pub profiles: Vec<ApiProfile>,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ApiProfileResponse {
|
||||
pub profile: ApiProfile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CreateProfileRequest {
|
||||
pub name: String,
|
||||
pub browser: String,
|
||||
pub version: Option<String>,
|
||||
pub proxy_id: Option<String>,
|
||||
pub release_type: Option<String>,
|
||||
pub camoufox_config: Option<serde_json::Value>,
|
||||
pub group_id: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UpdateProfileRequest {
|
||||
pub name: Option<String>,
|
||||
pub browser: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub proxy_id: Option<String>,
|
||||
pub release_type: Option<String>,
|
||||
pub camoufox_config: Option<serde_json::Value>,
|
||||
pub group_id: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ApiServerState {
|
||||
app_handle: tauri::AppHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ApiGroupResponse {
|
||||
id: String,
|
||||
name: String,
|
||||
profile_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateGroupRequest {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateGroupRequest {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ApiProxyResponse {
|
||||
id: String,
|
||||
name: String,
|
||||
proxy_settings: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateProxyRequest {
|
||||
name: String,
|
||||
proxy_settings: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateProxyRequest {
|
||||
name: Option<String>,
|
||||
proxy_settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToastPayload {
|
||||
pub message: String,
|
||||
pub variant: String,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ApiServer {
|
||||
port: Option<u16>,
|
||||
shutdown_tx: Option<mpsc::Sender<()>>,
|
||||
task_handle: Option<tokio::task::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ApiServer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
port: None,
|
||||
shutdown_tx: None,
|
||||
task_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_port(&self) -> Option<u16> {
|
||||
self.port
|
||||
}
|
||||
|
||||
async fn start(
|
||||
&mut self,
|
||||
app_handle: tauri::AppHandle,
|
||||
preferred_port: u16,
|
||||
) -> Result<u16, String> {
|
||||
// Stop existing server if running
|
||||
self.stop().await.ok();
|
||||
|
||||
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
|
||||
let state = ApiServerState {
|
||||
app_handle: app_handle.clone(),
|
||||
};
|
||||
|
||||
// Try preferred port first, then random port
|
||||
let listener = match TcpListener::bind(format!("127.0.0.1:{preferred_port}")).await {
|
||||
Ok(listener) => listener,
|
||||
Err(_) => {
|
||||
// Port conflict, try random port
|
||||
let random_port = rand::random::<u16>().saturating_add(10000);
|
||||
match TcpListener::bind(format!("127.0.0.1:{random_port}")).await {
|
||||
Ok(listener) => {
|
||||
let _ = app_handle.emit(
|
||||
"api-port-conflict",
|
||||
format!("API server using fallback port {random_port}"),
|
||||
);
|
||||
listener
|
||||
}
|
||||
Err(e) => return Err(format!("Failed to bind to any port: {e}")),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let actual_port = listener
|
||||
.local_addr()
|
||||
.map_err(|e| format!("Failed to get local address: {e}"))?
|
||||
.port();
|
||||
|
||||
// Create router with CORS
|
||||
let app = Router::new()
|
||||
.route("/profiles", get(get_profiles))
|
||||
.route("/profiles", post(create_profile))
|
||||
.route("/profiles/{id}", get(get_profile))
|
||||
.route("/profiles/{id}", put(update_profile))
|
||||
.route("/profiles/{id}", delete(delete_profile))
|
||||
.route("/groups", get(get_groups).post(create_group))
|
||||
.route(
|
||||
"/groups/{id}",
|
||||
get(get_group).put(update_group).delete(delete_group),
|
||||
)
|
||||
.route("/tags", get(get_tags))
|
||||
.route("/proxies", get(get_proxies).post(create_proxy))
|
||||
.route(
|
||||
"/proxies/{id}",
|
||||
get(get_proxy).put(update_proxy).delete(delete_proxy),
|
||||
)
|
||||
.layer(CorsLayer::permissive())
|
||||
.with_state(state);
|
||||
|
||||
// Start server task
|
||||
let task_handle = tokio::spawn(async move {
|
||||
let server = axum::serve(listener, app);
|
||||
tokio::select! {
|
||||
_ = server => {},
|
||||
_ = shutdown_rx.recv() => {},
|
||||
}
|
||||
});
|
||||
|
||||
self.port = Some(actual_port);
|
||||
self.shutdown_tx = Some(shutdown_tx);
|
||||
self.task_handle = Some(task_handle);
|
||||
|
||||
Ok(actual_port)
|
||||
}
|
||||
|
||||
async fn stop(&mut self) -> Result<(), String> {
|
||||
if let Some(shutdown_tx) = self.shutdown_tx.take() {
|
||||
let _ = shutdown_tx.send(()).await;
|
||||
}
|
||||
|
||||
if let Some(handle) = self.task_handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
self.port = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Global API server instance
|
||||
lazy_static! {
|
||||
pub static ref API_SERVER: Arc<Mutex<ApiServer>> = Arc::new(Mutex::new(ApiServer::new()));
|
||||
}
|
||||
|
||||
// Tauri commands
|
||||
#[tauri::command]
|
||||
pub async fn start_api_server_internal(
|
||||
port: u16,
|
||||
app_handle: &tauri::AppHandle,
|
||||
) -> Result<u16, String> {
|
||||
let mut server_guard = API_SERVER.lock().await;
|
||||
server_guard.start(app_handle.clone(), port).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn stop_api_server() -> Result<(), String> {
|
||||
let mut server_guard = API_SERVER.lock().await;
|
||||
server_guard.stop().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_api_server(
|
||||
port: Option<u16>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<u16, String> {
|
||||
let actual_port = port.unwrap_or(10108);
|
||||
start_api_server_internal(actual_port, &app_handle).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_api_server_status() -> Result<Option<u16>, String> {
|
||||
let server_guard = API_SERVER.lock().await;
|
||||
Ok(server_guard.get_port())
|
||||
}
|
||||
|
||||
// API Handlers - Profiles
|
||||
async fn get_profiles() -> Result<Json<ApiProfilesResponse>, StatusCode> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
match profile_manager.list_profiles() {
|
||||
Ok(profiles) => {
|
||||
let api_profiles: Vec<ApiProfile> = profiles
|
||||
.iter()
|
||||
.map(|profile| ApiProfile {
|
||||
id: profile.id.to_string(),
|
||||
name: profile.name.clone(),
|
||||
browser: profile.browser.clone(),
|
||||
version: profile.version.clone(),
|
||||
proxy_id: profile.proxy_id.clone(),
|
||||
process_id: profile.process_id,
|
||||
last_launch: profile.last_launch,
|
||||
release_type: profile.release_type.clone(),
|
||||
camoufox_config: profile
|
||||
.camoufox_config
|
||||
.as_ref()
|
||||
.and_then(|c| serde_json::to_value(c).ok()),
|
||||
group_id: profile.group_id.clone(),
|
||||
tags: profile.tags.clone(),
|
||||
is_running: false, // For now, set to false - can add running status later
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(ApiProfilesResponse {
|
||||
profiles: api_profiles,
|
||||
total: profiles.len(),
|
||||
}))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_profile(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<Json<ApiProfileResponse>, StatusCode> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
match profile_manager.list_profiles() {
|
||||
Ok(profiles) => {
|
||||
if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == id) {
|
||||
Ok(Json(ApiProfileResponse {
|
||||
profile: ApiProfile {
|
||||
id: profile.id.to_string(),
|
||||
name: profile.name.clone(),
|
||||
browser: profile.browser.clone(),
|
||||
version: profile.version.clone(),
|
||||
proxy_id: profile.proxy_id.clone(),
|
||||
process_id: profile.process_id,
|
||||
last_launch: profile.last_launch,
|
||||
release_type: profile.release_type.clone(),
|
||||
camoufox_config: profile
|
||||
.camoufox_config
|
||||
.as_ref()
|
||||
.and_then(|c| serde_json::to_value(c).ok()),
|
||||
group_id: profile.group_id.clone(),
|
||||
tags: profile.tags.clone(),
|
||||
is_running: false, // Simplified for now to avoid async complexity
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_profile(
|
||||
State(state): State<ApiServerState>,
|
||||
Json(request): Json<CreateProfileRequest>,
|
||||
) -> Result<Json<ApiProfileResponse>, StatusCode> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
|
||||
// Parse camoufox config if provided
|
||||
let camoufox_config = if let Some(config) = &request.camoufox_config {
|
||||
serde_json::from_value(config.clone()).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create profile using the async create_profile_with_group method
|
||||
match profile_manager
|
||||
.create_profile_with_group(
|
||||
&state.app_handle,
|
||||
&request.name,
|
||||
&request.browser,
|
||||
request.version.as_deref().unwrap_or("stable"),
|
||||
request.release_type.as_deref().unwrap_or("release"),
|
||||
request.proxy_id.clone(),
|
||||
camoufox_config,
|
||||
request.group_id.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(mut profile) => {
|
||||
// Apply tags if provided
|
||||
if let Some(tags) = &request.tags {
|
||||
if profile_manager
|
||||
.update_profile_tags(&profile.name, tags.clone())
|
||||
.is_err()
|
||||
{
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
profile.tags = tags.clone();
|
||||
}
|
||||
|
||||
// Update tag manager with new tags
|
||||
if let Ok(profiles) = profile_manager.list_profiles() {
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|manager| manager.rebuild_from_profiles(&profiles));
|
||||
}
|
||||
|
||||
Ok(Json(ApiProfileResponse {
|
||||
profile: ApiProfile {
|
||||
id: profile.id.to_string(),
|
||||
name: profile.name,
|
||||
browser: profile.browser,
|
||||
version: profile.version,
|
||||
proxy_id: profile.proxy_id,
|
||||
process_id: profile.process_id,
|
||||
last_launch: profile.last_launch,
|
||||
release_type: profile.release_type,
|
||||
camoufox_config: profile
|
||||
.camoufox_config
|
||||
.as_ref()
|
||||
.and_then(|c| serde_json::to_value(c).ok()),
|
||||
group_id: profile.group_id,
|
||||
tags: profile.tags,
|
||||
is_running: false,
|
||||
},
|
||||
}))
|
||||
}
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_profile(
|
||||
Path(id): Path<String>,
|
||||
State(state): State<ApiServerState>,
|
||||
Json(request): Json<UpdateProfileRequest>,
|
||||
) -> Result<Json<ApiProfileResponse>, StatusCode> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
|
||||
// Update profile fields
|
||||
if let Some(name) = request.name {
|
||||
if profile_manager.rename_profile(&id, &name).is_err() {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(version) = request.version {
|
||||
if profile_manager
|
||||
.update_profile_version(&id, &version)
|
||||
.is_err()
|
||||
{
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(proxy_id) = request.proxy_id {
|
||||
if profile_manager
|
||||
.update_profile_proxy(state.app_handle.clone(), &id, Some(proxy_id))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(camoufox_config) = request.camoufox_config {
|
||||
let config: Result<crate::camoufox::CamoufoxConfig, _> =
|
||||
serde_json::from_value(camoufox_config);
|
||||
match config {
|
||||
Ok(config) => {
|
||||
if profile_manager
|
||||
.update_camoufox_config(state.app_handle.clone(), &id, config)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(group_id) = request.group_id {
|
||||
if profile_manager
|
||||
.assign_profiles_to_group(vec![id.clone()], Some(group_id))
|
||||
.is_err()
|
||||
{
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tags) = request.tags {
|
||||
if profile_manager.update_profile_tags(&id, tags).is_err() {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Update tag manager with new tags from all profiles
|
||||
if let Ok(profiles) = profile_manager.list_profiles() {
|
||||
let _ = crate::tag_manager::TAG_MANAGER
|
||||
.lock()
|
||||
.map(|manager| manager.rebuild_from_profiles(&profiles));
|
||||
}
|
||||
}
|
||||
|
||||
// Return updated profile
|
||||
get_profile(Path(id), State(state)).await
|
||||
}
|
||||
|
||||
async fn delete_profile(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
match profile_manager.delete_profile(&id) {
|
||||
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
|
||||
// API Handlers - Groups
|
||||
async fn get_groups(
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<Json<Vec<ApiGroupResponse>>, StatusCode> {
|
||||
match GROUP_MANAGER.lock() {
|
||||
Ok(manager) => {
|
||||
match manager.get_all_groups() {
|
||||
Ok(groups) => {
|
||||
let api_groups = groups
|
||||
.into_iter()
|
||||
.map(|group| ApiGroupResponse {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
profile_count: 0, // Would need profile list to calculate this
|
||||
})
|
||||
.collect();
|
||||
Ok(Json(api_groups))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_group(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
||||
match GROUP_MANAGER.lock() {
|
||||
Ok(manager) => match manager.get_all_groups() {
|
||||
Ok(groups) => {
|
||||
if let Some(group) = groups.into_iter().find(|g| g.id == id) {
|
||||
Ok(Json(ApiGroupResponse {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
profile_count: 0,
|
||||
}))
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
},
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_group(
|
||||
State(_state): State<ApiServerState>,
|
||||
Json(request): Json<CreateGroupRequest>,
|
||||
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
||||
match GROUP_MANAGER.lock() {
|
||||
Ok(manager) => match manager.create_group(request.name) {
|
||||
Ok(group) => Ok(Json(ApiGroupResponse {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
profile_count: 0,
|
||||
})),
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
},
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_group(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
Json(request): Json<UpdateGroupRequest>,
|
||||
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
||||
match GROUP_MANAGER.lock() {
|
||||
Ok(manager) => match manager.update_group(id.clone(), request.name) {
|
||||
Ok(group) => Ok(Json(ApiGroupResponse {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
profile_count: 0,
|
||||
})),
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
},
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_group(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
match GROUP_MANAGER.lock() {
|
||||
Ok(manager) => match manager.delete_group(id.clone()) {
|
||||
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
},
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
// API Handlers - Tags
|
||||
async fn get_tags(State(_state): State<ApiServerState>) -> Result<Json<Vec<String>>, StatusCode> {
|
||||
match TAG_MANAGER.lock() {
|
||||
Ok(manager) => match manager.get_all_tags() {
|
||||
Ok(tags) => Ok(Json(tags)),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
},
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
// API Handlers - Proxies
|
||||
async fn get_proxies(
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<Json<Vec<ApiProxyResponse>>, StatusCode> {
|
||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||
Ok(Json(
|
||||
proxies
|
||||
.into_iter()
|
||||
.map(|p| ApiProxyResponse {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
proxy_settings: serde_json::to_value(p.proxy_settings).unwrap_or_default(),
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_proxy(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||
if let Some(proxy) = proxies.into_iter().find(|p| p.id == id) {
|
||||
Ok(Json(ApiProxyResponse {
|
||||
id: proxy.id,
|
||||
name: proxy.name,
|
||||
proxy_settings: serde_json::to_value(proxy.proxy_settings).unwrap_or_default(),
|
||||
}))
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_proxy(
|
||||
State(_state): State<ApiServerState>,
|
||||
Json(request): Json<CreateProxyRequest>,
|
||||
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
||||
// Convert JSON value to ProxySettings
|
||||
match serde_json::from_value(request.proxy_settings.clone()) {
|
||||
Ok(proxy_settings) => {
|
||||
match PROXY_MANAGER.create_stored_proxy(request.name.clone(), proxy_settings) {
|
||||
Ok(_) => {
|
||||
// Find the created proxy to return it
|
||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||
if let Some(proxy) = proxies.into_iter().find(|p| p.name == request.name) {
|
||||
Ok(Json(ApiProxyResponse {
|
||||
id: proxy.id,
|
||||
name: proxy.name,
|
||||
proxy_settings: request.proxy_settings,
|
||||
}))
|
||||
} else {
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_proxy(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
Json(request): Json<UpdateProxyRequest>,
|
||||
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||
if let Some(proxy) = proxies.into_iter().find(|p| p.id == id) {
|
||||
let new_name = request.name.unwrap_or(proxy.name.clone());
|
||||
let new_proxy_settings = if let Some(settings_json) = request.proxy_settings {
|
||||
match serde_json::from_value(settings_json) {
|
||||
Ok(settings) => settings,
|
||||
Err(_) => return Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
} else {
|
||||
proxy.proxy_settings.clone()
|
||||
};
|
||||
|
||||
match PROXY_MANAGER.update_stored_proxy(
|
||||
&id,
|
||||
Some(new_name.clone()),
|
||||
Some(new_proxy_settings.clone()),
|
||||
) {
|
||||
Ok(_) => Ok(Json(ApiProxyResponse {
|
||||
id,
|
||||
name: new_name,
|
||||
proxy_settings: serde_json::to_value(new_proxy_settings).unwrap_or_default(),
|
||||
})),
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_proxy(
|
||||
Path(id): Path<String>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
match PROXY_MANAGER.delete_stored_proxy(&id) {
|
||||
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user