Compare commits

...

5 Commits

Author SHA1 Message Date
calcu1on
86d58c3f8a Adding all sorts of goodies. 2025-04-27 11:34:30 -04:00
calcu1on
45375e0fa3 Adding current working progress to hashmap for dynamic routes. 2025-04-26 10:13:56 -04:00
calcu1on
967de436f6 Adding package file to theme and using scss instead of css. 2025-04-21 08:13:05 -04:00
calcu1on
3bd38b2d80 Cleanuo 2025-04-20 23:46:48 -04:00
calcu1on
99ea2cf3ad Rename project and add handling for images and assets. 2025-04-20 23:43:18 -04:00
28 changed files with 644 additions and 137 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
# will have compiled files and executables # will have compiled files and executables
debug/ debug/
target/ target/
.DS_STORE
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # 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 # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

View File

@@ -1,9 +1,10 @@
[package] [package]
name = "hermes" name = "batflip"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
base64 = "0.22.1" base64 = "0.22.1"
rand = "0.8" rand = "0.8"
regex = "1.11.1"
sqlite = "0.36.1" sqlite = "0.36.1"

BIN
main

Binary file not shown.

BIN
public/.DS_Store vendored

Binary file not shown.

View File

@@ -1,15 +0,0 @@
<!Doctype html>
<html>
<head>
<title>Page Not Found</title>
<style>
body {
background: orange;
}
</style>
</head>
<body>
<h1>Page cannot be found</h1>
<h3>Sorry, this page cannot be found.</h3>
</body>
</html>

View File

@@ -1,2 +1 @@
let body = document.getElementsByTagName('h1'); console.log("main.js loaded");
console.log(body);

View File

@@ -1,4 +1,168 @@
body { @import url("https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap");
color: white; @import url("https://fonts.googleapis.com/css2?family=Anton&family=Bebas+Neue&display=swap");
background: purple; :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;
}
/* 2. Remove default margin */
* {
margin: 0;
}
/* 3. Enable keyword animations */
@media (prefers-reduced-motion: no-preference) {
html {
interpolate-size: allow-keywords;
}
}
body {
/* 4. Add accessible line-height */
line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 6. Improve media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* 7. Inherit fonts for form controls */
input, button, textarea, select {
font: inherit;
}
/* 8. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root, #__next {
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: var(--background);
color: var(--primary-text);
padding: 20px;
font-size: 1.2rem;
}
img {
max-width: 100%;
display: block;
}
/*# sourceMappingURL=style.css.map */

View File

@@ -0,0 +1 @@
{"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"}

View File

@@ -0,0 +1,5 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./assets/style.css" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="./assets/main.js"></script>

BIN
public/images/fenway.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Toolshed</title>
<link rel="stylesheet" href="/assets/style.css">
<script src="/assets/main.js" defer></script>
</head>
<body>
<h1>This is my toolshed. This web server is built using Rust :)</h1>
<img src="/images/6.jpg"/>
</body>
</html>

32
public/pages/404.html Normal file
View File

@@ -0,0 +1,32 @@
<!Doctype html>
<html>
<head>
<title>Page Not Found</title>
<link rel="stylesheet" href="./assets/style.css" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="./assets/main.js"></script>
</head>
<body>
<header id="site-header">
<div id="site-name">
<a href="/">BatFlip</a>
</div>
<div id="site-menu">
<ul>
<li><a href="/scorecards">Past Scorecards</a></li>
<li><a href="/add-scorecard">Add New</a></li>
</ul>
</div>
</header>
<div id="main-content">
<h1>Sorry, this page cannot be found.</h1>
<h2>Sorry, this page cannot be found.</h2>
<h3>Sorry, this page cannot be found.</h3>
<h4>Sorry, this page cannot be found.</h4>
<h5>Sorry, this page cannot be found.</h5>
<h6>Sorry, this page cannot be found.</h6>
</div>
</body>
</html>

View File

