Add some data access methods to Connection
This commit is contained in:
parent
4fbc599d07
commit
cdb883c3c4
6 changed files with 180 additions and 114 deletions
|
|
@ -1,64 +1,73 @@
|
||||||
-- Version 1 is the initial structure.
|
-- Version 1 is the initial structure.
|
||||||
CREATE TABLE Version (
|
CREATE TABLE [Version] (
|
||||||
id INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
version INTEGER NOT NULL UNIQUE,
|
[version] INTEGER NOT NULL UNIQUE,
|
||||||
datetime DATETIME
|
[datetime] DATETIME
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE User (
|
CREATE TABLE [User] (
|
||||||
id INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
email TEXT NOT NULL,
|
[email] TEXT NOT NULL,
|
||||||
password TEXT NOT NULL, -- Hashed and salted.
|
[password] TEXT NOT NULL, -- Hashed and salted.
|
||||||
name TEXT NOT NULL
|
[name] TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Recipe (
|
CREATE TABLE [Recipe] (
|
||||||
id INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
user_id INTEGER NOT NULL,
|
[user_id] INTEGER NOT NULL,
|
||||||
title TEXT NOT NULL,
|
[title] TEXT NOT NULL,
|
||||||
estimate_time INTEGER,
|
[estimate_time] INTEGER,
|
||||||
description DATETIME,
|
[description] TEXT,
|
||||||
|
|
||||||
FOREIGN KEY(user_id) REFERENCES User(id)
|
FOREIGN KEY([user_id]) REFERENCES [User]([id])
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Quantity (
|
CREATE TABLE [Quantity] (
|
||||||
id INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
value REAL,
|
[value] REAL,
|
||||||
unit TEXT
|
[unit] TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Ingredient (
|
CREATE TABLE [Ingredient] (
|
||||||
id INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
[name] TEXT NOT NULL,
|
||||||
quantity_id INTEGER,
|
[quantity_id] INTEGER,
|
||||||
input_step_id INTEGER NOT NULL,
|
[input_step_id] INTEGER NOT NULL,
|
||||||
|
|
||||||
FOREIGN KEY(quantity_id) REFERENCES Quantity(id),
|
FOREIGN KEY([quantity_id]) REFERENCES Quantity([id]),
|
||||||
FOREIGN KEY(input_step_id) REFERENCES Step(id)
|
FOREIGN KEY([input_step_id]) REFERENCES Step([id])
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE [Group] (
|
CREATE TABLE [Group] (
|
||||||
id INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
name TEXT
|
[order] INTEGER NOT NULL DEFAULT 0,
|
||||||
|
[recipe_id] INTEGER,
|
||||||
|
name TEXT,
|
||||||
|
|
||||||
|
FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id])
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Step (
|
CREATE INDEX [Group_order_index] ON [Group] ([order]);
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
action TEXT NOT NULL,
|
CREATE TABLE [Step] (
|
||||||
group_id INTEGER NOT NULL,
|
[id] INTEGER PRIMARY KEY,
|
||||||
|
[order] INTEGER NOT NULL DEFAULT 0,
|
||||||
|
[action] TEXT NOT NULL,
|
||||||
|
[group_id] INTEGER NOT NULL,
|
||||||
|
|
||||||
FOREIGN KEY(group_id) REFERENCES [Group](id)
|
FOREIGN KEY(group_id) REFERENCES [Group](id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IntermediateSubstance (
|
CREATE INDEX [Step_order_index] ON [Group] ([order]);
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
quantity_id INTEGER,
|
|
||||||
output_step_id INTEGER NOT NULL,
|
|
||||||
input_step_id INTEGER NOT NULL,
|
|
||||||
|
|
||||||
FOREIGN KEY(quantity_id) REFERENCES Quantity(id),
|
CREATE TABLE [IntermediateSubstance] (
|
||||||
FOREIGN KEY(output_step_id) REFERENCES Step(id),
|
[id] INTEGER PRIMARY KEY,
|
||||||
FOREIGN KEY(input_step_id) REFERENCES Step(id)
|
[name] TEXT NOT NULL,
|
||||||
|
[quantity_id] INTEGER,
|
||||||
|
[output_step_id] INTEGER NOT NULL,
|
||||||
|
[input_step_id] INTEGER NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY([quantity_id]) REFERENCES [Quantity]([id]),
|
||||||
|
FOREIGN KEY([output_step_id]) REFERENCES [Step]([id]),
|
||||||
|
FOREIGN KEY([input_step_id]) REFERENCES [Step]([id])
|
||||||
);
|
);
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::consts::SQL_FILENAME;
|
|
||||||
|
|
||||||
use super::consts;
|
|
||||||
|
|
||||||
use std::{fs::{self, File}, path::Path, io::Read};
|
use std::{fs::{self, File}, path::Path, io::Read};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
//use rusqlite::types::ToSql;
|
//use rusqlite::types::ToSql;
|
||||||
//use rusqlite::{Connection, Result, NO_PARAMS};
|
//use rusqlite::{Connection, Result, NO_PARAMS};
|
||||||
use r2d2::Pool;
|
use r2d2::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
|
|
||||||
|
use crate::consts;
|
||||||
|
use crate::model;
|
||||||
|
|
||||||
const CURRENT_DB_VERSION: u32 = 1;
|
const CURRENT_DB_VERSION: u32 = 1;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -19,30 +19,28 @@ pub enum DBError {
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Connection {
|
impl From<rusqlite::Error> for DBError {
|
||||||
//con: rusqlite::Connection
|
|
||||||
pool: Pool<SqliteConnectionManager>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Recipe {
|
|
||||||
pub title: String,
|
|
||||||
pub id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::From<rusqlite::Error> for DBError {
|
|
||||||
fn from(error: rusqlite::Error) -> Self {
|
fn from(error: rusqlite::Error) -> Self {
|
||||||
DBError::SqliteError(error)
|
DBError::SqliteError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<r2d2::Error> for DBError {
|
impl From<r2d2::Error> for DBError {
|
||||||
fn from(error: r2d2::Error) -> Self {
|
fn from(error: r2d2::Error) -> Self {
|
||||||
DBError::R2d2Error(error)
|
DBError::R2d2Error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, DBError>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Connection {
|
||||||
|
//con: rusqlite::Connection
|
||||||
|
pool: Pool<SqliteConnectionManager>
|
||||||
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
pub fn new() -> Result<Connection, DBError> {
|
pub fn new() -> Result<Connection> {
|
||||||
|
|
||||||
let data_dir = Path::new(consts::DB_DIRECTORY);
|
let data_dir = Path::new(consts::DB_DIRECTORY);
|
||||||
|
|
||||||
|
|
@ -62,11 +60,7 @@ impl Connection {
|
||||||
* Called after the connection has been established for creating or updating the database.
|
* Called after the connection has been established for creating or updating the database.
|
||||||
* The 'Version' table tracks the current state of the database.
|
* The 'Version' table tracks the current state of the database.
|
||||||
*/
|
*/
|
||||||
fn create_or_update(self: &Self) -> Result<(), DBError> {
|
fn create_or_update(&self) -> Result<()> {
|
||||||
// let connection = Connection::new();
|
|
||||||
// let mut stmt = connection.sqlite_con.prepare("SELECT * FROM versions ORDER BY date").unwrap();
|
|
||||||
// let mut stmt = connection.sqlite_con.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='versions'").unwrap();
|
|
||||||
|
|
||||||
// Check the Database version.
|
// Check the Database version.
|
||||||
let mut con = self.pool.get()?;
|
let mut con = self.pool.get()?;
|
||||||
let tx = con.transaction()?;
|
let tx = con.transaction()?;
|
||||||
|
|
@ -78,7 +72,7 @@ impl Connection {
|
||||||
[],
|
[],
|
||||||
|row| row.get::<usize, String>(0)
|
|row| row.get::<usize, String>(0)
|
||||||
) {
|
) {
|
||||||
Ok(_) => tx.query_row("SELECT [version] FROM [Version]", [], |row| row.get(0)).unwrap_or_default(),
|
Ok(_) => tx.query_row("SELECT [version] FROM [Version] ORDER BY [id] DESC", [], |row| row.get(0)).unwrap_or_default(),
|
||||||
Err(_) => 0
|
Err(_) => 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -92,14 +86,18 @@ impl Connection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_to_next_version(current_version: u32, tx: &rusqlite::Transaction) -> Result<bool, DBError> {
|
fn update_to_next_version(current_version: u32, tx: &rusqlite::Transaction) -> Result<bool> {
|
||||||
let next_version = current_version + 1;
|
let next_version = current_version + 1;
|
||||||
|
|
||||||
if next_version <= CURRENT_DB_VERSION {
|
if next_version <= CURRENT_DB_VERSION {
|
||||||
println!("Update to version {}...", next_version);
|
println!("Update to version {}...", next_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ok(updated: bool) -> Result<bool, DBError> {
|
fn update_version(to_version: u32, tx: &rusqlite::Transaction) -> Result<()> {
|
||||||
|
tx.execute("INSERT INTO [Version] ([version], [datetime]) VALUES (?1, datetime('now'))", [to_version]).map(|_| ()).map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ok(updated: bool) -> Result<bool> {
|
||||||
if updated {
|
if updated {
|
||||||
println!("Version updated");
|
println!("Version updated");
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +107,7 @@ impl Connection {
|
||||||
match next_version {
|
match next_version {
|
||||||
1 => {
|
1 => {
|
||||||
tx.execute_batch(&load_sql_file(next_version)?)?;
|
tx.execute_batch(&load_sql_file(next_version)?)?;
|
||||||
|
update_version(next_version, tx)?;
|
||||||
|
|
||||||
ok(true)
|
ok(true)
|
||||||
}
|
}
|
||||||
|
|
@ -122,13 +121,36 @@ impl Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_recipes() {
|
pub fn get_all_recipe_titles(&self) -> Result<Vec<(i32, String)>> {
|
||||||
|
let con = self.pool.get()?;
|
||||||
|
let mut stmt = con.prepare("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")?;
|
||||||
|
let titles =
|
||||||
|
stmt.query_map([], |row| {
|
||||||
|
Ok((row.get(0)?, row.get(1)?))
|
||||||
|
})?.map(|r| r.unwrap()).collect_vec(); // TODO: remove unwrap.
|
||||||
|
Ok(titles)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_recipes(&self) -> Result<Vec<model::Recipe>> {
|
||||||
|
let con = self.pool.get()?;
|
||||||
|
let mut stmt = con.prepare("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")?;
|
||||||
|
let recipes =
|
||||||
|
stmt.query_map([], |row| {
|
||||||
|
Ok(model::Recipe::new(row.get(0)?, row.get(1)?))
|
||||||
|
})?.map(|r| r.unwrap()).collect_vec(); // TODO: remove unwrap.
|
||||||
|
Ok(recipes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_recipe(&self, id: i32) -> Result<model::Recipe> {
|
||||||
|
let con = self.pool.get()?;
|
||||||
|
con.query_row("SELECT [id], [title] FROM [Recipe] WHERE [id] = ?1", [id], |row| {
|
||||||
|
Ok(model::Recipe::new(row.get(0)?, row.get(1)?))
|
||||||
|
}).map_err(DBError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_sql_file(version: u32) -> Result<String, DBError> {
|
fn load_sql_file(version: u32) -> Result<String> {
|
||||||
let sql_file = SQL_FILENAME.replace("{VERSION}", &version.to_string());
|
let sql_file = consts::SQL_FILENAME.replace("{VERSION}", &version.to_string());
|
||||||
let mut file = File::open(&sql_file).map_err(|err| DBError::Other(format!("Cannot open SQL file ({}): {}", &sql_file, err.to_string())))?;
|
let mut file = File::open(&sql_file).map_err(|err| DBError::Other(format!("Cannot open SQL file ({}): {}", &sql_file, err.to_string())))?;
|
||||||
let mut sql = String::new();
|
let mut sql = String::new();
|
||||||
file.read_to_string(&mut sql).map_err(|err| DBError::Other(format!("Cannot read SQL file ({}) : {}", &sql_file, err.to_string())))?;
|
file.read_to_string(&mut sql).map_err(|err| DBError::Other(format!("Cannot read SQL file ({}) : {}", &sql_file, err.to_string())))?;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
use std::io::prelude::*;
|
use std::fs::File;
|
||||||
use std::{fs::File, env::args};
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use actix_files as fs;
|
use actix_files as fs;
|
||||||
use actix_web::{get, web, Responder, middleware, App, HttpServer, HttpResponse, HttpRequest, web::Query};
|
use actix_web::{get, web, Responder, middleware, App, HttpServer, HttpRequest};
|
||||||
|
|
||||||
use askama_actix::Template;
|
use askama_actix::Template;
|
||||||
|
use clap::Parser;
|
||||||
use ron::de::from_reader;
|
use ron::de::from_reader;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
mod consts;
|
mod consts;
|
||||||
mod model;
|
mod model;
|
||||||
mod db;
|
mod db;
|
||||||
|
|
@ -17,14 +15,14 @@ mod db;
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "home.html")]
|
#[template(path = "home.html")]
|
||||||
struct HomeTemplate {
|
struct HomeTemplate {
|
||||||
recipes: Vec<db::Recipe>
|
recipes: Vec<(i32, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "view_recipe.html")]
|
#[template(path = "view_recipe.html")]
|
||||||
struct ViewRecipeTemplate {
|
struct ViewRecipeTemplate {
|
||||||
recipes: Vec<db::Recipe>,
|
recipes: Vec<(i32, String)>,
|
||||||
current_recipe: db::Recipe
|
current_recipe: model::Recipe,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -33,13 +31,16 @@ pub struct Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn home_page(req: HttpRequest) -> impl Responder {
|
async fn home_page(req: HttpRequest, connection: web::Data<db::Connection>) -> impl Responder {
|
||||||
HomeTemplate { recipes: vec![ db::Recipe { title: String::from("Saumon en croûte feuilletée"), id: 1 }, db::Recipe { title: String::from("Croissant au jambon"), id: 2 } ] }
|
HomeTemplate { recipes: connection.get_all_recipe_titles().unwrap() } // TODO: unwrap.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/recipe/view/{id}")]
|
#[get("/recipe/view/{id}")]
|
||||||
async fn view_page(req: HttpRequest, path: web::Path<(i32,)>) -> impl Responder {
|
async fn view_recipe(req: HttpRequest, path: web::Path<(i32,)>, connection: web::Data<db::Connection>) -> impl Responder {
|
||||||
ViewRecipeTemplate { recipes: vec![ db::Recipe { title: String::from("Saumon en croûte feuilletée"), id: 1 }, db::Recipe { title: String::from("Croissant au jambon"), id: 2 } ], current_recipe: db::Recipe { title: String::from("Saumon en croûte feuilletée"), id: 1 } }
|
ViewRecipeTemplate {
|
||||||
|
recipes: connection.get_all_recipe_titles().unwrap(),
|
||||||
|
current_recipe: connection.get_recipe(path.0).unwrap(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -72,18 +73,19 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
println!("Configuration: {:?}", config);
|
println!("Configuration: {:?}", config);
|
||||||
|
|
||||||
// let database_connection = db::create_or_update();
|
let db_connection = web::Data::new(db::Connection::new().unwrap()); // TODO: remove unwrap.
|
||||||
|
|
||||||
std::env::set_var("RUST_LOG", "actix_web=info");
|
std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
|
||||||
let mut server =
|
let mut server =
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
|| {
|
move || {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.wrap(middleware::Compress::default())
|
.wrap(middleware::Compress::default())
|
||||||
|
.app_data(db_connection.clone())
|
||||||
.service(home_page)
|
.service(home_page)
|
||||||
.service(view_page)
|
.service(view_recipe)
|
||||||
.service(fs::Files::new("/static", "static").show_files_listing())
|
.service(fs::Files::new("/static", "static").show_files_listing())
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -93,7 +95,27 @@ async fn main() -> std::io::Result<()> {
|
||||||
server.run().await
|
server.run().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(long)]
|
||||||
|
test: bool
|
||||||
|
}
|
||||||
|
|
||||||
fn process_args() -> bool {
|
fn process_args() -> bool {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
if args.test {
|
||||||
|
if let Err(error) = db::Connection::new() {
|
||||||
|
println!("Error: {:?}", error)
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
fn print_usage() {
|
fn print_usage() {
|
||||||
println!("Usage:");
|
println!("Usage:");
|
||||||
println!(" {} [--help] [--test]", get_exe_name());
|
println!(" {} [--help] [--test]", get_exe_name());
|
||||||
|
|
@ -111,6 +133,6 @@ fn process_args() -> bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,57 @@
|
||||||
struct Recipe {
|
pub struct Recipe {
|
||||||
title: String,
|
pub id: i32,
|
||||||
estimate_time: Option<i32>, // [min].
|
pub title: String,
|
||||||
difficulty: Option<Difficulty>,
|
pub estimate_time: Option<i32>, // [min].
|
||||||
|
pub difficulty: Option<Difficulty>,
|
||||||
|
|
||||||
//ingredients: Vec<Ingredient>, // For four people.
|
//ingredients: Vec<Ingredient>, // For four people.
|
||||||
process: Vec<Group>,
|
pub process: Vec<Group>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Ingredient {
|
impl Recipe {
|
||||||
quantity: Option<Quantity>,
|
pub fn new(id: i32, title: String) -> Recipe {
|
||||||
name: String,
|
Recipe {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
estimate_time: None,
|
||||||
|
difficulty: None,
|
||||||
|
process: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Quantity {
|
pub struct Ingredient {
|
||||||
value: f32,
|
pub quantity: Option<Quantity>,
|
||||||
unit: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Group {
|
pub struct Quantity {
|
||||||
name: Option<String>,
|
pub value: f32,
|
||||||
steps: Vec<Step>,
|
pub unit: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Step {
|
pub struct Group {
|
||||||
action: String,
|
pub name: Option<String>,
|
||||||
input: Vec<StepInput>,
|
pub steps: Vec<Step>,
|
||||||
output: Vec<IntermediateSubstance>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IntermediateSubstance {
|
pub struct Step {
|
||||||
name: String,
|
pub action: String,
|
||||||
quantity: Option<Quantity>,
|
pub input: Vec<StepInput>,
|
||||||
|
pub output: Vec<IntermediateSubstance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StepInput {
|
pub struct IntermediateSubstance {
|
||||||
|
pub name: String,
|
||||||
|
pub quantity: Option<Quantity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum StepInput {
|
||||||
Ingredient(Ingredient),
|
Ingredient(Ingredient),
|
||||||
IntermediateSubstance(IntermediateSubstance),
|
IntermediateSubstance(IntermediateSubstance),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Difficulty {
|
pub enum Difficulty {
|
||||||
Unknown,
|
Unknown,
|
||||||
Easy,
|
Easy,
|
||||||
Medium,
|
Medium,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
{% block main_container %}
|
{% block main_container %}
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<ul>
|
<ul>
|
||||||
{% for recipe in recipes %}
|
{% for (id, title) in recipes %}
|
||||||
<li><a href="/recipe/view/{{ recipe.id }}">{{ recipe.title|escape }}</a></li>
|
<li><a href="/recipe/view/{{ id }}">{{ title|escape }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
*** HOME - PUT SOMETHING HERE ***
|
HOME: TODO
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue