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 :)
-
-
-
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 %}
+
+
+
+
+
+
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
+

+
+
+
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