summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml4
-rw-r--r--artifacts/categories48
-rw-r--r--artifacts/makes-models5
-rw-r--r--artifacts/template.json22
-rw-r--r--src/config.rs22
-rw-r--r--src/main.rs43
-rw-r--r--src/models/mod.rs43
-rw-r--r--src/templates/index.html92
9 files changed, 245 insertions, 36 deletions
diff --git a/.gitignore b/.gitignore
index 422814a..f504fd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ Cargo.lock
#Added by cargo
/target
+
+.env
diff --git a/Cargo.toml b/Cargo.toml
index f3608d2..4fc3f0c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,10 +7,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+env_logger = "0.8"
handlebars = "3.5"
hyper = { version = "0.14", default-features = false, features = ["full"] }
+log = "0.4"
mongodb = "1.2"
-once_cell = "1.5"
+once_cell = "1.7"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
diff --git a/artifacts/categories b/artifacts/categories
new file mode 100644
index 0000000..4f9ad46
--- /dev/null
+++ b/artifacts/categories
@@ -0,0 +1,48 @@
+From [Wikipedia](https://en.wikipedia.org/wiki/List_of_auto_parts). Could be adapted for use in this application. Need to dump electronic parts that aren't as important and add parts that are no longer considered essential equipment.
+
+1 Car body and main part
+
+ 1.1 Body components, including trim
+ 1.2 Doors
+ 1.3 Windows
+
+2 Low voltage/auxiliary electrical system and electronics
+
+ 2.1 Audio/video devices
+ 2.2 Cameras
+ 2.3 Low voltage electrical supply system
+ 2.4 Gauges and meters
+ 2.5 Ignition system
+ 2.6 Lighting and signaling system
+ 2.7 Sensors
+ 2.8 Starting system
+ 2.9 Electrical switches
+ 2.10 Wiring harnesses
+ 2.11 Miscellaneous
+
+3 Interior
+
+ 3.1 Floor components and parts
+ 3.2 Other components
+ 3.3 Car seat
+
+4 Power-train and chassis
+
+ 4.1 Braking system
+ 4.2 Electrified powertrain components
+ 4.3 Engine components and parts
+ 4.4 Engine cooling system
+ 4.5 Engine oil systems
+ 4.6 Exhaust system
+ 4.7 Fuel supply system
+ 4.8 Suspension and steering systems
+ 4.9 Transmission system
+
+5 Miscellaneous auto parts
+
+ 5.1 Air conditioning system (A/C)
+ 5.2 Bearings
+ 5.3 Hose
+ 5.4 Other miscellaneous parts
+
+
diff --git a/artifacts/makes-models b/artifacts/makes-models
new file mode 100644
index 0000000..a18b407
--- /dev/null
+++ b/artifacts/makes-models
@@ -0,0 +1,5 @@
+Hudson
+ Hornet
+ Wasp
+ Pacemaker
+
diff --git a/artifacts/template.json b/artifacts/template.json
new file mode 100644
index 0000000..ddacc2a
--- /dev/null
+++ b/artifacts/template.json
@@ -0,0 +1,22 @@
+{
+ "type": "part",
+ "shortName": "Wheel Bearing",
+ "fitsCars": [
+ {
+ "type": "car",
+ "year": 1953,
+ "make": "Hudson",
+ "model": "Hornet",
+ "engine": {
+ "type": "engine",
+ "cylinders": 6,
+ "displacement": 308,
+ "layout": "I"
+ },
+ "doors": 4,
+ "transmission": "Dual-Range Hydramatic"
+ }
+ ],
+ "sources": ["url://", "url://"],
+ "categories": ["Wheel & Tire", "Drivetrain"]
+}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..7798967
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,22 @@
+use once_cell::sync::Lazy;
+use std::{env::var, error::Error, net::SocketAddr};
+
+pub static INSTANCE: Lazy<AppConfig> =
+ Lazy::new(|| AppConfig::from_env().expect("Error loading config"));
+
+#[derive(Clone, Debug)]
+pub struct AppConfig {
+ pub addr: SocketAddr,
+ pub db_uri: String,
+}
+
+impl AppConfig {
+ pub fn from_env() -> Result<Self, Box<dyn Error>> {
+ let new = Self {
+ addr: var("TWINH_ADDR")?.parse()?,
+ db_uri: var("TWINH_DB_URI")?,
+ };
+
+ Ok(new)
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 2f5bdf4..b71df18 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,16 +5,22 @@ use hyper::StatusCode;
use hyper::{Body, Request, Response, Server};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
-use std::net::SocketAddr;
+mod config;
+mod models;
mod templates;
#[tokio::main]
-async fn main() {
- // bind to 127.0.0.1:3000
- let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let addr = config::INSTANCE.addr;
- let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(router)) });
+ let client = mongodb::Client::with_uri_str(&config::INSTANCE.db_uri).await?;
+
+ let make_svc = make_service_fn(move |_conn| {
+ let client = client.clone();
+
+ async { Ok::<_, Infallible>(service_fn(move |req| router(req, client.to_owned()))) }
+ });
let server = Server::bind(&addr).serve(make_svc);
let graceful = server.with_graceful_shutdown(shutdown_signal());
@@ -23,12 +29,17 @@ async fn main() {
if let Err(e) = graceful.await {
eprintln!("server error: {}", e);
}
+
+ Ok(())
}
-async fn router(req: Request<Body>) -> Result<Response<Body>, Infallible> {
+async fn router(
+ req: Request<Body>,
+ _client: mongodb::Client,
+) -> Result<Response<Body>, Infallible> {
match (req.method(), req.uri().path()) {
(&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new("Welcome!".into())),
- (&Method::GET, "/parts") | (&Method::GET, "/parts/index.html") => parts(req).await,
+ (&Method::GET, "/parts") | (&Method::GET, "/parts/index.html") => get_parts(req).await,
_ => Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not found.".into())
@@ -36,14 +47,14 @@ async fn router(req: Request<Body>) -> Result<Response<Body>, Infallible> {
}
}
-async fn parts(req: Request<Body>) -> Result<Response<Body>, Infallible> {
+async fn get_parts(req: Request<Body>) -> Result<Response<Body>, Infallible> {
let query = req.uri().query().unwrap_or_default();
- let filter = serde_urlencoded::de::from_str::<PartsRequest>(query).unwrap();
+ 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 = PartsView::default();
- data.makes = Some(vec!["Hudson".into(), "Essex".into()]);
+ 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") {
@@ -72,16 +83,22 @@ async fn shutdown_signal() {
}
#[derive(Debug, Deserialize)]
-struct PartsRequest {
+struct PartsQuery {
make: Option<String>,
+ year: Option<String>,
model: Option<String>,
+ engine: Option<String>,
}
#[derive(Debug, Serialize, Default)]
-struct PartsView {
+struct PartsData {
makes: Option<Vec<String>>,
+ years: Option<Vec<String>>,
models: Option<Vec<String>>,
+ engines: Option<Vec<String>>,
selected_make: Option<String>,
+ selected_year: Option<String>,
selected_model: Option<String>,
+ selected_engine: Option<String>,
parts: Option<Vec<String>>,
}
diff --git a/src/models/mod.rs b/src/models/mod.rs
new file mode 100644
index 0000000..d7ff47a
--- /dev/null
+++ b/src/models/mod.rs
@@ -0,0 +1,43 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize)]
+pub struct Car {
+ year: u16, // Yes, in 30767 years I will address this.
+ make: String,
+ model: String,
+ engine: Engine,
+ doors: u8,
+ transmission: Transmission,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Part {
+ name: String,
+ compatible_cars: Vec<Car>,
+ sources: Vec<Source>,
+ categories: Vec<Category>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub enum Source {
+ Uri(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/templates/index.html b/src/templates/index.html
index 683891c..4663f5d 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -4,34 +4,82 @@
<meta charset="UTF-8" />
<style>
- input[type="submit"] {
+ .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>
- <h1>Parts Catalog</h1>
- {{#each makes}}
- <form action="/parts">
- {{#if (eq this ../selected_make)}}
- <input disabled type="submit" name="make" value="{{ this }}" />
- <input type="hidden" name="make" value="{{ ../selected_make }}" />
- {{#each ../models}}
- {{#if (eq this ../../selected_model)}}
- <input disabled type="submit" name="model" value="{{ this }}" />
- Parts for {{ this }}
- {{else}}
- <input type="submit" name="model" value="{{ this }}" />
- {{/if}}
+ <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 models found.
+ No makes found.
{{/each}}
- {{else}}
- <input type="submit" name="make" value="{{ this }}" />
- {{/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>