@@ -1,16 +0,0 @@
<!Doctype html>
<html>
<head>
<title>About</title>
<style>
body {
background: purple;
padding: 20px;
color: white;
}
</style>
</head>
<body>
<h1>This is the about page!</h1>
</body>
</html>

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>Add New Scorecard</title>
{% header %}
</head>
<body>
<header id="site-header">
<div id="site-name">
<a href="/">BatFlip</a>
</div>
<div id="site-menu">
<ul>
<li><a href="/scorecards">Past Scorecards</a></li>
</ul>
</div>
</header>
<div id="main-content">
<table id="home-scorecard" class="styled-table">
<thead>
<tr>
<th>Name</th>
<th>Date</th>
<th>Score</th>
<th>Actions</th>
</tr>
</thead
<tbody>
<tr>
<td><input type="text" name="name" placeholder="Name" /></td>
<td><input type="text" name="date" placeholder="Date" /></td>
<td><input type="text" name="score" placeholder="Score" /></td>
<td><input type="submit" value="Add" /></td>
</tbody>
</table>
</div>
</body>
</html>

24
public/pages/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>BatFlip Scoring App</title>
{% header %}
</head>
<body>
<header id="site-header">
<div id="site-name">
<a href="/">BatFlip</a>
</div>
<div id="site-menu">
<ul>
<li><a href="/scorecards">Past Scorecards</a></li>
<li><a href="/add-scorecard">Add New</a></li>
</ul>
</div>
</header>
<div id="main-content">
<h1>Live Baseball Scoring App, written in Rust</h1>
<img src="./images/fenway.jpg" alt="some image" />
</div>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>Scorecards</title>
{% header %}
</head>
<body>
<header id="site-header">
<div id="site-name">
<a href="/">BatFlip</a>
</div>
<div id="site-menu">
<ul>
<li><a href="/add-scorecard">Add New</a></li>
</ul>
</div>
</header>
<div id="main-content">
<h1>Scorecards Go Here.</h1>
</div>
</body>
</html>

View File

@@ -3,8 +3,7 @@ use std::{
io::{prelude::*, BufReader}, io::{prelude::*, BufReader},
net::TcpStream, net::TcpStream,
}; };
use batflip::*;
use crate::router;
#[allow(dead_code)] #[allow(dead_code)]
struct IncomingRequest { struct IncomingRequest {
@@ -13,7 +12,7 @@ struct IncomingRequest {
// headers: Vec<String> // headers: Vec<String>
} }
#[allow(unused_assignments)] // Handle the incoming connection.
pub fn handle_connection(mut stream: TcpStream) { pub fn handle_connection(mut stream: TcpStream) {
// Create a new buffer reader with the stream data. // Create a new buffer reader with the stream data.
let buf_reader = BufReader::new(&stream); let buf_reader = BufReader::new(&stream);
@@ -25,58 +24,35 @@ pub fn handle_connection(mut stream: TcpStream) {
.collect(); .collect();
// Get the first line of the stream which contains request data. // Get the first line of the stream which contains request data.
let request_line: String = http_request.first().expect("No request line found").to_string(); let request_line: String = http_request.first().expect("No request line found").to_string();
let response: String; let mut response;
// Split the request data on spaces into a vector. // Split the request data on spaces into a vector.
let req_vec: Vec<&str> = request_line.split(" ").collect(); let req_vec: Vec<&str> = request_line.split(" ").collect();
let inc_request = IncomingRequest { let inc_request = IncomingRequest {
req_type: req_vec[0].to_string(), req_type: req_vec[0].to_string(),
path: req_vec[1].to_string(), path: req_vec[1].to_string(),
}; };
// Handle 404.
if inc_request.path.is_empty() { if inc_request.path.is_empty() {
response = return_response("404"); response = return_response("404");
stream.write_all(response.as_bytes()).unwrap()
} }
else {
// 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);
let image_data = fs::read(&public_route).unwrap();
let status_line = "HTTP/1.1 200 OK".to_string();
let response = format!(
"{}\r\nContent-Length: {}\r\n\r\n",
status_line,
image_data.len()
);
stream.write_all(response.as_bytes()).unwrap();
stream.write_all(&image_data).unwrap()
} else {
response = return_response(&inc_request.path); response = return_response(&inc_request.path);
stream.write_all(response.as_bytes()).unwrap()
} }
stream.write_all(response.as_bytes()).unwrap();
}
#[allow(dead_code)]
fn get_header(headers: Vec<String>, 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}")
} }

