#![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> { files(PathBuf::from("index.html")) } #[get("/", rank=6)] fn files(file: PathBuf) -> Result> { 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> { Json( recipes::load_all(&conn) .into_iter() .map(|r| RecipeObject::from(&conn, r)) .collect() ) } /// Delete a recipe given it's `id` #[get("/delete/")] pub fn delete_recipe(conn: CookbookDbConn, id: i32) -> Json { Json( recipes::delete(&conn, id) ) } #[derive(Serialize)] pub struct TemplateItems { key: (String, String), value: Option, } #[derive(Serialize)] pub struct TemplateObject { items: Vec } #[get("/solver/one")] pub fn one_solution(conn: CookbookDbConn) -> Json { 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(); 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]) .attach(cors) .launch(); Ok(()) }