diff options
-rw-r--r-- | Cargo.toml | 10 | ||||
-rw-r--r-- | artifacts/ui-demo.html | 119 | ||||
-rw-r--r-- | src/config.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 13 | ||||
-rw-r--r-- | src/repo/constants.rs | 9 | ||||
-rw-r--r-- | src/repo/mod.rs | 28 | ||||
-rw-r--r-- | src/routes/mod.rs (renamed from src/handlers/mod.rs) | 45 | ||||
-rw-r--r-- | src/templates/base.hbs | 155 | ||||
-rw-r--r-- | src/templates/index.hbs | 74 | ||||
-rw-r--r-- | src/templates/index.html | 85 | ||||
-rw-r--r-- | src/templates/login.hbs | 7 | ||||
-rw-r--r-- | src/templates/login.html | 6 | ||||
-rw-r--r-- | src/templates/mod.rs | 25 | ||||
-rw-r--r-- | src/templates/suggestions.hbs | 9 |
14 files changed, 394 insertions, 193 deletions
@@ -5,14 +5,14 @@ authors = ["Adam T. Carpenter <atc@53hor.net>"] edition = "2018" [dependencies] -bincode = "1.3" +bincode = "1" env_logger = "0.8" -handlebars = "3.5" +handlebars = "3" hyper = { version = "0.14", default-features = false, features = ["full"] } hyper-rustls = "0.22" log = "0.4" -once_cell = "1.7" -serde = { version = "1.0", features = ["derive"]} +once_cell = "1" +serde = { version = "1", features = ["derive"]} serde_urlencoded = "0.7" sled = "0.34" -tokio = { version = "1.2", default-features = false, features = ["full"] } +tokio = { version = "1", default-features = false, features = ["full"] } diff --git a/artifacts/ui-demo.html b/artifacts/ui-demo.html index 6fe3a18..09dc9cb 100644 --- a/artifacts/ui-demo.html +++ b/artifacts/ui-demo.html @@ -11,6 +11,10 @@ padding: 0; } + .model::before { + content: "➥"; + } + .topbar { background-color: var(--balboa); box-shadow: 0 0 0.5em black; @@ -31,6 +35,10 @@ padding: 14px 14px; } + .topbar li.right { + float: right; + } + .topbar li a { color: white; text-decoration: none; @@ -42,38 +50,46 @@ z-index: -1; position: fixed; top: 3em; - left: -20%; + left: 0; width: 20%; height: 100%; transition: 0.25s; } - .sidebar ul { - list-style-type: none; + .sidebar button { + display: block; + background: none; + border: 0 none; + cursor: pointer; + color: white; } - .sidebar a { - color: white; - text-decoration: none; + .model { + margin-left: 20px; } - #menu:target .sidebar { - left: 0; + .model::before { + content: "➥"; } - .menuToggle a.closeMenu { - display: none; + #hidden:target .sidebar { + left: -20%; } - #menu:target .menuToggle a.closeMenu { - display: block; + + .menuToggle a.openMenu { + display: none; } - #menu:target .menuToggle a.openMenu { + #hidden:target .menuToggle a.closeMenu { display: none; } - #menu:target section { - margin-left: 20%; + #hidden:target .menuToggle a.openMenu { + display: block; + } + + #hidden:target section { + margin-left: 0; } .catalogTable { @@ -105,6 +121,7 @@ } section { + margin-left: 20%; transition: margin-left 0.25s; padding-top: 2em; padding-left: 2em; @@ -112,12 +129,12 @@ } </style> </head> - <body id="menu"> + <body id="hidden"> <nav class="topbar"> <ul> <li> <div class="menuToggle"> - <a class="openMenu" href="#menu" + <a class="openMenu" href="#" ><span ><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"> <path @@ -125,7 +142,7 @@ d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" /></svg></span ></a> - <a class="closeMenu" href="#" + <a class="closeMenu" href="#hidden" ><span ><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"> <path @@ -135,57 +152,57 @@ ></a> </div> </li> - <li><a href="#menu">Catalog</a></li> - <li><a href="#">Parts List</a></li> - <li><a href="#">Cars List</a></li> + <li><a href="#">Index</a></li> + <li><a href="#">Parts</a></li> + <li><a href="#">Cars</a></li> <li><a href="#">Suggestions</a></li> <li><a href="#">About</a></li> - <li><a href="#">Login</a></li> + <li class="right"><a href="#">Login</a></li> </ul> </nav> <nav class="sidebar"> - <ul> - <li> - <a href="#menu">Hudson</a> - <ul> - <li><a href="#menu">Hornet</a></li> - </ul> - </li> - <li><a href="#menu">Essex</a></li> - <li><a href="#menu">Terraplane</a></li> - </ul> + {{#each makes}} + <form action="/parts"> + {{#if (eq this ../selected_make)}} + <button disabled class="make"> + {{ this }} + </button> + <input type="hidden" name="make" value="{{ ../selected_make }}" /> + {{#each ../models}} + {{#if (eq this ../../selected_model)}} + <button disabled class="model">{{ this }}</button> + {{else}} + <button class="model" name="model" value="{{ this }}"> + {{ this }} + </button> + {{/if}} + {{else}} + <button disabled class="model">(No models found)</button> + {{/each}} + {{else}} + <button class="make" name="make" value="{{ this }}">{{ this }}</button> + {{/if}} + </form> + {{else}} + No makes found. + {{/each}} </nav> <section> <form> - <input autofocus type="search" placeholder="hit enter to search!" /> + <input autofocus type="search" placeholder="hit enter to search" /> </form> </section> <section> <table class="catalogTable"> - <thead> - <tr> - <th>Make</th> - <th>Model</th> - <th>Engine CID</th> - <th>Year</th> - </tr> - </thead> <tbody> + {{#each parts}} <tr> - <td>Hudson</td> - <td>Wasp</td> - <td>262ci</td> - <td>1952</td> - </tr> - <tr> - <td>Hudson</td> - <td>Hornet</td> - <td>308ci</td> - <td>1953</td> + <td>{{ this }}</td> </tr> + {{/each}} </tbody> </table> </section> diff --git a/src/config.rs b/src/config.rs index 7798967..b3c9715 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ use once_cell::sync::Lazy; use std::{env::var, error::Error, net::SocketAddr}; -pub static INSTANCE: Lazy<AppConfig> = +pub static CONFIG_INSTANCE: Lazy<AppConfig> = Lazy::new(|| AppConfig::from_env().expect("Error loading config")); #[derive(Clone, Debug)] diff --git a/src/main.rs b/src/main.rs index 1947ff8..4a5a8b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,27 @@ +use crate::config::CONFIG_INSTANCE; +use crate::error::TwinHError; use hyper::{ service::{make_service_fn, service_fn}, Server, }; -use std::convert::Infallible; // TODO: mod config; mod error; -mod handlers; mod models; mod repo; +mod routes; mod templates; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { - let addr = config::INSTANCE.addr; + let addr = CONFIG_INSTANCE.addr; - let make_svc = make_service_fn(move |_conn| async { - Ok::<_, Infallible>(service_fn(|req| handlers::router(req))) - }); + let make_svc = + make_service_fn(move |_conn| async { Ok::<_, TwinHError>(service_fn(routes::router)) }); let server = Server::bind(&addr).serve(make_svc); let graceful = server.with_graceful_shutdown(shutdown_signal()); - // run until shutdown signal if let Err(e) = graceful.await { eprintln!("server error: {}", e); } diff --git a/src/repo/constants.rs b/src/repo/constants.rs index e168a77..6da641f 100644 --- a/src/repo/constants.rs +++ b/src/repo/constants.rs @@ -1,11 +1,2 @@ -use once_cell::sync::Lazy; -use sled::{Config, Db}; - pub const PARTS_TREE: &str = "parts"; pub const CARS_TREE: &str = "cars"; -pub static REPO_INSTANCE: Lazy<Db> = Lazy::new(|| { - Config::default() - .path("/tmp/twinh") - .open() - .expect("Couldn't open DB!") -}); diff --git a/src/repo/mod.rs b/src/repo/mod.rs index d4c5e44..f0bb95c 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,16 +1,36 @@ +use crate::config::CONFIG_INSTANCE; use crate::error::TwinHError; use crate::models::Car; use crate::models::Part; use bincode::deserialize; use constants::*; +use once_cell::sync::Lazy; +use sled::{Config, Db}; mod constants; +static REPO_INSTANCE: Lazy<Db> = Lazy::new(|| { + Config::default() + .path(&CONFIG_INSTANCE.db_uri) + .temporary(true) + .open() + .expect("Couldn't open DB!") +}); + +pub fn create_demo_db() -> Result<(), TwinHError> { + let db = sled::Config::default() + .path(&CONFIG_INSTANCE.db_uri) + .create_new(true) + .open()?; + let cars_tree = db.open_tree(CARS_TREE)?; + Ok(()) +} + pub fn create_new_db() -> Result<(), TwinHError> { - let config = sled::Config::default() - .path("/var/db/twinh") - .create_new(true); - let db = config.open()?; + sled::Config::default() + .path(&CONFIG_INSTANCE.db_uri) + .create_new(true) + .open()?; Ok(()) } diff --git a/src/handlers/mod.rs b/src/routes/mod.rs index 3ad7f49..1866e49 100644 --- a/src/handlers/mod.rs +++ b/src/routes/mod.rs @@ -1,19 +1,20 @@ +use crate::error::TwinHError; +use crate::repo; use crate::templates; -use handlebars::Handlebars; use hyper::body::HttpBody as _; use hyper::Method; use hyper::StatusCode; use hyper::{Body, Request, Response}; use serde::{Deserialize, Serialize}; -use sled::Db; -use std::convert::Infallible; // TODO: -pub async fn router(req: Request<Body>) -> Result<Response<Body>, Infallible> { +pub async fn router(req: Request<Body>) -> Result<Response<Body>, TwinHError> { match (req.method(), req.uri().path()) { - (&Method::GET, "/") | (&Method::GET, "/index.html") => index(req).await, - (&Method::GET, "/login") | (&Method::GET, "/login/index.html") => login().await, + (&Method::GET, "/") => index(req).await, + (&Method::GET, "/cars") => cars(req).await, + (&Method::GET, "/login") => login().await, (&Method::GET, "/login/google") => login_google().await, (&Method::GET, "/login/google/code") => login_google_code(req).await, + (&Method::GET, "/suggestions") => suggestions().await, _ => Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body("Not found.".into()) @@ -21,11 +22,17 @@ pub async fn router(req: Request<Body>) -> Result<Response<Body>, Infallible> { } } -async fn login() -> Result<Response<Body>, Infallible> { - Ok(Response::new(templates::LOGIN_T.into())) +async fn suggestions() -> Result<Response<Body>, TwinHError> { + todo!() } -async fn login_google() -> Result<Response<Body>, Infallible> { +async fn login() -> Result<Response<Body>, TwinHError> { + Ok(Response::new( + templates::REGISTRY.render("login", &"").unwrap().into(), + )) +} + +async fn login_google() -> Result<Response<Body>, TwinHError> { let uri = hyper::Uri::builder() .scheme("https") .authority("accounts.google.com") @@ -69,7 +76,7 @@ impl Default for AuthRequest { } } -async fn login_google_code(req: Request<Body>) -> Result<Response<Body>, Infallible> { +async fn login_google_code(req: Request<Body>) -> Result<Response<Body>, TwinHError> { let query = req.uri().query().unwrap_or_default(); let query = serde_urlencoded::de::from_str::<GoogleOAuthQuery>(query).unwrap(); if query.state != "BLARGH" { @@ -116,12 +123,14 @@ pub struct AccessToken { token_type: String, } -async fn index(req: Request<Body>) -> Result<Response<Body>, Infallible> { +async fn cars(req: Request<Body>) -> Result<Response<Body>, TwinHError> { + let cars = repo::get_all_cars().unwrap(); + todo!() +} + +async fn index(req: Request<Body>) -> Result<Response<Body>, TwinHError> { let query = req.uri().query().unwrap_or_default(); let filter = serde_urlencoded::de::from_str::<PartsQuery>(query).unwrap(); - let mut reg = Handlebars::new(); - reg.register_template_string("index", templates::INDEX_T) - .unwrap(); let mut data = PartsData::default(); data.makes = Some(vec!["Hudson".into(), "Essex".into(), "Terraplane".into()]); @@ -140,8 +149,12 @@ async fn index(req: Request<Body>) -> Result<Response<Body>, Infallible> { data.selected_model = Some(model); } - let result = reg.render("index", &data).unwrap(); - Ok(Response::new(result.into())) + let body = templates::REGISTRY.render("index", &data).unwrap(); + let resp = Response::builder() + .header(hyper::header::CONTENT_TYPE, "text/html; charset=utf-8") + .body(body.into()) + .unwrap(); + Ok(resp) } #[derive(Debug, Deserialize)] diff --git a/src/templates/base.hbs b/src/templates/base.hbs new file mode 100644 index 0000000..df3ba9d --- /dev/null +++ b/src/templates/base.hbs @@ -0,0 +1,155 @@ +<!DOCTYPE html> +<html> + +<head> + <style> + :root { + --balboa: #195970; + } + + body { + margin: 0; + padding: 0; + font-size: 1em; + } + + .model::before { + content: "⤷"; + } + + .topbar { + background-color: var(--balboa); + box-shadow: 0 0 0.5em black; + position: relative; + height: 3em; + z-index: 2; + } + + .topbar ul { + margin: 0; + padding: 0; + overflow: hidden; + } + + .topbar li { + float: left; + display: block; + padding: 14px 14px; + } + + .topbar li.right { + float: right; + } + + .topbar li a { + color: white; + text-decoration: none; + } + + .sidebar { + background-color: var(--balboa); + box-shadow: inset -0.5em 0 0.5em -0.5em black; + z-index: 1; + position: fixed; + top: 3em; + left: 0; + width: 20%; + height: 100%; + transition: 0.25s; + } + + .sidebar form { + padding: 10px 10px; + } + + .sidebar button { + font-size: 1em; + display: block; + background: none; + border: 0 none; + cursor: pointer; + color: white; + } + + .model { + margin-left: 20px; + } + + #hidden:target .sidebar { + left: -20%; + } + + .menuToggle a.openMenu { + display: none; + } + + #hidden:target .menuToggle a.closeMenu { + display: none; + } + + #hidden:target .menuToggle a.openMenu { + display: block; + } + + #hidden:target section { + margin-left: 0; + } + + .catalogTable { + width: 100%; + border-collapse: collapse; + } + + .catalogTable td, + .catalogTable th { + padding: 0.5em; + } + + .catalogTable tr:nth-child(even) { + background-color: #f2f2f2; + } + + .catalogTable tr:hover { + background-color: #ddd; + } + + .catalogTable th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; + background-color: var(--balboa); + color: white; + } + + input[type="search"] { + width: 100%; + } + + section { + /* TODO: need to make this not applicable to other templates that don't have sidebar */ + margin-left: 20%; + transition: margin-left 0.25s; + padding-top: 2em; + padding-left: 2em; + padding-right: 2em; + } + </style> +</head> + +<body id="hidden"> + <nav class="topbar"> + <ul> + {{~> navItem}} + <li><a href="/">Index</a></li> + <li><a href="#">Parts</a></li> + <li><a href="#">Cars</a></li> + <li><a href="#">Suggestions</a></li> + <li><a href="#">About</a></li> + <li class="right"><a href="/login">Login</a></li> + </ul> + </nav> + + {{~> body}} +</body> + +</html> diff --git a/src/templates/index.hbs b/src/templates/index.hbs new file mode 100644 index 0000000..6bd539b --- /dev/null +++ b/src/templates/index.hbs @@ -0,0 +1,74 @@ +{{#> base}} +{{#*inline "navItem"}} +<li> + <div class="menuToggle"> + <a class="openMenu" href="#"><span> + <svg style="width: 24px; height: 24px" viewBox="0 0 24 24"> + <path fill="currentColor" d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" /> + </svg> + </span></a> + <a class="closeMenu" href="#hidden"><span> + <svg style="width: 24px; height: 24px" viewBox="0 0 24 24"> + <path fill="currentColor" + d="M21,15.61L19.59,17L14.58,12L19.59,7L21,8.39L17.44,12L21,15.61M3,6H16V8H3V6M3,13V11H13V13H3M3,18V16H16V18H3Z" /> + </svg> + </span></a> + </div> +</li> +{{/inline}} +{{#*inline "body"}} +<nav class="sidebar"> + {{#each makes}} + <form action="/"> + {{#if (eq this ../selected_make)}} + <button disabled class="make"> + {{ this }} + </button> + <input type="hidden" name="make" value="{{ ../selected_make }}" /> + {{#each ../models}} + {{#if (eq this ../../selected_model)}} + <button disabled class="model">{{~ this ~}}</button> + {{else}} + <button class="model" name="model" value="{{ this }}"> + {{~ this ~}} + </button> + {{/if}} + {{else}} + <button disabled class="model">(No models found)</button> + {{/each}} + {{else}} + <button class="make" name="make" value="{{ this }}"> + {{~ this ~}} + </button> + {{/if}} + </form> + {{else}} + <button disabled class="make">(No makes found)</button> + {{/each}} +</nav> + +<section> + <form> + {{#if selected_make}} + <input type="hidden" name="make" value="{{ selected_make }}" /> + {{/if}} + {{#if selected_model}} + <input type="hidden" name="model" value="{{ selected_model }}" /> + {{/if}} + <input autofocus type="search" name="search" placeholder="hit enter to search" /> + </form> +</section> + +<section> + <table class="catalogTable"> + <tbody> + {{#each parts}} + <tr> + <td>{{ this }}</td> + </tr> + {{/each}} + </tbody> + </table> +</section> +{{/inline}} +{{/base}} diff --git a/src/templates/index.html b/src/templates/index.html deleted file mode 100644 index 4663f5d..0000000 --- a/src/templates/index.html +++ /dev/null @@ -1,85 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta charset="UTF-8" /> - - <style> - .make { - display: block; - background: none; - border: 0 none; - cursor: pointer; - } - - .model { - display: block; - background: none; - border: 0 none; - cursor: pointer; - margin-left: 20px; - } - - .sidebar { - width: 300px; - height: 100%; - position: fixed; - z-index: 1; - } - - .main { - margin-left: 300px; - height: 100%; - } - </style> - </head> - <body> - <div class="sidebar"> - <h1>Parts Catalog</h1> - {{#each makes}} - <form action="/parts"> - {{#if (eq this ../selected_make)}} - <button disabled class="make"> - {{ this }} - </button> - <input type="hidden" name="make" value="{{ ../selected_make }}" /> - {{#each ../models}} - {{#if (eq this ../../selected_model)}} - <button disabled class="model">{{ this }}</button> - {{else}} - <button class="model" name="model" value="{{ this }}"> - {{ this }} - </button> - {{/if}} - {{else}} - <button disabled class="model">(No models found)</button> - {{/each}} - {{else}} - <button class="make" name="make" value="{{ this }}">{{ this }}</button> - {{/if}} - </form> - {{else}} - No makes found. - {{/each}} - </div> - - <div class="main"> - <form action="/parts"> - <input type="hidden" name="make" value="{{ selected_make }}" /> - <input type="hidden" name="model" value="{{ selected_model }}" /> - <input - type="search" - name="search" - placeholder="Part #, Name, Source, etc." - /> - <button>Search</button> - </form> - <table> - {{#each parts}} - <tr> - <td>{{ this }}</td> - </tr> - {{/each}} - </table> - </div> - </body> -</html> diff --git a/src/templates/login.hbs b/src/templates/login.hbs new file mode 100644 index 0000000..84b354b --- /dev/null +++ b/src/templates/login.hbs @@ -0,0 +1,7 @@ +{{#> base}} +{{#*inline "body"}} +<body> + <a href="/login/google">Login with Google</a> +</body> +{{/inline}} +{{/base}} diff --git a/src/templates/login.html b/src/templates/login.html deleted file mode 100644 index 9329a22..0000000 --- a/src/templates/login.html +++ /dev/null @@ -1,6 +0,0 @@ -<!DOCTYPE html> -<html> - <body> - <a href="/login/google">Login with Google</a> - </body> -</html> diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 3a6eb2b..534ada0 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -1,9 +1,16 @@ -pub static INDEX_T: &str = include_str!("index.html"); -pub static LOGIN_T: &str = include_str!("login.html"); -pub const CATALOG_L: &str = "/#menu"; -pub const PARTS_L: &str = "/parts"; -pub const PARTS_CSV_L: &str = "/parts.csv"; -pub const CARS_L: &str = "/cars"; -pub const CARS_CSV_L: &str = "/cars.csv"; -pub const SUGGESTIONS_L: &str = "/suggestions"; -pub const LOGIN_L: &str = "/login"; +use handlebars::Handlebars; +use once_cell::sync::Lazy; + +pub static REGISTRY: Lazy<Handlebars> = Lazy::new(|| { + let mut handlebars = Handlebars::new(); + handlebars + .register_template_string("index", include_str!("index.hbs")) + .unwrap(); + handlebars + .register_template_string("login", include_str!("login.hbs")) + .unwrap(); + handlebars + .register_template_string("base", include_str!("base.hbs")) + .unwrap(); + handlebars +}); diff --git a/src/templates/suggestions.hbs b/src/templates/suggestions.hbs new file mode 100644 index 0000000..f979be7 --- /dev/null +++ b/src/templates/suggestions.hbs @@ -0,0 +1,9 @@ +{{#> base}} +{{#*inline "body"}} +<section> + <form> + <!-- input for new car or part --> + </form> +</section> +{{/inline}} +{{/base}} |