136
src/lib.rs Normal file
View File

@@ -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<Worker>,
sender: mpsc::Sender<Job>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
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<F>(&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<Mutex<mpsc::Receiver<Job>>>) -> 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"\{%.(?<name>.*).%\}").unwrap();
for section in re.captures_iter(source_html) {
let comp_content = retrieve_component(&section["name"]);
updated_html = updated_html.replace(&section[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<String>, 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
}

View File

@@ -1,20 +1,17 @@
use std::net::TcpListener; use std::net::TcpListener;
use batflip::ThreadPool;
// use std::thread;
pub mod connection; pub mod connection;
mod router;
// Listen on port and return response. // Listen on port and return response.
fn main() { fn main() {
let run_local = true; let listener = TcpListener::bind("127.0.0.1:6942").unwrap();
let mut listener = TcpListener::bind("127.0.0.1:6942").unwrap(); let pool = ThreadPool::new(4);
if !run_local {
listener = TcpListener::bind("0.0.0.0:6942").unwrap();
}
for stream in listener.incoming() { for stream in listener.incoming() {
let stream = stream.unwrap(); let stream = stream.unwrap();
connection::handle_connection(stream); pool.process(move || {
connection::handle_connection(stream);
});
} }
} }

View File

@@ -1,32 +0,0 @@
use std::fs;
pub fn get_route_data(route: &str) -> String {
let route_data: String;
match route {
"/" => {
route_data = fs::read_to_string("./public/index.html").unwrap();
},
"/about" => {
route_data = fs::read_to_string("./public/pages/about.html").unwrap();
}
&_ => {
let mut public_path: String = "./public".to_string();
if path_exists(&public_path) {
if public_path.contains("images") {
route_data = fs::read(public_path).unwrap();
}
else {
route_data = fs::read_to_string(public_path).unwrap();
}
}
else {
route_data = fs::read_to_string("./public/404.html").unwrap();
}
}
}
route_data
}
pub fn path_exists(path: &str) -> bool {
fs::metadata(path).is_ok()
}

14
theme/header.scss Normal file
View File

@@ -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);
}
}

36
theme/headings.scss Normal file
View File

@@ -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;
}

11
theme/package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "batflip",
"version": "1.0.0",
"description": "Theme for Batflip scoring.",
"main": "index.js",
"scripts": {
"build": "sass styles.scss ../public/assets/style.css"
},
"author": "Dan Chadwick",
"license": "MIT"
}

54
theme/reset.scss Normal file
View File

@@ -0,0 +1,54 @@
/* 1. Use a more-intuitive box-sizing model */
*, *::before, *::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
/* 3. Enable keyword animations */
@media (prefers-reduced-motion: no-preference) {
html {
interpolate-size: allow-keywords;
}
}
body {
/* 4. Add accessible line-height */
line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 6. Improve media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* 7. Inherit fonts for form controls */
input, button, textarea, select {
font: inherit;
}
/* 8. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}

21
theme/styles.scss Normal file
View File

@@ -0,0 +1,21 @@
@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: var(--background);
color: var(--primary-text);
padding: 20px;
font-size: 1.2rem;
}
img {
max-width: 100%;
display: block;
}

36
theme/tables.scss Normal file
View File

@@ -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;
}

14
theme/variables.scss Normal file
View File

@@ -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;
}

0
todo.md Normal file
View File