diff --git a/Cargo.toml b/Cargo.toml index 2faab33..2d27e6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] base64 = "0.22.1" rand = "0.8" +regex = "1.11.1" sqlite = "0.36.1" diff --git a/public/404.html b/public/404.html deleted file mode 100644 index d42bc82..0000000 --- a/public/404.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Page Not Found - - - -

Page cannot be found

-

Sorry, this page cannot be found.

- - diff --git a/public/assets/main.js b/public/assets/main.js index 3650871..60dc2f6 100644 --- a/public/assets/main.js +++ b/public/assets/main.js @@ -1,2 +1 @@ -alert("time to rock"); -console.log("hello"); +console.log("main.js loaded"); diff --git a/public/assets/style.css b/public/assets/style.css index 9036e97..620a6aa 100644 --- a/public/assets/style.css +++ b/public/assets/style.css @@ -1,3 +1,19 @@ +@import url("https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Anton&family=Bebas+Neue&display=swap"); +:root { + --blue: #002D62; + --red: #C8102E; + --green: #4CAF50; + --background: #F5F5F5; + --primary-text: #1A1A1A; + --secondary-text: #555555; + --success: #2E8B57; + --warning: #FFA500; + --error: #D72638; + --primary-font: "Bebas Neue", sans-serif; + --secondary-font: "Anton", sans-serif; +} + /* 1. Use a more-intuitive box-sizing model */ *, *::before, *::after { box-sizing: border-box; @@ -53,8 +69,95 @@ h1, h2, h3, h4, h5, h6 { isolation: isolate; } +header { + background: var(--blue); + padding: 20px; +} +header #site-name { + font-family: var(--secondary-font); + font-size: 2rem; + letter-spacing: 1px; +} +header a { + color: var(--background); +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--primary-font); +} + +h1 { + font-size: 3rem; + font-weight: 700; +} + +h2 { + font-size: 2.5rem; + font-weight: 700; +} + +h3 { + font-size: 2rem; + font-weight: 500; +} + +h4 { + font-size: 1.5rem; + font-weight: 500; +} + +h5 { + font-size: 1.2rem; + font-weight: 500; +} + +h6 { + font-size: 1rem; + font-weight: 500; +} + +.styled-table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} + +.styled-table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; +} + +.styled-table th, +.styled-table td { + padding: 12px 15px; +} + +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; +} + +.styled-table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +.styled-table tbody tr:last-of-type { + border-bottom: 2px solid #009879; +} + +.styled-table tbody tr.active-row { + font-weight: bold; + color: #009879; +} + body { - background: orange; + background: var(--background); + color: var(--primary-text); + padding: 20px; + font-size: 1.2rem; } img { diff --git a/public/assets/style.css.map b/public/assets/style.css.map index 8d73f85..d8a2c55 100644 --- a/public/assets/style.css.map +++ b/public/assets/style.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../theme/reset.scss","../../theme/styles.scss"],"names":[],"mappings":"AAAA;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;IACE;;;AAIJ;AACE;EACA;AACA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAEF;EACE;;;AAGF;AAAA;AAAA;AAGA;EACE;;;AClDF;EACE;;;AAGF;EACE;EACA","file":"style.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../theme/styles.scss","../../theme/variables.scss","../../theme/reset.scss","../../theme/header.scss","../../theme/headings.scss","../../theme/tables.scss"],"names":[],"mappings":"AAAQ;AACA;ACAR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;ACZF;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;IACE;;;AAIJ;AACE;EACA;AACA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAEF;EACE;;;AAGF;AAAA;AAAA;AAGA;EACE;;;ACpDF;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;;ACXJ;EACE;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;;;AAIF;EACE;EACA;;;AClCF;EACI;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;AAAA;EAEI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AL1BJ;EACE;EACA;EACA;EACA;;;AAKF;EACE;EACA","file":"style.css"} \ No newline at end of file diff --git a/public/components/header.html b/public/components/header.html new file mode 100644 index 0000000..f0ca420 --- /dev/null +++ b/public/components/header.html @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/fenway.jpg b/public/images/fenway.jpg new file mode 100644 index 0000000..a44a86d Binary files /dev/null and b/public/images/fenway.jpg differ diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 89a50b3..0000000 --- a/public/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - Toolshed - - - - -

This is my toolshed. This web server is built using Rust :)

