diff --git a/Cargo.lock b/Cargo.lock index e85623b..be46cbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,7 +371,17 @@ version = "0.1.0" dependencies = [ "actix-cors", "actix-web", - "anyhow", + "clap", + "dt_core", + "serde", +] + +[[package]] +name = "api_server_portable" +version = "0.1.0" +dependencies = [ + "actix-cors", + "actix-web", "clap", "dt_core", "serde", diff --git a/Cargo.toml b/Cargo.toml index 220bf13..eb08620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/api_server", + "crates/api_server_portable", "crates/cli", "crates/demo", "crates/dt_core", @@ -25,4 +26,4 @@ swc_core = { version = "0.104.2", features = ["common", "ecma_ast", "ecma swc_ecma_parser = { version = "0.150.0", features = ["typescript"] } clap = { version = "4.5", features = ["derive"] } rusqlite = { version = "0.32.1", features = ["bundled"] } -indicatif = "0.17.8" \ No newline at end of file +indicatif = "0.17.8" diff --git a/crates/api_server/Cargo.toml b/crates/api_server/Cargo.toml index d491b6d..458d576 100644 --- a/crates/api_server/Cargo.toml +++ b/crates/api_server/Cargo.toml @@ -7,7 +7,6 @@ version = "0.1.0" [dependencies] -anyhow = { workspace = true } serde = { workspace = true } clap = { workspace = true } diff --git a/crates/api_server_portable/Cargo.toml b/crates/api_server_portable/Cargo.toml new file mode 100644 index 0000000..3e91580 --- /dev/null +++ b/crates/api_server_portable/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["Leo Lin "] +description = "run the tracker and provide APIs" +edition = "2021" +name = "api_server_portable" +version = "0.1.0" + + +[dependencies] +serde = { workspace = true } +clap = { workspace = true } + +actix-web = "4" +actix-cors = "0.7" + +dt_core = { version = "0.1.0", path = "../dt_core" } \ No newline at end of file diff --git a/crates/api_server_portable/src/main.rs b/crates/api_server_portable/src/main.rs new file mode 100644 index 0000000..492c287 --- /dev/null +++ b/crates/api_server_portable/src/main.rs @@ -0,0 +1,175 @@ +use actix_cors::Cors; +use actix_web::{error, get, web, App, HttpServer, Result}; +use clap::Parser; +use dt_core::{ + graph::used_by_graph::UsedByGraph, + portable::Portable, + tracker::{DependencyTracker, TraceTarget}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::Read, +}; + +struct AppState { + project_root: String, + translation_json: HashMap, + i18n_to_symbol: HashMap>>, + symbol_to_route: HashMap>>, + used_by_graph: UsedByGraph, +} + +#[derive(Serialize, Clone)] +struct Step { + module_path: String, + symbol_name: String, +} + +#[derive(Serialize)] +struct SearchResponse { + project_root: String, + trace_result: HashMap>>>>, +} + +#[derive(Deserialize)] +struct Info { + q: String, + exact_match: bool, +} + +#[get("/search")] +async fn search( + data: web::Data, + info: web::Query, +) -> Result> { + let search = &info.q; + let exact_match = info.exact_match; + + let mut matched_i18n_keys: Vec = Vec::new(); + match exact_match { + true => { + for (i18n_key, translation) in data.translation_json.iter() { + if translation == search { + matched_i18n_keys.push(i18n_key.to_owned()); + } + } + } + false => { + for (i18n_key, translation) in data.translation_json.iter() { + if translation.contains(search) { + matched_i18n_keys.push(i18n_key.to_owned()); + } + } + } + } + + if matched_i18n_keys.len() == 0 { + return Err(error::ErrorNotFound(format!("No result for {}", search))); + } + + let mut dependency_tracker = DependencyTracker::new(&data.used_by_graph, true); + let mut trace_result = HashMap::new(); + for i18n_key in matched_i18n_keys.iter() { + let mut route_to_paths = HashMap::new(); + if let Some(i18n_key_usage) = data.i18n_to_symbol.get(i18n_key) { + for (module_path, symbols) in i18n_key_usage { + for symbol in symbols { + let full_paths = dependency_tracker + .trace((module_path.clone(), TraceTarget::LocalVar(symbol.clone()))) + .unwrap(); + // traverse each path and check if any symbol is used in some routes + for mut full_path in full_paths { + full_path.reverse(); + for (i, (step_module_path, step_trace_target)) in + full_path.iter().enumerate() + { + match step_trace_target { + TraceTarget::LocalVar(step_symbol_name) => { + if let Some(symbol_to_routes) = + data.symbol_to_route.get(step_module_path) + { + if let Some(routes) = symbol_to_routes.get(step_symbol_name) + { + let dependency_from_target_to_route: Vec = + full_path[0..i] + .iter() + .map(|(path, target)| Step { + module_path: path.clone(), + symbol_name: target.to_string(), + }) + .collect(); + for route in routes.iter() { + if !route_to_paths.contains_key(route) { + route_to_paths + .insert(route.clone(), HashMap::new()); + } + if !route_to_paths + .get(route) + .unwrap() + .contains_key(symbol) + { + route_to_paths + .get_mut(route) + .unwrap() + .insert(symbol.to_string(), vec![]); + } + route_to_paths + .get_mut(route) + .unwrap() + .get_mut(symbol) + .unwrap() + .push(dependency_from_target_to_route.clone()); + } + } + } + } + _ => (), + } + } + } + } + } + } + trace_result.insert(i18n_key.to_string(), route_to_paths); + } + + Ok(web::Json(SearchResponse { + project_root: data.project_root.to_owned(), + trace_result, + })) +} + +#[derive(Parser)] +#[command(version, about = "Start the server to provide search API", long_about = None)] +struct Cli { + /// Portable path + #[arg(short)] + portable: String, +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let cli = Cli::parse(); + let mut file = File::open(cli.portable)?; + let mut exported = String::new(); + file.read_to_string(&mut exported)?; + let portable = Portable::import(&exported).unwrap(); + + HttpServer::new(move || { + App::new() + .wrap(Cors::default().allow_any_method().allow_any_origin()) + .app_data(web::Data::new(AppState { + project_root: portable.project_root.clone(), + translation_json: portable.translation_json.clone(), + i18n_to_symbol: portable.i18n_to_symbol.clone(), + symbol_to_route: portable.symbol_to_route.clone(), + used_by_graph: portable.used_by_graph.clone(), + })) + .service(search) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +}