From ba49aa806a48839b61fb261f7ccd007a507d8d50 Mon Sep 17 00:00:00 2001 From: 53hornet Date: Sat, 22 Jan 2022 08:45:24 -0500 Subject: feat: experimental cgi fork --- .gitignore | 20 +---- Cargo.toml | 12 +-- Makefile | 5 ++ TODO | 0 src/config.rs | 73 +++++++--------- src/error.rs | 51 ----------- src/import/mod.rs | 1 - src/main.rs | 80 ++++++++--------- src/models/mod.rs | 63 -------------- src/repo/constants.rs | 2 - src/repo/mod.rs | 107 ----------------------- src/routes/mod.rs | 194 ------------------------------------------ src/templates/base.hbs | 10 +-- src/templates/favicon.svg | 5 -- src/templates/index.hbs | 7 +- src/templates/login.hbs | 5 -- src/templates/mod.rs | 25 ++---- src/templates/suggestions.hbs | 2 + twinh.1.txt | 0 twinh.css | 0 twinhrc.5.txt | 12 +++ 21 files changed, 112 insertions(+), 562 deletions(-) create mode 100644 Makefile create mode 100644 TODO delete mode 100644 src/error.rs delete mode 100644 src/import/mod.rs delete mode 100644 src/models/mod.rs delete mode 100644 src/repo/constants.rs delete mode 100644 src/repo/mod.rs delete mode 100644 src/routes/mod.rs delete mode 100644 src/templates/favicon.svg delete mode 100644 src/templates/login.hbs create mode 100644 twinh.1.txt create mode 100644 twinh.css create mode 100644 twinhrc.5.txt diff --git a/.gitignore b/.gitignore index f504fd3..79382fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,4 @@ -# ---> Rust -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt **/*.rs.bk - - - -#Added by cargo - +/run /target - -.env +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 7deeff7..b376c93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,18 +3,14 @@ name = "twinh" version = "0.1.0" authors = ["Adam T. Carpenter "] description = "Twin H-Power: A dead simple classic car parts catalog and cross-reference tool" -edition = "2018" +edition = "2021" [dependencies] -bincode = "1" env_logger = { version = "0.8", default-features = false } handlebars = "3" -hyper = { version = "0.14", default-features = false, features = ["server", "client", "http1", "http2"] } -hyper-rustls = "0.22" log = "0.4" -lazy_static = "1.4" +rusqlite = "0.26.3" serde = { version = "1", features = ["derive"]} serde_urlencoded = "0.7" -sled = "0.34" -structopt = { version = "0.3", default-features = false } -tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "signal"] } +totp-lite = "1" +anyhow = "1" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5763e77 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ + +all: + cargo build + mkdir -p run + mv target/debug/twinh run/twinh.cgi diff --git a/TODO b/TODO new file mode 100644 index 0000000..e69de29 diff --git a/src/config.rs b/src/config.rs index c4396d4..f7d391f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,48 +1,37 @@ -use std::{net::SocketAddrV4, path::PathBuf}; -use structopt::StructOpt; +use anyhow::{Context, Result}; +use std::{ + collections::HashMap, + env, + fs::File, + io::{BufRead, BufReader}, +}; -lazy_static! { - pub static ref INSTANCE: Config = Config::from_args(); -} - -#[derive(Debug, StructOpt)] -#[structopt(author, about)] -pub struct Config { - #[structopt( - name = "DIR", - required = true, - parse(from_str), - help = "The path to your data directory" - )] - pub data_dir: PathBuf, - - #[structopt( - name = "ADDR", - long = "bind-addr", - required = false, - default_value = "127.0.0.1:5353", - help = "Sets the IP address and port to bind to" - )] - pub bind_addr: SocketAddrV4, +const DEFAULT_RCFILE: &str = "/usr/local/etc/twinhrc"; // TODO: windows users? +const RCFILE: &str = "TWINHRC"; +const PREFIX: &str = "TWINH_"; +pub const LOGLEVEL: &str = "TWINH_LOGLEVEL"; - #[structopt( - long, - short, - multiple = true, - parse(from_occurrences), - help = "Increases log level" - )] - pub verbose: usize, +pub fn init() -> Result> { + let rcpath = env::var(RCFILE).unwrap_or_else(|_| DEFAULT_RCFILE.to_owned()); + let rcfile = File::open(rcpath).with_context(|| "Failed to open rcfile")?; - #[structopt( - long, - help = "Creates a fresh data directory at DIR and exits; Fails if DIR exists" - )] - pub create_dir: bool, + // read rcfile into config map + let mut config = HashMap::new(); + for line in BufReader::new(rcfile).lines() { + let line = line.with_context(|| "Failed to parse line in rcfile")?; + if !line.starts_with('#') { + if let Some((key, val)) = line.split_once('=') { + config.insert(key.into(), val.into()); + } + } + } - #[structopt(long, help = "Imports CSV car data into the data directory")] - pub import_cars: bool, + // override rcfile config with any present env vars + for (key, val) in env::vars() { + if let Some(key) = key.strip_prefix(PREFIX) { + config.insert(key.into(), val); + } + } - #[structopt(long, help = "Imports CSV parts data into the data directory")] - pub import_parts: bool, + Ok(config) } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index f987d66..0000000 --- a/src/error.rs +++ /dev/null @@ -1,51 +0,0 @@ -use bincode::Error as bincode_e; -use hyper::Error as hyper_e; -use sled::Error as sled_e; -use std::fmt; -use std::net::AddrParseError; -use std::num::ParseIntError; - -#[derive(Debug)] -pub struct TwinHError(pub String); - -impl TwinHError { - fn bail(self) {} -} - -impl std::error::Error for TwinHError {} - -impl std::fmt::Display for TwinHError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "twinh: {}", self.0) - } -} - -impl From for TwinHError { - fn from(e: sled_e) -> Self { - Self(format!("database error: {}", e)) - } -} - -impl From for TwinHError { - fn from(e: bincode_e) -> Self { - Self(format!("(de)serialization error: {}", e)) - } -} - -impl From for TwinHError { - fn from(e: AddrParseError) -> Self { - Self(format!("failed to parse addr: {}", e)) - } -} - -impl From for TwinHError { - fn from(e: ParseIntError) -> Self { - Self(format!("failed to parse port: {}", e)) - } -} - -impl From for TwinHError { - fn from(e: hyper_e) -> Self { - Self(format!("server error: {}", e)) - } -} diff --git a/src/import/mod.rs b/src/import/mod.rs deleted file mode 100644 index bac9966..0000000 --- a/src/import/mod.rs +++ /dev/null @@ -1 +0,0 @@ -const test: i32 = 4; diff --git a/src/main.rs b/src/main.rs index a17e2b2..edf8be1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,47 +1,47 @@ -use crate::error::TwinHError; -use hyper::{ - service::{make_service_fn, service_fn}, - Server, -}; -use log::LevelFilter; - -#[macro_use] -extern crate lazy_static; +use anyhow::Result; +use log::{debug, error}; +use std::env; +use std::io::prelude::*; mod config; -mod error; -mod import; -mod models; -mod repo; -mod routes; mod templates; -#[tokio::main] -async fn main() -> Result<(), TwinHError> { - // configure logger - let level = match config::INSTANCE.verbose { - 1 => LevelFilter::Info, - 2 => LevelFilter::Debug, - _ => LevelFilter::Warn, - }; - env_logger::builder().filter_level(level).init(); - - // create HTTP listener - let make_svc = - make_service_fn(move |_conn| async { Ok::<_, TwinHError>(service_fn(routes::router)) }); - - // bind server with signal - let server = Server::bind(&config::INSTANCE.bind_addr.into()).serve(make_svc); - let graceful = server.with_graceful_shutdown(shutdown_signal()); - - // start and wait for shutdown - graceful.await?; - Ok(()) +fn main() { + if let Err(e) = || -> Result<()> { + env_logger::Builder::from_env(config::LOGLEVEL).init(); + let config = config::init()?; + + let reg = templates::init(); + let vars = env::vars(); + + let mut input = String::new(); + std::io::stdin().read_to_string(&mut input).unwrap(); + debug!("Final starting input: {input}"); + debug!("Final starting config: {:?}", config); + + if (env::var("PATH_INFO")? == "/status") { + status(); + } else { + println!("content-type: text/html\n"); + println!("{}", reg.render("index", &String::new())?); + } + Ok(()) + }() { + error!("{:#}", e); + println!("content-type: text/plain\n\n500"); + } } -async fn shutdown_signal() { - // Wait for CTRL+C - tokio::signal::ctrl_c() - .await - .expect("failed to install CTRL+C signal handler"); +fn status() { + let vars = env::vars(); + let mut input = String::new(); + + // begin writing + println!("Content-Type: text/html\n\n
    "); + + for each in vars { + println!("
  • {:?}
  • ", &each); + } + + println!("{:?}", &input); } diff --git a/src/models/mod.rs b/src/models/mod.rs deleted file mode 100644 index ddef71f..0000000 --- a/src/models/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::error::TwinHError; -use bincode::deserialize; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use sled::IVec; - -pub struct Entity { - pub umrn: u64, - pub model: M, -} - -impl Entity { - pub fn new(t: (IVec, IVec)) -> Result { - let (umrn, model) = t; - let umrn = deserialize(&umrn)?; - let model = deserialize(&model)?; - Ok(Entity { umrn, model }) - } -} - -#[derive(Serialize, Deserialize)] -pub struct Part { - pub key: u64, - pub number: String, - pub name: String, - pub sources: Vec, - pub categories: Vec, - pub fits_cars: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct Car { - pub doors: u8, - //pub engine: Engine, - pub make: String, - pub model: String, - //pub transmission: Transmission, - pub trim: String, - pub year: u16, -} - -#[derive(Serialize, Deserialize)] -pub enum Source { - Web(String), -} - -#[derive(Serialize, Deserialize)] -pub struct Engine { - cylinders: u8, - displacement: u16, - layout: Layout, -} - -#[derive(Serialize, Deserialize)] -pub enum Layout { - I, - V, -} - -#[derive(Serialize, Deserialize)] -pub struct Transmission(String); - -#[derive(Serialize, Deserialize)] -pub struct Category(String); diff --git a/src/repo/constants.rs b/src/repo/constants.rs deleted file mode 100644 index 6da641f..0000000 --- a/src/repo/constants.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub const PARTS_TREE: &str = "parts"; -pub const CARS_TREE: &str = "cars"; diff --git a/src/repo/mod.rs b/src/repo/mod.rs deleted file mode 100644 index 4c55696..0000000 --- a/src/repo/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::config; -use crate::error::TwinHError; -use crate::models::Car; -use crate::models::Part; -use bincode::deserialize; -use constants::*; -use sled::{Config, Db}; - -mod constants; - -lazy_static! { - static ref REPO_INSTANCE: Db = match Config::default().path(&config::INSTANCE.data_dir).open() { - Err(e) => panic!("failed to open database: {}", e), - Ok(db) => db, - }; -} - -pub fn create_demo_db() -> Result<(), TwinHError> { - let db = sled::Config::default() - .path(&config::INSTANCE.data_dir) - .create_new(true) - .open()?; - let cars_tree = db.open_tree(CARS_TREE)?; - Ok(()) -} - -pub fn create_new_db() -> Result<(), TwinHError> { - sled::Config::default() - .path(&config::INSTANCE.data_dir) - .create_new(true) - .open()?; - Ok(()) -} - -pub fn get_all_cars() -> Result, TwinHError> { - let cars = REPO_INSTANCE - .open_tree(CARS_TREE)? - .iter() - .collect::, _>>()? - .into_iter() - .map(|e| crate::models::Entity::new(e)) - .map(|e| e.map(|e| e.model)) - .collect::, _>>()?; - // let cars = REPO_INSTANCE - // .open_tree(CARS_TREE)? - // .into_iter() - // .values() - // .collect::, _>>()? - // .iter() - // .map(|c| deserialize::(&c)) - // .collect::, _>>()?; - Ok(cars) -} - -pub fn insert_part(part: Part) -> Result { - todo!() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::models::{Car, Entity, Part}; - use bincode::serialize; - use std::error::Error; - - #[test] - fn test_insert_part() -> Result<(), Box> { - let db = sled::Config::default() - .mode(sled::Mode::HighThroughput) - .temporary(true) - .open()?; - - let car = Car { - make: "Hudson".into(), - model: "Hornet".into(), - trim: "Sedan".into(), - doors: 4, - year: 1953, - }; - - let entity = Entity(1, &car); - - let tree = db.open_tree(CARS_TREE)?; - let key = entity.0.to_be_bytes(); - let val = serialize(&car)?; - tree.insert(key, val)?; - - let part = Part { - key: 2, - number: "ABC123".into(), - name: "Rear Wheel Bearing".into(), - fits_cars: vec![entity.0], - categories: Vec::new(), - sources: Vec::new(), - }; - - let tree = db.open_tree(PARTS_TREE)?; - let (key, val) = (serialize(&part.key)?, serialize(&part)?); - tree.insert(key.clone(), val)?; - - let part_out = tree.get(key)?; - let part_out = deserialize::(&part_out.unwrap())?; - assert_eq!(part.key, part_out.key); - - Ok(()) - } -} diff --git a/src/routes/mod.rs b/src/routes/mod.rs deleted file mode 100644 index 1948657..0000000 --- a/src/routes/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::error::TwinHError; -//use crate::repo; -use crate::templates; -use hyper::body::HttpBody as _; -use hyper::Method; -use hyper::StatusCode; -use hyper::{Body, Request, Response}; -use serde::{Deserialize, Serialize}; - -pub async fn router(req: Request) -> Result, TwinHError> { - match (req.method(), req.uri().path()) { - (&Method::GET, "/favicon.ico") | (&Method::GET, "/favicon.svg") => favicon(), - (&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()) - .unwrap()), - } -} - -async fn suggestions() -> Result, TwinHError> { - todo!() -} - -async fn login() -> Result, TwinHError> { - Ok(Response::new( - templates::REGISTRY.render("login", &"").unwrap().into(), - )) -} - -async fn login_google() -> Result, TwinHError> { - let uri = hyper::Uri::builder() - .scheme("https") - .authority("accounts.google.com") - .path_and_query(format!( - "{}?client_id={}&redirect_uri={}&response_type={}&scope={}&state={}", - "/o/oauth2/v2/auth", - "???", - "http://localhost:3000/login/google/code", - "code", - "openid%20profile%20email", - "BLARGH" - )) - .build() - .unwrap(); - let resp = Response::builder() - .header(hyper::header::LOCATION, uri.to_string()) - .status(StatusCode::TEMPORARY_REDIRECT) - .body("".into()) - .unwrap(); - Ok(resp) -} - -#[derive(Serialize)] -struct AuthRequest { - client_id: String, - client_secret: String, - grant_type: &'static str, - redirect_uri: String, - code: String, -} - -impl Default for AuthRequest { - fn default() -> Self { - Self { - client_id: "???".into(), - client_secret: "???".into(), - grant_type: "authorization_code", - redirect_uri: String::new(), - code: String::new(), - } - } -} - -async fn login_google_code(req: Request) -> Result, TwinHError> { - let query = req.uri().query().unwrap_or_default(); - let query = serde_urlencoded::de::from_str::(query).unwrap(); - if query.state != "BLARGH" { - dbg!("tampering?"); - } - - // get access token - let auth_body = AuthRequest { - code: query.code, - redirect_uri: "http://localhost:3000/login/google/code".to_owned(), - ..Default::default() - }; - - let https = hyper_rustls::HttpsConnector::with_native_roots(); - let client: hyper::Client<_, hyper::Body> = hyper::Client::builder().build(https); - - let req = Request::builder() - .method(Method::POST) - .uri("https://oauth2.googleapis.com/token") - .header( - hyper::header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .body(serde_urlencoded::ser::to_string(auth_body).unwrap().into()) - .unwrap(); - let mut resp = client.request(req).await.unwrap(); - use std::io::prelude::*; - while let Some(chunk) = resp.body_mut().data().await { - std::io::stdout().write_all(&chunk.unwrap()).unwrap(); - } - - let resp = Response::builder() - .header(hyper::header::LOCATION, "/".to_string()) - .status(StatusCode::TEMPORARY_REDIRECT) - .body("Successful login, redirecting...".into()) - .unwrap(); - Ok(resp) -} - -pub struct AccessToken { - access_token: String, - expires_in: String, - scope: String, - token_type: String, -} - -async fn cars(req: Request) -> Result, TwinHError> { - //let cars = repo::get_all_cars().unwrap(); - todo!() -} - -fn favicon() -> Result, TwinHError> { - let resp = Response::builder() - .header(hyper::header::CONTENT_TYPE, "image/svg+xml") - .body(templates::FAVICON.into()) - .unwrap(); - Ok(resp) -} - -async fn index(req: Request) -> Result, TwinHError> { - let query = req.uri().query().unwrap_or_default(); - let filter = serde_urlencoded::de::from_str::(query).unwrap(); - let mut data = PartsData::default(); - data.makes = Some(vec!["Hudson".into(), "Essex".into(), "Terraplane".into()]); - - if let Some(make) = filter.make { - if make.eq("Hudson") { - data.models = Some(vec!["Hornet".into(), "Wasp".into(), "Jet".into()]); - } else if make.eq("Essex") { - data.models = Some(vec!["Super Six".into()]); - } - data.selected_make = Some(make); - } - - data.parts = Some(Vec::new()); - if let Some(model) = filter.model { - data.parts = Some(vec!["1".into(), "2".into(), "3".into()]); - data.selected_model = Some(model); - } - - 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)] -struct GoogleOAuthQuery { - code: String, - state: String, -} - -#[derive(Debug, Deserialize)] -struct PartsQuery { - make: Option, - year: Option, - model: Option, - engine: Option, -} - -#[derive(Debug, Serialize, Default)] -struct PartsData { - makes: Option>, - years: Option>, - models: Option>, - engines: Option>, - selected_make: Option, - selected_year: Option, - selected_model: Option, - selected_engine: Option, - parts: Option>, -} diff --git a/src/templates/base.hbs b/src/templates/base.hbs index 34f435b..410a5b2 100644 --- a/src/templates/base.hbs +++ b/src/templates/base.hbs @@ -42,12 +42,10 @@ flex-direction: column; background-color: var(--balboa); padding: 0.5em; - color: white; } aside button { font-size: 1em; - padding: 0.5em 0.5em; display: block; background: none; border: 0 none; @@ -63,11 +61,11 @@ padding: 0.5em; } - #hidemenu:target aside { + #hidden:target aside { display: none; } - #hidemenu:target a.openMenu { + #hidden:target a.openMenu { display: block; } @@ -75,7 +73,7 @@ display: none; } - #hidemenu:target a.closeMenu { + #hidden:target a.closeMenu { display: none; } @@ -150,7 +148,7 @@ - +