Files
CookAssistant/web/src/main.rs
2019-02-10 21:29:48 +01:00

144 lines
4.2 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)]
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)]
pub struct TemplateItems {
key: (String, String),
value: Option<RecipeObject>,
}
#[derive(Serialize)]
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 mut problem = problem
.add_constraint(|_| true)
.finish();
let results = problem.solve_all();
let one_result = results.first().unwrap();
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(),
})
}
}
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])
.attach(cors)
.launch();
Ok(())
}