1use actix_multipart::Multipart;
9use actix_web::{web, HttpResponse};
10use futures_util::StreamExt;
11use icb_graph::graph::CodePropertyGraph;
12use std::io::Cursor;
13use std::sync::Mutex;
14use tempfile::tempdir;
15use zip::ZipArchive;
16
17use crate::graph_builder;
18
19#[derive(serde::Deserialize)]
20pub struct UploadQuery {
21 pub languages: Option<String>,
22}
23
24pub async fn handle_upload(
26 data: web::Data<Mutex<CodePropertyGraph>>,
27 query: web::Query<UploadQuery>,
28 mut payload: Multipart,
29) -> HttpResponse {
30 let tmp = match tempdir() {
31 Ok(dir) => dir,
32 Err(e) => return HttpResponse::InternalServerError().body(e.to_string()),
33 };
34
35 let mut zip_bytes: Vec<u8> = Vec::new();
36
37 while let Some(item) = payload.next().await {
38 let mut field = match item {
39 Ok(f) => f,
40 Err(e) => return HttpResponse::BadRequest().body(e.to_string()),
41 };
42 while let Some(chunk) = field.next().await {
43 let chunk = match chunk {
44 Ok(c) => c,
45 Err(_) => break,
46 };
47 zip_bytes.extend_from_slice(&chunk);
48 }
49 }
50
51 if zip_bytes.is_empty() {
52 return HttpResponse::BadRequest().body("No ZIP file received");
53 }
54
55 let reader = Cursor::new(zip_bytes);
56 let mut archive = match ZipArchive::new(reader) {
57 Ok(a) => a,
58 Err(e) => return HttpResponse::BadRequest().body(format!("Invalid ZIP: {}", e)),
59 };
60
61 for i in 0..archive.len() {
62 let mut file = match archive.by_index(i) {
63 Ok(f) => f,
64 Err(_) => continue,
65 };
66
67 let name = file.name().to_string();
68 let path = tmp.path().join(&name);
69
70 if file.is_dir() {
71 std::fs::create_dir_all(&path).ok();
72 } else {
73 if let Some(parent) = path.parent() {
74 std::fs::create_dir_all(parent).ok();
75 }
76 let mut out = match std::fs::File::create(&path) {
77 Ok(f) => f,
78 Err(_) => continue,
79 };
80 std::io::copy(&mut file, &mut out).ok();
81 }
82 }
83
84 let graph_result = if let Some(langs) = &query.languages {
85 let languages: Vec<String> = langs.split(',').map(|s| s.trim().to_string()).collect();
86 graph_builder::build_or_load_graph_multi(
87 tmp.path(),
88 &languages,
89 None, None, true,
92 )
93 } else {
94 graph_builder::build_or_load_graph(tmp.path(), "auto", None, None, true)
95 };
96
97 drop(tmp);
98
99 match graph_result {
100 Ok(new_graph) => {
101 let nodes = new_graph.graph.node_count();
102 let edges = new_graph.graph.edge_count();
103 if let Ok(mut locked) = data.lock() {
104 *locked = new_graph;
105 }
106 HttpResponse::Ok().json(serde_json::json!({
107 "status": "ok",
108 "nodes": nodes,
109 "edges": edges,
110 }))
111 }
112 Err(e) => HttpResponse::BadRequest().body(e.to_string()),
113 }
114}