1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-20 09:39:38 +01:00

Allow file upload for ADF; fix some server bugs

This commit is contained in:
monsterkrampe 2023-04-14 09:01:39 +02:00
parent da8e79147b
commit 9012f0ee23
No known key found for this signature in database
GPG Key ID: B8ADC1F5A5CE5057
6 changed files with 263 additions and 128 deletions

97
Cargo.lock generated
View File

@ -122,6 +122,44 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "actix-multipart"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e"
dependencies = [
"actix-multipart-derive",
"actix-utils",
"actix-web",
"bytes",
"derive_more",
"futures-core",
"futures-util",
"httparse",
"local-waker",
"log",
"memchr",
"mime",
"serde",
"serde_json",
"serde_plain",
"tempfile",
"tokio",
]
[[package]]
name = "actix-multipart-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7"
dependencies = [
"darling 0.14.4",
"parse-size",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "actix-router" name = "actix-router"
version = "0.5.1" version = "0.5.1"
@ -278,6 +316,7 @@ dependencies = [
"actix-cors", "actix-cors",
"actix-files", "actix-files",
"actix-identity", "actix-identity",
"actix-multipart",
"actix-session", "actix-session",
"actix-web", "actix-web",
"adf_bdd", "adf_bdd",
@ -918,8 +957,18 @@ version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.13.4",
"darling_macro", "darling_macro 0.13.4",
]
[[package]]
name = "darling"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
dependencies = [
"darling_core 0.14.4",
"darling_macro 0.14.4",
] ]
[[package]] [[package]]
@ -936,13 +985,38 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "darling_core"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 1.0.109",
]
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.13.4" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.13.4",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core 0.14.4",
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",
] ]
@ -1881,6 +1955,12 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "parse-size"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae"
[[package]] [[package]]
name = "password-hash" name = "password-hash"
version = "0.5.0" version = "0.5.0"
@ -2339,6 +2419,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_plain"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -2367,7 +2456,7 @@ version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
dependencies = [ dependencies = [
"darling", "darling 0.13.4",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",

View File

@ -25,6 +25,7 @@ argon2 = "0.5.0"
actix-session = { version="0.7.2", features = ["cookie-session"] } actix-session = { version="0.7.2", features = ["cookie-session"] }
names = "0.14.0" names = "0.14.0"
futures-util = "0.3.28" futures-util = "0.3.28"
actix-multipart = "0.6.0"
[features] [features]
cors_for_local_development = [] cors_for_local_development = []

View File

@ -4,6 +4,7 @@ use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::Duration;
use actix_identity::Identity; use actix_identity::Identity;
use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
use actix_web::rt::spawn; use actix_web::rt::spawn;
use actix_web::rt::task::spawn_blocking; use actix_web::rt::task::spawn_blocking;
use actix_web::rt::time::timeout; use actix_web::rt::time::timeout;
@ -27,13 +28,13 @@ use crate::user::{username_exists, User};
type Ac = Vec<Term>; type Ac = Vec<Term>;
type AcDb = Vec<String>; type AcDb = Vec<String>;
#[derive(Copy, Clone, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub(crate) enum Parsing { pub(crate) enum Parsing {
Naive, Naive,
Hybrid, Hybrid,
} }
#[derive(Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub(crate) enum Strategy { pub(crate) enum Strategy {
Ground, Ground,
Complete, Complete,
@ -56,6 +57,7 @@ impl From<AcAndGraph> for Bson {
} }
#[derive(Clone, Default, Deserialize, Serialize)] #[derive(Clone, Default, Deserialize, Serialize)]
#[serde(tag = "type", content = "content")]
pub(crate) enum OptionWithError<T> { pub(crate) enum OptionWithError<T> {
Some(T), Some(T),
Error(String), Error(String),
@ -182,11 +184,36 @@ pub(crate) struct AdfProblem {
pub(crate) acs_per_strategy: AcsPerStrategy, pub(crate) acs_per_strategy: AcsPerStrategy,
} }
#[derive(Clone, Deserialize)] #[derive(MultipartForm)]
struct AddAdfProblemBody { struct AddAdfProblemBodyMultipart {
name: Option<String>, name: Text<String>,
code: Option<Text<String>>, // Either Code or File is set
file: Option<TempFile>, // Either Code or File is set
parsing: Text<Parsing>,
}
#[derive(Clone)]
struct AddAdfProblemBodyPlain {
name: String,
code: String, code: String,
parse_strategy: Parsing, parsing: Parsing,
}
impl TryFrom<AddAdfProblemBodyMultipart> for AddAdfProblemBodyPlain {
type Error = &'static str;
fn try_from(source: AddAdfProblemBodyMultipart) -> Result<Self, Self::Error> {
Ok(Self {
name: source.name.into_inner(),
code: source
.file
.map(|f| std::io::read_to_string(f.file).expect("TempFile should be readable"))
.or_else(|| source.code.map(|c| c.into_inner()))
.and_then(|code| (!code.is_empty()).then_some(code))
.ok_or("Either a file or the code has to be provided.")?,
parsing: source.parsing.into_inner(),
})
}
} }
async fn adf_problem_exists( async fn adf_problem_exists(
@ -233,9 +260,12 @@ async fn add_adf_problem(
req: HttpRequest, req: HttpRequest,
app_state: web::Data<AppState>, app_state: web::Data<AppState>,
identity: Option<Identity>, identity: Option<Identity>,
req_body: web::Json<AddAdfProblemBody>, req_body: MultipartForm<AddAdfProblemBodyMultipart>,
) -> impl Responder { ) -> impl Responder {
let adf_problem_input: AddAdfProblemBody = req_body.into_inner(); let adf_problem_input: AddAdfProblemBodyPlain = match req_body.into_inner().try_into() {
Ok(input) => input,
Err(err) => return HttpResponse::BadRequest().body(err),
};
let adf_coll: mongodb::Collection<AdfProblem> = app_state let adf_coll: mongodb::Collection<AdfProblem> = app_state
.mongodb_client .mongodb_client
.database(DB_NAME) .database(DB_NAME)
@ -291,35 +321,32 @@ async fn add_adf_problem(
Some(Ok(username)) => username, Some(Ok(username)) => username,
}; };
let problem_name = match &adf_problem_input.name { let problem_name = if !adf_problem_input.name.is_empty() {
Some(name) => { if adf_problem_exists(&adf_coll, &adf_problem_input.name, &username).await {
if adf_problem_exists(&adf_coll, name, &username).await { return HttpResponse::Conflict()
return HttpResponse::Conflict() .body("ADF Problem with that name already exists. Please pick another one!");
.body("ADF Problem with that name already exists. Please pick another one!");
}
name.clone()
} }
None => {
let gen = Generator::with_naming(Name::Numbered);
let candidates = gen.take(10);
let mut name: Option<String> = None; adf_problem_input.name.clone()
for candidate in candidates { } else {
if name.is_some() { let gen = Generator::with_naming(Name::Numbered);
continue; let candidates = gen.take(10);
}
if !(adf_problem_exists(&adf_coll, &candidate, &username).await) { let mut name: Option<String> = None;
name = Some(candidate); for candidate in candidates {
} if name.is_some() {
continue;
} }
match name { if !(adf_problem_exists(&adf_coll, &candidate, &username).await) {
Some(name) => name, name = Some(candidate);
None => { }
return HttpResponse::InternalServerError().body("Could not generate new name.") }
}
match name {
Some(name) => name,
None => {
return HttpResponse::InternalServerError().body("Could not generate new name.")
} }
} }
}; };
@ -328,7 +355,7 @@ async fn add_adf_problem(
name: problem_name.clone(), name: problem_name.clone(),
username: username.clone(), username: username.clone(),
code: adf_problem_input.code.clone(), code: adf_problem_input.code.clone(),
parsing_used: adf_problem_input.parse_strategy, parsing_used: adf_problem_input.parsing,
adf: SimplifiedAdfOpt::None, adf: SimplifiedAdfOpt::None,
acs_per_strategy: AcsPerStrategy::default(), acs_per_strategy: AcsPerStrategy::default(),
}; };
@ -362,21 +389,25 @@ async fn add_adf_problem(
std::thread::sleep(Duration::from_secs(20)); std::thread::sleep(Duration::from_secs(20));
let parser = AdfParser::default(); let parser = AdfParser::default();
parser.parse()(&adf_problem_input.code) let parse_result = parser.parse()(&adf_problem_input.code)
.map_err(|_| "ADF could not be parsed, double check your input!")?; .map_err(|_| "ADF could not be parsed, double check your input!");
let lib_adf = match adf_problem_input.parse_strategy { let result = parse_result.map(|_| {
Parsing::Naive => Adf::from_parser(&parser), let lib_adf = match adf_problem_input.parsing {
Parsing::Hybrid => { Parsing::Naive => Adf::from_parser(&parser),
let bd_adf = BdAdf::from_parser(&parser); Parsing::Hybrid => {
bd_adf.hybrid_step_opt(false) let bd_adf = BdAdf::from_parser(&parser);
} bd_adf.hybrid_step_opt(false)
}; }
};
let ac_and_graph = AcAndGraph { let ac_and_graph = AcAndGraph {
ac: lib_adf.ac.iter().map(|t| t.0.to_string()).collect(), ac: lib_adf.ac.iter().map(|t| t.0.to_string()).collect(),
graph: lib_adf.into_double_labeled_graph(None), graph: lib_adf.into_double_labeled_graph(None),
}; };
(SimplifiedAdf::from_lib_adf(lib_adf), ac_and_graph)
});
app_state app_state
.currently_running .currently_running
@ -384,7 +415,7 @@ async fn add_adf_problem(
.unwrap() .unwrap()
.remove(&running_info); .remove(&running_info);
Ok::<_, &str>((SimplifiedAdf::from_lib_adf(lib_adf), ac_and_graph)) result
}), }),
); );