- some image - - diff --git a/public/pages/404.html b/public/pages/404.html new file mode 100644 index 0000000..ba5d29a --- /dev/null +++ b/public/pages/404.html @@ -0,0 +1,32 @@ + + + + Page Not Found + + + + + + + + +
+

Sorry, this page cannot be found.

+

Sorry, this page cannot be found.

+

Sorry, this page cannot be found.

+

Sorry, this page cannot be found.

+
Sorry, this page cannot be found.
+
Sorry, this page cannot be found.
+
+ + diff --git a/public/pages/about.html b/public/pages/about.html deleted file mode 100644 index 5f03faf..0000000 --- a/public/pages/about.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - About - - -

This is the about page!

- - diff --git a/public/pages/add-scorecard.html b/public/pages/add-scorecard.html new file mode 100644 index 0000000..6bc8fa3 --- /dev/null +++ b/public/pages/add-scorecard.html @@ -0,0 +1,38 @@ + + + + Add New Scorecard + {% header %} + + + +
+ + + + + + + + + + + + + + + +
NameDateScoreActions
+
+ + diff --git a/public/pages/index.html b/public/pages/index.html new file mode 100644 index 0000000..8a875fb --- /dev/null +++ b/public/pages/index.html @@ -0,0 +1,24 @@ + + + + BatFlip Scoring App + {% header %} + + + +
+

Live Baseball Scoring App, written in Rust

+ some image +
+ + diff --git a/public/pages/scorecards.html b/public/pages/scorecards.html new file mode 100644 index 0000000..fe98434 --- /dev/null +++ b/public/pages/scorecards.html @@ -0,0 +1,22 @@ + + + + Scorecards + {% header %} + + + +
+

Scorecards Go Here.

