206 lines
6.6 KiB
Rust
206 lines
6.6 KiB
Rust
#![feature(proc_macro_hygiene, decl_macro)]
|
|
|
|
#[macro_use] extern crate rocket;
|
|
#[macro_use] extern crate rocket_contrib;
|
|
#[macro_use] extern crate serde_derive;
|
|
extern crate rocket_cors;
|
|
extern crate cookbook;
|
|
extern crate planner;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use rocket::{
|
|
response::{NamedFile, status::NotFound},
|
|
http::Method,
|
|
};
|
|
use rocket_cors::{AllowedHeaders, AllowedOrigins, Error};
|
|
|
|
#[get("/")]
|
|
fn index() -> Result<NamedFile, NotFound<String>> {
|
|
files(PathBuf::from("index.html"))
|
|
}
|
|
|
|
#[get("/<file..>", rank=6)]
|
|
fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
|
|
let path = Path::new("vue/dist/").join(file);
|
|
NamedFile::open(&path)
|
|
.map_err(|_| NotFound(format!("Bad path: {:?}", path)))
|
|
}
|
|
|
|
mod api {
|
|
use cookbook::*;
|
|
use rocket_contrib::{
|
|
json::Json,
|
|
databases::diesel,
|
|
};
|
|
|
|
#[database("cookbook_db")]
|
|
pub struct CookbookDbConn(diesel::SqliteConnection);
|
|
|
|
/// A serializable wrapper for [`cookbook::recipes::Recipe`]
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct RecipeObject {
|
|
id: i32,
|
|
title: String,
|
|
category: i16,
|
|
ingredients: String,
|
|
preparation: String,
|
|
}
|
|
|
|
impl RecipeObject {
|
|
fn from(conn: &diesel::SqliteConnection, rec: recipes::Recipe) -> Self {
|
|
Self {
|
|
id: rec.id,
|
|
title: rec.title,
|
|
category: rec.category as i16,
|
|
ingredients: rec.ingredients
|
|
.into_manager(conn)
|
|
.display_lines(),
|
|
preparation: rec.preparation,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Retrieves data from database and returns all recipes,
|
|
/// transformed into a js friendly [`RecipeObject`].
|
|
#[get("/list")]
|
|
pub fn recipes_list(conn: CookbookDbConn) -> Json<Vec<RecipeObject>> {
|
|
Json( recipes::load_all(&conn)
|
|
.into_iter()
|
|
.map(|r| RecipeObject::from(&conn, r))
|
|
.collect() )
|
|
}
|
|
|
|
/// Delete a recipe given it's `id`
|
|
#[get("/delete/<id>")]
|
|
pub fn delete_recipe(conn: CookbookDbConn, id: i32) -> Json<bool> {
|
|
Json( recipes::delete(&conn, id) )
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct TemplateItems {
|
|
key: (String, String),
|
|
value: Option<RecipeObject>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct TemplateObject {
|
|
items: Vec<TemplateItems>
|
|
}
|
|
|
|
#[get("/solver/one")]
|
|
pub fn one_solution(conn: CookbookDbConn) -> Json<TemplateObject> {
|
|
use planner::{
|
|
template,
|
|
solver::{Domain, Problem}
|
|
};
|
|
|
|
let possible_values = recipes::load_all(&conn);
|
|
let domain = Domain::new(possible_values);
|
|
let mut problem = Problem::build();
|
|
for (var, dom, ini) in template::Template::generate_variables(&domain) {
|
|
problem = problem.add_variable(var, dom, ini);
|
|
}
|
|
let problem = problem.finish();
|
|
|
|
if let Some(one_result) = problem.solve_one() {
|
|
Json(TemplateObject {
|
|
items: one_result
|
|
.into_iter()
|
|
.map(|(k,v)| {
|
|
TemplateItems {
|
|
key: (format!("{}", k.0), format!("{:?}", k.1)),
|
|
value: v.map(|r| RecipeObject::from(&conn, r.clone())),
|
|
}})
|
|
.collect(),
|
|
})
|
|
} else {
|
|
panic!("No solution at all !");
|
|
}
|
|
}
|
|
|
|
|
|
#[post("/solver/complete", data="<partial>")]
|
|
pub fn complete_problem(conn: CookbookDbConn, partial: Json<Vec<TemplateItems>>) -> Json<TemplateObject> {
|
|
use planner::{
|
|
template,
|
|
solver::{Domain, Problem}
|
|
};
|
|
|
|
let possible_values = recipes::load_all(&conn);
|
|
let domain = Domain::new(possible_values);
|
|
let mut problem = Problem::build();
|
|
for (var, dom, ini) in template::Template::generate_variables(&domain) {
|
|
// Let's hack for now
|
|
// BUGGY because template does not generate every variables, needs refactoring
|
|
// Find variable in partial
|
|
let initial_id = partial.iter()
|
|
.filter(|slot| slot.value.is_some())
|
|
.find_map(|slot| {
|
|
//println!("{:?} vs {:?}", slot, var);
|
|
if slot.key.0 == var.0
|
|
&& slot.key.1 == format!("{:?}",var.1)
|
|
{
|
|
let id = slot.value.as_ref().unwrap().id;
|
|
//println!("found initial : recipe with id {}", id);
|
|
Some(id)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
let ini = if let Some(id) = initial_id {
|
|
let new_ini = domain.find(|r| {r.id == id});
|
|
//println!("Overrided {:?}", new_ini);
|
|
new_ini
|
|
} else {
|
|
ini
|
|
};
|
|
// If found, override initial value
|
|
problem = problem.add_variable(var, dom, ini);
|
|
};
|
|
let problem = problem.finish();
|
|
|
|
if let Some(one_result) = problem.solve_one() {
|
|
Json(TemplateObject {
|
|
items: one_result
|
|
.into_iter()
|
|
.map(|(k,v)| {
|
|
TemplateItems {
|
|
key: (format!("{}", k.0), format!("{:?}", k.1)),
|
|
value: v.map(|r| RecipeObject::from(&conn, r.clone())),
|
|
}})
|
|
.collect(),
|
|
})
|
|
} else {
|
|
panic!("No solution at all !");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<(), Error> {
|
|
let (allowed_origins, failed_origins) = AllowedOrigins::some(&["http://localhost:8080"]);
|
|
assert!(failed_origins.is_empty());
|
|
|
|
// You can also deserialize this
|
|
let cors = rocket_cors::CorsOptions {
|
|
allowed_origins: allowed_origins,
|
|
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
|
|
allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]),
|
|
allow_credentials: true,
|
|
..Default::default()
|
|
}
|
|
.to_cors()?;
|
|
|
|
rocket::ignite()
|
|
.attach(api::CookbookDbConn::fairing())
|
|
.mount("/", routes![index, files])
|
|
.mount("/api", routes![
|
|
api::recipes_list,
|
|
api::delete_recipe,
|
|
api::one_solution,
|
|
api::complete_problem,
|
|
])
|
|
.attach(cors)
|
|
.launch();
|
|
Ok(())
|
|
}
|