View File

@ -17,13 +17,14 @@ pub(crate) const DB_NAME: &str = "adf-obdd";
pub(crate) const USER_COLL: &str = "users"; pub(crate) const USER_COLL: &str = "users";
pub(crate) const ADF_COLL: &str = "adf-problems"; pub(crate) const ADF_COLL: &str = "adf-problems";
#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(tag = "type", content = "content")]
pub(crate) enum Task { pub(crate) enum Task {
Parse, Parse,
Solve(Strategy), Solve(Strategy),
} }
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct RunningInfo { pub(crate) struct RunningInfo {
pub(crate) username: String, pub(crate) username: String,
pub(crate) adf_name: String, pub(crate) adf_name: String,

View File

@ -43,6 +43,12 @@ async fn main() -> std::io::Result<()> {
// cookie secret ket // cookie secret ket
let secret_key = Key::generate(); let secret_key = Key::generate();
// needs to be set outside of httpserver closure to only create it once!
let app_data = web::Data::new(AppState {
mongodb_client: client.clone(),
currently_running: Mutex::new(HashSet::new()),
});
HttpServer::new(move || { HttpServer::new(move || {
let app = App::new(); let app = App::new();
@ -62,37 +68,34 @@ async fn main() -> std::io::Result<()> {
#[cfg(not(feature = "cors_for_local_development"))] #[cfg(not(feature = "cors_for_local_development"))]
let cookie_secure = true; let cookie_secure = true;
app.app_data(web::Data::new(AppState { app.app_data(app_data.clone())
mongodb_client: client.clone(), .wrap(IdentityMiddleware::default())
currently_running: Mutex::new(HashSet::new()), .wrap(
})) SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
.wrap(IdentityMiddleware::default()) .cookie_name("adf-obdd-service-auth".to_owned())
.wrap( .cookie_secure(cookie_secure)
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone()) .session_lifecycle(PersistentSession::default().session_ttl(COOKIE_DURATION))
.cookie_name("adf-obdd-service-auth".to_owned()) .build(),
.cookie_secure(cookie_secure) )
.session_lifecycle(PersistentSession::default().session_ttl(COOKIE_DURATION)) .service(
.build(), web::scope("/users")
) .service(register)
.service( .service(delete_account)
web::scope("/users") .service(login)
.service(register) .service(logout)
.service(delete_account) .service(user_info)
.service(login) .service(update_user),
.service(logout) )
.service(user_info) .service(
.service(update_user), web::scope("/adf")
) .service(add_adf_problem)
.service( .service(solve_adf_problem)
web::scope("/adf") .service(get_adf_problem)
.service(add_adf_problem) .service(delete_adf_problem)
.service(solve_adf_problem) .service(get_adf_problems_for_user),
.service(get_adf_problem) )
.service(delete_adf_problem) // this mus be last to not override anything
.service(get_adf_problems_for_user), .service(fs::Files::new("/", ASSET_DIRECTORY).index_file("index.html"))
)
// this mus be last to not override anything
.service(fs::Files::new("/", ASSET_DIRECTORY).index_file("index.html"))
}) })
.bind(("0.0.0.0", 8080))? .bind(("0.0.0.0", 8080))?
.run() .run()

View File

@ -110,39 +110,44 @@ async fn delete_account(
.database(DB_NAME) .database(DB_NAME)
.collection(ADF_COLL); .collection(ADF_COLL);
match identity.map(|id| id.id()) { match identity {
None => HttpResponse::Unauthorized().body("You need to login to delete your account."), None => HttpResponse::Unauthorized().body("You are not logged in."),
Some(Err(err)) => HttpResponse::InternalServerError().body(err.to_string()), Some(id) => match id.id() {
Some(Ok(username)) => { Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
// Delete all adfs created by user Ok(username) => {
match adf_coll // Delete all adfs created by user
.delete_many(doc! { "username": &username }, None) match adf_coll
.await .delete_many(doc! { "username": &username }, None)
{ .await
Err(err) => HttpResponse::InternalServerError().body(err.to_string()), {
Ok(DeleteResult { Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
deleted_count: _, .. Ok(DeleteResult {
}) => { deleted_count: _, ..
// Delete actual user }) => {
match user_coll // Delete actual user
.delete_one(doc! { "username": &username }, None) match user_coll
.await .delete_one(doc! { "username": &username }, None)
{ .await
Ok(DeleteResult { {
deleted_count: 0, .. Ok(DeleteResult {
}) => HttpResponse::InternalServerError() deleted_count: 0, ..
.body("Account could not be deleted."), }) => HttpResponse::InternalServerError()
Ok(DeleteResult { .body("Account could not be deleted."),
deleted_count: 1, .. Ok(DeleteResult {
}) => HttpResponse::Ok().body("Account deleted."), deleted_count: 1, ..
Ok(_) => unreachable!( }) => {
id.logout();
HttpResponse::Ok().body("Account deleted.")
}
Ok(_) => unreachable!(
"delete_one removes at most one entry so all cases are covered already" "delete_one removes at most one entry so all cases are covered already"
), ),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()), Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
} }
} }
} }
} },
} }
} }
@ -237,28 +242,33 @@ async fn user_info(app_state: web::Data<AppState>, identity: Option<Identity>) -
.database(DB_NAME) .database(DB_NAME)
.collection(USER_COLL); .collection(USER_COLL);
match identity.map(|id| id.id()) { match identity {
None => { None => {
HttpResponse::Unauthorized().body("You need to login get your account information.") HttpResponse::Unauthorized().body("You need to login get your account information.")
} }
Some(Err(err)) => HttpResponse::InternalServerError().body(err.to_string()), Some(id) => match id.id() {
Some(Ok(username)) => { Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
match user_coll Ok(username) => {
.find_one(doc! { "username": &username }, None) match user_coll
.await .find_one(doc! { "username": &username }, None)
{ .await
Ok(Some(user)) => { {
let info = UserInfo { Ok(Some(user)) => {
username: user.username, let info = UserInfo {
temp: user.password.is_none(), username: user.username,
}; temp: user.password.is_none(),
};
HttpResponse::Ok().json(info) HttpResponse::Ok().json(info)
}
Ok(None) => {
id.logout();
HttpResponse::NotFound().body("Logged in user does not exist anymore.")
}
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
} }
Ok(None) => HttpResponse::NotFound().body("Logged in user does not exist anymore."),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
} }
} },
} }
} }