From 4683410a2c79686484d22ba58bbd1f3ca14c05c0 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:32:49 +0400 Subject: [PATCH] feat: add openapi spec generation --- src-tauri/Cargo.lock | 47 ++- src-tauri/Cargo.toml | 2 + src-tauri/src/api_server.rs | 598 ++++++++++++++++++++++++++++++------ src-tauri/src/browser.rs | 3 +- 4 files changed, 561 insertions(+), 89 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4e68ea3..d480415 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1341,6 +1341,8 @@ dependencies = [ "tower-http", "url", "urlencoding", + "utoipa", + "utoipa-axum", "uuid", "windows 0.62.2", "winreg", @@ -2281,7 +2283,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -3483,6 +3485,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -5973,6 +5981,43 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap 2.12.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-axum" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0" +dependencies = [ + "axum", + "paste", + "tower-layer", + "tower-service", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.110", +] + [[package]] name = "uuid" version = "1.18.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f74b67e..8ec8a04 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -64,6 +64,8 @@ axum = "0.8.7" tower = "0.5" tower-http = { version = "0.6", features = ["cors"] } rand = "0.9.2" +utoipa = { version = "5", features = ["axum_extras", "chrono"] } +utoipa-axum = "0.2" argon2 = "0.5" aes-gcm = "0.10" hyper = { version = "1.8", features = ["full"] } diff --git a/src-tauri/src/api_server.rs b/src-tauri/src/api_server.rs index 2d2b1b3..7572679 100644 --- a/src-tauri/src/api_server.rs +++ b/src-tauri/src/api_server.rs @@ -1,3 +1,4 @@ +use crate::browser::ProxySettings; use crate::camoufox_manager::CamoufoxConfig; use crate::group_manager::GROUP_MANAGER; use crate::profile::manager::ProfileManager; @@ -8,7 +9,7 @@ use axum::{ http::{HeaderMap, StatusCode}, middleware::{self, Next}, response::{Json, Response}, - routing::{delete, get, post, put}, + routing::get, Router, }; use lazy_static::lazy_static; @@ -18,9 +19,11 @@ use tauri::Emitter; use tokio::net::TcpListener; use tokio::sync::{mpsc, Mutex}; use tower_http::cors::CorsLayer; +use utoipa::{OpenApi, ToSchema}; +use utoipa_axum::{router::OpenApiRouter, routes}; // API Types -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] pub struct ApiProfile { pub id: String, pub name: String, @@ -30,42 +33,45 @@ pub struct ApiProfile { pub process_id: Option, pub last_launch: Option, pub release_type: String, + #[schema(value_type = Object)] pub camoufox_config: Option, pub group_id: Option, pub tags: Vec, pub is_running: bool, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct ApiProfilesResponse { pub profiles: Vec, pub total: usize, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct ApiProfileResponse { pub profile: ApiProfile, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct CreateProfileRequest { pub name: String, pub browser: String, pub version: String, pub proxy_id: Option, pub release_type: Option, + #[schema(value_type = Object)] pub camoufox_config: Option, pub group_id: Option, pub tags: Option>, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct UpdateProfileRequest { pub name: Option, pub browser: Option, pub version: Option, pub proxy_id: Option, pub release_type: Option, + #[schema(value_type = Object)] pub camoufox_config: Option, pub group_id: Option, pub tags: Option>, @@ -76,56 +82,59 @@ struct ApiServerState { app_handle: tauri::AppHandle, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] struct ApiGroupResponse { id: String, name: String, profile_count: usize, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct CreateGroupRequest { name: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct UpdateGroupRequest { name: String, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] struct ApiProxyResponse { id: String, name: String, - proxy_settings: serde_json::Value, + #[schema(value_type = Object)] + proxy_settings: ProxySettings, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct CreateProxyRequest { name: String, - proxy_settings: serde_json::Value, + #[schema(value_type = Object)] + proxy_settings: ProxySettings, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct UpdateProxyRequest { name: Option, - proxy_settings: Option, + #[schema(value_type = Object)] + proxy_settings: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct DownloadBrowserRequest { browser: String, version: String, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] struct DownloadBrowserResponse { browser: String, version: String, status: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ToastPayload { pub message: String, pub variant: String, @@ -133,24 +142,98 @@ pub struct ToastPayload { pub description: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] struct RunProfileResponse { profile_id: String, remote_debugging_port: u16, headless: bool, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct RunProfileRequest { url: Option, headless: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] struct OpenUrlRequest { url: String, } +#[derive(OpenApi)] +#[openapi( + paths( + get_profiles, + get_profile, + create_profile, + update_profile, + delete_profile, + run_profile, + open_url_in_profile, + kill_profile, + get_groups, + get_group, + create_group, + update_group, + delete_group, + get_tags, + get_proxies, + get_proxy, + create_proxy, + update_proxy, + delete_proxy, + download_browser_api, + get_browser_versions, + check_browser_downloaded, + ), + components(schemas( + ApiProfile, + ApiProfilesResponse, + ApiProfileResponse, + CreateProfileRequest, + UpdateProfileRequest, + ApiGroupResponse, + CreateGroupRequest, + UpdateGroupRequest, + ApiProxyResponse, + CreateProxyRequest, + UpdateProxyRequest, + DownloadBrowserRequest, + DownloadBrowserResponse, + RunProfileResponse, + RunProfileRequest, + OpenUrlRequest, + ProxySettings, + )), + tags( + (name = "profiles", description = "Profile management endpoints"), + (name = "groups", description = "Group management endpoints"), + (name = "tags", description = "Tag management endpoints"), + (name = "proxies", description = "Proxy management endpoints"), + (name = "browsers", description = "Browser management endpoints"), + ), + modifiers(&SecurityAddon), +)] +struct ApiDoc; + +struct SecurityAddon; + +impl utoipa::Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + if let Some(components) = openapi.components.as_mut() { + components.add_security_scheme( + "bearer_auth", + utoipa::openapi::security::SecurityScheme::Http( + utoipa::openapi::security::HttpBuilder::new() + .scheme(utoipa::openapi::security::HttpAuthScheme::Bearer) + .bearer_format("JWT") + .build(), + ), + ); + } + } +} + pub struct ApiServer { port: Option, shutdown_tx: Option>, @@ -207,40 +290,44 @@ impl ApiServer { .map_err(|e| format!("Failed to get local address: {e}"))? .port(); - // Create router with CORS, authentication, and versioning - let v1_routes = 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("/profiles/{id}/run", post(run_profile)) - .route("/profiles/{id}/open-url", post(open_url_in_profile)) - .route("/profiles/{id}/kill", post(kill_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), - ) - .route("/browsers/download", post(download_browser_api)) - .route("/browsers/{browser}/versions", get(get_browser_versions)) - .route( - "/browsers/{browser}/versions/{version}/downloaded", - get(check_browser_downloaded), - ) - .layer(middleware::from_fn_with_state( - state.clone(), - auth_middleware, - )); + // Create router with OpenAPI documentation + let (v1_routes, _) = OpenApiRouter::new() + .routes(routes!( + get_profiles, + create_profile, + get_profile, + update_profile, + delete_profile, + run_profile, + open_url_in_profile, + kill_profile, + get_groups, + create_group, + get_group, + update_group, + delete_group, + get_tags, + get_proxies, + create_proxy, + get_proxy, + update_proxy, + delete_proxy, + download_browser_api, + get_browser_versions, + check_browser_downloaded, + )) + .split_for_parts(); + + let api = ApiDoc::openapi(); + + let v1_routes = v1_routes.layer(middleware::from_fn_with_state( + state.clone(), + auth_middleware, + )); let app = Router::new() .nest("/v1", v1_routes) + .route("/openapi.json", get(move || async move { Json(api) })) .layer(CorsLayer::permissive()) .with_state(state); @@ -346,6 +433,19 @@ pub async fn get_api_server_status() -> Result, String> { } // API Handlers - Profiles +#[utoipa::path( + get, + path = "/v1/profiles", + responses( + (status = 200, description = "List of all profiles", body = ApiProfilesResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn get_profiles() -> Result, StatusCode> { let profile_manager = ProfileManager::instance(); match profile_manager.list_profiles() { @@ -380,6 +480,23 @@ async fn get_profiles() -> Result, StatusCode> { } } +#[utoipa::path( + get, + path = "/v1/profiles/{id}", + params( + ("id" = String, Path, description = "Profile ID") + ), + responses( + (status = 200, description = "Profile details", body = ApiProfileResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Profile not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn get_profile( Path(id): Path, State(_state): State, @@ -415,6 +532,21 @@ async fn get_profile( } } +#[utoipa::path( + post, + path = "/v1/profiles", + request_body = CreateProfileRequest, + responses( + (status = 200, description = "Profile created successfully", body = ApiProfileResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn create_profile( State(state): State, Json(request): Json, @@ -485,6 +617,25 @@ async fn create_profile( } } +#[utoipa::path( + put, + path = "/v1/profiles/{id}", + params( + ("id" = String, Path, description = "Profile ID") + ), + request_body = UpdateProfileRequest, + responses( + (status = 200, description = "Profile updated successfully", body = ApiProfileResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Profile not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn update_profile( Path(id): Path, State(state): State, @@ -566,6 +717,23 @@ async fn update_profile( get_profile(Path(id), State(state)).await } +#[utoipa::path( + delete, + path = "/v1/profiles/{id}", + params( + ("id" = String, Path, description = "Profile ID") + ), + responses( + (status = 204, description = "Profile deleted successfully"), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn delete_profile( Path(id): Path, State(state): State, @@ -578,6 +746,19 @@ async fn delete_profile( } // API Handlers - Groups +#[utoipa::path( + get, + path = "/v1/groups", + responses( + (status = 200, description = "List of all groups", body = Vec), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "groups" +)] async fn get_groups( State(_state): State, ) -> Result>, StatusCode> { @@ -602,6 +783,23 @@ async fn get_groups( } } +#[utoipa::path( + get, + path = "/v1/groups/{id}", + params( + ("id" = String, Path, description = "Group ID") + ), + responses( + (status = 200, description = "Group details", body = ApiGroupResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Group not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "groups" +)] async fn get_group( Path(id): Path, State(_state): State, @@ -625,6 +823,21 @@ async fn get_group( } } +#[utoipa::path( + post, + path = "/v1/groups", + request_body = CreateGroupRequest, + responses( + (status = 200, description = "Group created successfully", body = ApiGroupResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "groups" +)] async fn create_group( State(state): State, Json(request): Json, @@ -642,6 +855,25 @@ async fn create_group( } } +#[utoipa::path( + put, + path = "/v1/groups/{id}", + params( + ("id" = String, Path, description = "Group ID") + ), + request_body = UpdateGroupRequest, + responses( + (status = 200, description = "Group updated successfully", body = ApiGroupResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Group not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "groups" +)] async fn update_group( Path(id): Path, State(state): State, @@ -660,6 +892,23 @@ async fn update_group( } } +#[utoipa::path( + delete, + path = "/v1/groups/{id}", + params( + ("id" = String, Path, description = "Group ID") + ), + responses( + (status = 204, description = "Group deleted successfully"), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "groups" +)] async fn delete_group( Path(id): Path, State(state): State, @@ -674,6 +923,19 @@ async fn delete_group( } // API Handlers - Tags +#[utoipa::path( + get, + path = "/v1/tags", + responses( + (status = 200, description = "List of all tags", body = Vec), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "tags" +)] async fn get_tags(State(_state): State) -> Result>, StatusCode> { match TAG_MANAGER.lock() { Ok(manager) => match manager.get_all_tags() { @@ -685,6 +947,19 @@ async fn get_tags(State(_state): State) -> Result), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "proxies" +)] async fn get_proxies( State(_state): State, ) -> Result>, StatusCode> { @@ -695,12 +970,29 @@ async fn get_proxies( .map(|p| ApiProxyResponse { id: p.id, name: p.name, - proxy_settings: serde_json::to_value(p.proxy_settings).unwrap_or_default(), + proxy_settings: p.proxy_settings, }) .collect(), )) } +#[utoipa::path( + get, + path = "/v1/proxies/{id}", + params( + ("id" = String, Path, description = "Proxy ID") + ), + responses( + (status = 200, description = "Proxy details", body = ApiProxyResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Proxy not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "proxies" +)] async fn get_proxy( Path(id): Path, State(_state): State, @@ -710,45 +1002,65 @@ async fn get_proxy( Ok(Json(ApiProxyResponse { id: proxy.id, name: proxy.name, - proxy_settings: serde_json::to_value(proxy.proxy_settings).unwrap_or_default(), + proxy_settings: proxy.proxy_settings, })) } else { Err(StatusCode::NOT_FOUND) } } +#[utoipa::path( + post, + path = "/v1/proxies", + request_body = CreateProxyRequest, + responses( + (status = 200, description = "Proxy created successfully", body = ApiProxyResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "proxies" +)] async fn create_proxy( State(state): State, Json(request): Json, ) -> Result, StatusCode> { - // Convert JSON value to ProxySettings - match serde_json::from_value(request.proxy_settings.clone()) { - Ok(proxy_settings) => { - match PROXY_MANAGER.create_stored_proxy( - &state.app_handle, - 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), - } - } + match PROXY_MANAGER.create_stored_proxy( + &state.app_handle, + request.name.clone(), + request.proxy_settings, + ) { + Ok(proxy) => Ok(Json(ApiProxyResponse { + id: proxy.id, + name: proxy.name, + proxy_settings: proxy.proxy_settings, + })), Err(_) => Err(StatusCode::BAD_REQUEST), } } +#[utoipa::path( + put, + path = "/v1/proxies/{id}", + params( + ("id" = String, Path, description = "Proxy ID") + ), + request_body = UpdateProxyRequest, + responses( + (status = 200, description = "Proxy updated successfully", body = ApiProxyResponse), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Proxy not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "proxies" +)] async fn update_proxy( Path(id): Path, State(state): State, @@ -757,14 +1069,9 @@ async fn update_proxy( 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() - }; + let new_proxy_settings = request + .proxy_settings + .unwrap_or(proxy.proxy_settings.clone()); match PROXY_MANAGER.update_stored_proxy( &state.app_handle, @@ -775,7 +1082,7 @@ async fn update_proxy( Ok(_) => Ok(Json(ApiProxyResponse { id, name: new_name, - proxy_settings: serde_json::to_value(new_proxy_settings).unwrap_or_default(), + proxy_settings: new_proxy_settings, })), Err(_) => Err(StatusCode::BAD_REQUEST), } @@ -784,6 +1091,23 @@ async fn update_proxy( } } +#[utoipa::path( + delete, + path = "/v1/proxies/{id}", + params( + ("id" = String, Path, description = "Proxy ID") + ), + responses( + (status = 204, description = "Proxy deleted successfully"), + (status = 400, description = "Bad request"), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "proxies" +)] async fn delete_proxy( Path(id): Path, State(state): State, @@ -795,6 +1119,24 @@ async fn delete_proxy( } // API Handler - Run Profile with Remote Debugging +#[utoipa::path( + post, + path = "/v1/profiles/{id}/run", + params( + ("id" = String, Path, description = "Profile ID") + ), + request_body = RunProfileRequest, + responses( + (status = 200, description = "Profile launched successfully", body = RunProfileResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Profile not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn run_profile( Path(id): Path, State(state): State, @@ -836,6 +1178,24 @@ async fn run_profile( } // API Handler - Open URL in existing browser +#[utoipa::path( + post, + path = "/v1/profiles/{id}/open-url", + params( + ("id" = String, Path, description = "Profile ID") + ), + request_body = OpenUrlRequest, + responses( + (status = 200, description = "URL opened successfully"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Profile not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn open_url_in_profile( Path(id): Path, State(state): State, @@ -852,6 +1212,23 @@ async fn open_url_in_profile( } // API Handler - Kill browser process +#[utoipa::path( + post, + path = "/v1/profiles/{id}/kill", + params( + ("id" = String, Path, description = "Profile ID") + ), + responses( + (status = 204, description = "Browser process killed successfully"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Profile not found"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "profiles" +)] async fn kill_profile( Path(id): Path, State(state): State, @@ -876,6 +1253,20 @@ async fn kill_profile( } // API Handler - Download Browser +#[utoipa::path( + post, + path = "/v1/browsers/download", + request_body = DownloadBrowserRequest, + responses( + (status = 200, description = "Browser download initiated", body = DownloadBrowserResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "browsers" +)] async fn download_browser_api( State(state): State, Json(request): Json, @@ -897,6 +1288,22 @@ async fn download_browser_api( } // API Handler - Get Browser Versions +#[utoipa::path( + get, + path = "/v1/browsers/{browser}/versions", + params( + ("browser" = String, Path, description = "Browser name") + ), + responses( + (status = 200, description = "List of available browser versions", body = Vec), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "browsers" +)] async fn get_browser_versions( Path(browser): Path, State(_state): State, @@ -913,6 +1320,23 @@ async fn get_browser_versions( } // API Handler - Check if Browser is Downloaded +#[utoipa::path( + get, + path = "/v1/browsers/{browser}/versions/{version}/downloaded", + params( + ("browser" = String, Path, description = "Browser name"), + ("version" = String, Path, description = "Browser version") + ), + responses( + (status = 200, description = "Browser download status", body = bool), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearer_auth" = []) + ), + tag = "browsers" +)] async fn check_browser_downloaded( Path((browser, version)): Path<(String, String)>, State(_state): State, diff --git a/src-tauri/src/browser.rs b/src-tauri/src/browser.rs index 8c00477..fbdb974 100644 --- a/src-tauri/src/browser.rs +++ b/src-tauri/src/browser.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; +use utoipa::ToSchema; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ProxySettings { pub proxy_type: String, // "http", "https", "socks4", or "socks5" pub host: String,