+
+ + diff --git a/src/connection.rs b/src/connection.rs index 1d15a01..64d8309 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -3,8 +3,7 @@ use std::{ io::{prelude::*, BufReader}, net::TcpStream, }; - -use crate::router; +use batflip::*; #[allow(dead_code)] struct IncomingRequest { @@ -13,7 +12,7 @@ struct IncomingRequest { // headers: Vec } -#[allow(unused_assignments)] +// Handle the incoming connection. pub fn handle_connection(mut stream: TcpStream) { // Create a new buffer reader with the stream data. let buf_reader = BufReader::new(&stream); @@ -36,9 +35,10 @@ pub fn handle_connection(mut stream: TcpStream) { // Handle 404. if inc_request.path.is_empty() { response = return_response("404"); + stream.write_all(response.as_bytes()).unwrap() } - // Handle images. + // Handle images, else pages or assets. if inc_request.path.contains("images") { let mut public_route: String = "./public".to_string(); public_route.push_str(&inc_request.path); @@ -49,53 +49,10 @@ pub fn handle_connection(mut stream: TcpStream) { status_line, image_data.len() ); - stream.write(response.as_bytes()).unwrap(); - stream.write(&image_data).unwrap(); - } - else { + stream.write_all(response.as_bytes()).unwrap(); + stream.write_all(&image_data).unwrap() + } else { response = return_response(&inc_request.path); - stream.write(response.as_bytes()).unwrap(); + stream.write_all(response.as_bytes()).unwrap() } } - -#[allow(dead_code)] -fn get_header(headers: Vec, needle: &str) -> String { - let mut value = String::new(); - for header in headers { - let split_header: Vec<&str> = header.split(":").collect(); - let header_key: String = split_header.first().expect("Nothing here").to_string(); - if header_key == needle { - value = split_header[1].to_string(); - } - } - value -} - -fn return_response(path_or_code: &str) -> String { - let status_line: String; - let contents: String; - - match path_or_code { - "404" => { - status_line = "HTTP/1.1 404 Not Found".to_string(); - contents = fs::read_to_string("./public/404.html").unwrap(); - }, - "200" => { - status_line = "HTTP/1.1 200 OK".to_string(); - contents = fs::read_to_string("./public/index.html").unwrap(); - }, - &_ => { - // Need to get the proper response based on the given path. - status_line = "HTTP/1.1 200 OK".to_string(); - contents = router::get_route_data(path_or_code); - // status_line = "HTTP/1.1 404 Not Found".to_string(); - } - } - - let length = contents.len(); - format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}") -} - -pub fn path_exists(path: &str) -> bool { - fs::metadata(path).is_ok() -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..11c2c47 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,136 @@ +use std::fs; +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, +}; +use regex::Regex; + +#[allow(dead_code)] +pub struct ThreadPool { + workers: Vec, + sender: mpsc::Sender, +} + +type Job = Box; + +impl ThreadPool { + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + let (sender, receiver) = mpsc::channel(); + let receiver = Arc::new(Mutex::new(receiver)); + let mut workers = Vec::with_capacity(size); + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + ThreadPool { workers, sender } + } + + pub fn process(&self, f: F) + where + F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + self.sender.send(job).unwrap(); + } +} + +#[allow(dead_code)] +struct Worker { + id: usize, + thread: thread::JoinHandle<()>, +} + +impl Worker { + fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || loop { + let job = receiver.lock().unwrap().recv().unwrap(); + // println!("Worker {id} got a job; executing."); + job(); + }); + + Worker { id, thread } + } +} + +// Create the response for a request. +pub fn return_response(path_or_code: &str) -> String { + let status_line: String; + let contents: String; + match path_or_code { + "404" => { + status_line = "HTTP/1.1 404 Not Found".to_string(); + contents = fs::read_to_string("./public/pages/404.html").unwrap(); + }, + &_ => { + status_line = "HTTP/1.1 200 OK".to_string(); + contents = get_route_data(path_or_code); + } + } + + let length = contents.len(); + format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}") +} + +// Generate the data for the given route. +pub fn get_route_data(route: &str) -> String { + match route { + "/" => { + let file_contents = fs::read_to_string("./public/pages/index.html").unwrap(); + hot_swap(&file_contents) + }, + "/scorecards" => { + let file_contents = fs::read_to_string("./public/pages/scorecards.html").unwrap(); + hot_swap(&file_contents) + }, + "/add-scorecard" => { + let file_contents = fs::read_to_string("./public/pages/add-scorecard.html").unwrap(); + hot_swap(&file_contents) + }, + _ if route.contains("assets") => { + let mut public_route: String = "./public".to_string(); + public_route.push_str(&route); + return fs::read_to_string(&public_route).unwrap() + }, + &_ => { + return fs::read_to_string("./public/pages/404.html").unwrap(); + } + } +} + +// Swap dynamic sections of html template. +pub fn hot_swap(source_html: &str) -> String { + let mut updated_html: String = String::from(source_html); + let re = Regex::new(r"\{%.(?.*).%\}").unwrap(); + for section in re.captures_iter(source_html) { + let comp_content = retrieve_component(§ion["name"]); + updated_html = updated_html.replace(§ion[0], &comp_content); + } + updated_html +} + +// Retrieve html markup from component. +pub fn retrieve_component(component: &str) -> String { + let mut comp_path = String::from("./public/components/"); + let html_ext = String::from(".html"); + comp_path.push_str(&component); + comp_path.push_str(&html_ext); + fs::read_to_string(&comp_path).unwrap() +} + +// Validate if a given path exists. +pub fn path_exists(path: &str) -> bool { + fs::metadata(path).is_ok() +} + +#[allow(dead_code)] +pub fn get_header(headers: Vec, needle: &str) -> String { + let mut value = String::new(); + for header in headers { + let split_header: Vec<&str> = header.split(":").collect(); + let header_key: String = split_header.first().expect("Nothing here").to_string(); + if header_key == needle { + value = split_header[1].to_string(); + } + } + value +} diff --git a/src/main.rs b/src/main.rs index cb37d60..625a708 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,17 @@ use std::net::TcpListener; - +use batflip::ThreadPool; +// use std::thread; pub mod connection; -mod router; // Listen on port and return response. fn main() { - let run_local = true; - let mut listener = TcpListener::bind("127.0.0.1:6942").unwrap(); - - if !run_local { - listener = TcpListener::bind("0.0.0.0:6942").unwrap(); - } - + let listener = TcpListener::bind("127.0.0.1:6942").unwrap(); + let pool = ThreadPool::new(4); for stream in listener.incoming() { let stream = stream.unwrap(); - connection::handle_connection(stream); + pool.process(move || { + connection::handle_connection(stream); + }); } } diff --git a/src/router.rs b/src/router.rs deleted file mode 100644 index a141a67..0000000 --- a/src/router.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::fs; - -pub fn get_route_data(route: &str) -> String { - match route { - "/" => { - return fs::read_to_string("./public/index.html").unwrap(); - }, - "/about" => { - return fs::read_to_string("./public/pages/about.html").unwrap(); - } - _ if route.contains("assets") => { - let mut public_route: String = "./public".to_string(); - public_route.push_str(&route); - return fs::read_to_string(&public_route).unwrap() - }, - &_ => { - return fs::read_to_string("./public/404.html").unwrap(); - } - } -} - -// pub fn path_exists(path: &str) -> bool { -// fs::metadata(path).is_ok() -// } diff --git a/theme/header.scss b/theme/header.scss new file mode 100644 index 0000000..d050da9 --- /dev/null +++ b/theme/header.scss @@ -0,0 +1,14 @@ +header { + background: var(--blue); + padding: 20px; + + #site-name { + font-family: var(--secondary-font); + font-size: 2rem; + letter-spacing: 1px; + } + + a { + color: var(--background); + } +} diff --git a/theme/headings.scss b/theme/headings.scss new file mode 100644 index 0000000..690d137 --- /dev/null +++ b/theme/headings.scss @@ -0,0 +1,36 @@ +h1, h2, h3, h4, h5, h6 { + font-family: var(--primary-font); +} + +h1 { + font-size: 3rem; + font-weight: 700; + +} + +h2 { + font-size: 2.5rem; + font-weight: 700; +} + +h3 { + font-size: 2rem; + font-weight: 500; +} + +h4 { + font-size: 1.5rem; + font-weight: 500; + +} + +h5 { + font-size: 1.2rem; + font-weight: 500; + +} + +h6 { + font-size: 1rem; + font-weight: 500; +} diff --git a/theme/styles.scss b/theme/styles.scss index 21c6862..d446a29 100644 --- a/theme/styles.scss +++ b/theme/styles.scss @@ -1,9 +1,20 @@ +@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Anton&family=Bebas+Neue&display=swap'); +@import "variables"; @import "reset"; +@import "header"; +@import "headings"; +@import "tables"; body { - background: orange; + background: var(--background); + color: var(--primary-text); + padding: 20px; + font-size: 1.2rem; } + + img { max-width: 100%; display: block; diff --git a/theme/tables.scss b/theme/tables.scss new file mode 100644 index 0000000..005113c --- /dev/null +++ b/theme/tables.scss @@ -0,0 +1,36 @@ +.styled-table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} + +.styled-table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; +} + +.styled-table th, +.styled-table td { + padding: 12px 15px; +} + +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; +} + +.styled-table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +.styled-table tbody tr:last-of-type { + border-bottom: 2px solid #009879; +} + +.styled-table tbody tr.active-row { + font-weight: bold; + color: #009879; +} diff --git a/theme/variables.scss b/theme/variables.scss new file mode 100644 index 0000000..7949884 --- /dev/null +++ b/theme/variables.scss @@ -0,0 +1,14 @@ +// Color Scheme +:root { + --blue: #002D62; + --red: #C8102E; + --green: #4CAF50; + --background: #F5F5F5; + --primary-text: #1A1A1A; + --secondary-text: #555555; + --success: #2E8B57; + --warning: #FFA500; + --error: #D72638; + --primary-font: "Bebas Neue", sans-serif; + --secondary-font: "Anton", sans-serif; +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..e69de29