Adding all sorts of goodies.
This commit is contained in:
parent
45375e0fa3
commit
86d58c3f8a
@ -6,4 +6,5 @@ edition = "2021"
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
rand = "0.8"
|
||||
regex = "1.11.1"
|
||||
sqlite = "0.36.1"
|
||||
|
||||
@ -116,6 +116,43 @@ h6 {
|
||||
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);
|
||||
|
||||
@ -1 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../theme/styles.scss","../../theme/variables.scss","../../theme/reset.scss","../../theme/header.scss","../../theme/headings.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;;;AJ3BF;EACE;EACA;EACA;EACA;;;AAKF;EACE;EACA","file":"style.css"}
|
||||
{"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"}
|
||||
5
public/components/header.html
Normal file
5
public/components/header.html
Normal 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>
|
||||
@ -2,11 +2,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Page Not Found</title>
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<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>
|
||||
<script src="./assets/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="site-header">
|
||||
@ -1,9 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>About</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>This is the about page!</h1>
|
||||
</body>
|
||||
</html>
|
||||
38
public/pages/add-scorecard.html
Normal file
38
public/pages/add-scorecard.html
Normal 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>
|
||||
@ -2,11 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>BatFlip Scoring App</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>
|
||||
{% header %}
|
||||
</head>
|
||||
<body>
|
||||
<header id="site-header">
|
||||
@ -22,7 +18,7 @@
|
||||
</header>
|
||||
<div id="main-content">
|
||||
<h1>Live Baseball Scoring App, written in Rust</h1>
|
||||
<img src="/images/fenway.jpg" alt="some image" />
|
||||
<img src="./images/fenway.jpg" alt="some image" />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
22
public/pages/scorecards.html
Normal file
22
public/pages/scorecards.html
Normal 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>
|
||||
@ -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<String>
|
||||
}
|
||||
|
||||
#[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,52 +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();
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
136
src/lib.rs
Normal file
136
src/lib.rs
Normal 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(§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<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
|
||||
}
|
||||
17
src/main.rs
17
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
use std::fs;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn get_route_data(route: &str) -> String {
|
||||
// @required - set valid routes here.
|
||||
let route_map = HashMap::from([
|
||||
("/", "./public/index.html"),
|
||||
]);
|
||||
|
||||
dbg!(route_map.get(route));
|
||||
|
||||
// match route_map.get(route) {
|
||||
// None => ,
|
||||
// Some(page) => Some(page),
|
||||
// };
|
||||
// Some("test".to_string())
|
||||
//
|
||||
//
|
||||
// High leve list of thigns to do
|
||||
// 1. get the correct file to retrieve
|
||||
// 2. parse the file and swap out any dynamic sections
|
||||
// 3. return the string based response.
|
||||
let route_file: 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();
|
||||
}
|
||||
}
|
||||
|
||||
// fs::read_to_string(route_file).unwrap();
|
||||
}
|
||||
|
||||
// pub fn path_exists(path: &str) -> bool {
|
||||
// fs::metadata(path).is_ok()
|
||||
// }
|
||||
@ -4,6 +4,7 @@
|
||||
@import "reset";
|
||||
@import "header";
|
||||
@import "headings";
|
||||
@import "tables";
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
|
||||
36
theme/tables.scss
Normal file
36
theme/tables.scss
Normal 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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user