From 2404b04e761ecdb5169c01878f7222ee84314fb6 Mon Sep 17 00:00:00 2001 From: saw <> Date: Wed, 14 Jul 2021 22:30:53 -0400 Subject: [PATCH] Borders v2.0.0 - Pirate Stache Now using handlebars instead of format! for page generation. Added quick and dirty logging with SimpleLogger. `Board` objects now have a `last_active` field. Bit of a version gap, but whatever. This is a significant enough update I think it deserves a change in the major version. 0 Warnings, 0 Errors Handlebars eliminated the XSS vulnerability because it automatically escapes text. Added comments to src/main.rs Moved some static HTML blobs into static/ rather than baking them into the binary. Now licensed under Zlib libBoarders is now compiled separately as a library. It may have VERY little application, but whatever. Made banner 3D, and thus cooler. Replaced "Boarders" with the banner used in the backend. --- Cargo.toml | 19 ++- src/api/messages.rs | 2 +- src/bin/boarders_client.rs | 3 + src/html/accounts.rs | 96 +++--------- src/html/boards.rs | 176 ++++++++-------------- src/html/index.css | 158 -------------------- src/html/mod.rs | 20 +++ src/html/navbar.html | 17 --- src/lib/boards.rs | 32 ++-- src/lib/mod.rs | 6 +- src/main.rs | 235 ++++++++++++++++-------------- src/res/auth.hbs | 19 +++ src/res/banner | 11 +- src/res/board.hbs | 42 ++++++ src/res/board_index.hbs | 31 ++++ src/res/new_account_redirect.hbs | 15 ++ src/res/snippets/board_bar.hbs | 11 ++ src/res/snippets/login_status.hbs | 17 +++ src/res/welcome.hbs | 50 +++++++ static/account_login.html | 25 ++++ static/index.css | 39 +++++ static/new_account.html | 24 +++ static/new_board.html | 28 ++++ 23 files changed, 567 insertions(+), 509 deletions(-) create mode 100644 src/bin/boarders_client.rs delete mode 100644 src/html/index.css delete mode 100644 src/html/navbar.html create mode 100644 src/res/auth.hbs create mode 100644 src/res/board.hbs create mode 100644 src/res/board_index.hbs create mode 100644 src/res/new_account_redirect.hbs create mode 100644 src/res/snippets/board_bar.hbs create mode 100644 src/res/snippets/login_status.hbs create mode 100644 src/res/welcome.hbs create mode 100644 static/account_login.html create mode 100644 static/new_account.html create mode 100644 static/new_board.html diff --git a/Cargo.toml b/Cargo.toml index 040f51c..23936e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "board_server" -version = "1.0.1" +version = "2.0.0" edition = "2018" +license = "Zlib" +repository = "https://git.solow.xyz/cgit.cgi/Boarders/" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,4 +15,19 @@ serde = "1.0.125" serde_json = "1.0" bcrypt-bsd = "0.1.3" rand = "0.8.3" +console = "0.14.1" colored = "2.0.0" +tokio = {version = "1.7.1", features=["net", "rt-multi-thread", "sync", "macros"]} +futures = "0.3.15" +handlebars = "4.0.1" +maplit = "1.0.2" +simple_logger = "1.11.0" + +[lib] +name = "boarders" +path = "src/lib/mod.rs" +test = false +bench = false +doc = false +harness = false +edition = "2018" diff --git a/src/api/messages.rs b/src/api/messages.rs index 8f93b8c..826a2e3 100644 --- a/src/api/messages.rs +++ b/src/api/messages.rs @@ -61,7 +61,7 @@ pub async fn new( } }, form.content.clone()); println!("new message"); - a.add_message(msg.clone()); + a.clone().add_message(msg.clone()); Ok(HttpResponse::Created().body(serde_json::to_string(&msg).unwrap())) }, None => Err(HttpResponse::BadRequest().body("Bad Request: Board does not exist")) diff --git a/src/bin/boarders_client.rs b/src/bin/boarders_client.rs new file mode 100644 index 0000000..03ba313 --- /dev/null +++ b/src/bin/boarders_client.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Client"); +} diff --git a/src/html/accounts.rs b/src/html/accounts.rs index be41803..ba8fa90 100644 --- a/src/html/accounts.rs +++ b/src/html/accounts.rs @@ -1,14 +1,21 @@ use actix_web::{ get, post, web, - HttpResponse, Responder, + HttpResponse, cookie::Cookie }; +use actix_files::NamedFile; use crate::api::types::*; use crate::lib::users::{User, Account}; +use crate::ThreadData; + +use handlebars::to_json; + +use maplit::btreemap; #[post("/account/auth")] pub async fn auth_html( + tdata: web::Data>, data: web::Data, form: web::Form ) -> HttpResponse { @@ -17,22 +24,10 @@ pub async fn auth_html( match u.verify(form.password.clone()) { Ok(a) => if a { let mut response = HttpResponse::Ok() - .body(format!(r#" - - - - -
-

Hello, {0}

-
-

-Logged in as {0}, return to index -

-
-"#, u.username)); + .body(tdata.handlebars.render("auth", &btreemap!("user" => u.clone())).unwrap()); response.add_cookie( &Cookie::build("auth", format!("{}&{}", u.user_id, u.secret)) - .domain("boards.solow.xyz") + .domain("localhost") //.domain("localhost") .path("/") .same_site(actix_web::cookie::SameSite::Strict) @@ -51,36 +46,15 @@ Logged in as {0}, return to index } #[get("/account/login/")] -pub async fn login() -> impl Responder { - HttpResponse::Ok().body(r#" - - - - -
-

Login

-
-
-
-
- - -
-
- - -
- -
-
-
-"#) +pub async fn login() -> actix_web::Result { + Ok(NamedFile::open("static/account_login.html")?) } #[post("/account/new")] pub async fn sign_up_result( data: web::Data, - form: web::Form + form: web::Form, + tdata: web::Data> ) -> HttpResponse { println!("new user"); let mut um = data.user_manager.lock().unwrap(); @@ -93,44 +67,12 @@ pub async fn sign_up_result( } }; um.add_user(user.clone()); - HttpResponse::Created().body(format!(r#" - - - - -
-

Hello, {0}

-
-

-Hooray! You can log into you new account here -

-
-"#, user.username)) + HttpResponse::Created().body(tdata.handlebars.render("new_user_redirect", &btreemap! { + "user" => to_json(user.username) + }).unwrap()) } #[get("/account/new/")] -pub async fn sign_up() -> impl Responder { - HttpResponse::Ok().body(r#" - - - - -
-

Sign Up

-
-
-
-
- - -
-
- - -
- -
-
-
-"#) +pub async fn sign_up() -> actix_web::Result { + Ok(NamedFile::open("static/new_account.html")?) } diff --git a/src/html/boards.rs b/src/html/boards.rs index 879d2f3..3ff3940 100644 --- a/src/html/boards.rs +++ b/src/html/boards.rs @@ -1,7 +1,6 @@ use std::{ - time, - time::Duration, - path::PathBuf + path::PathBuf, + time::SystemTime }; use actix_web::{ @@ -14,59 +13,60 @@ use crate::{ boards::Board, users::{UserCookie, User} }, - api::types::* + api::types::*, + ThreadData }; +use handlebars::to_json; + +use maplit::btreemap; + +use super::TimeDerive; + #[get("/boards/")] pub async fn list_boards( + req: HttpRequest, data: web::Data, + tdata: web::Data> ) -> impl Responder { - let now = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap(); - let mut ab = false; + let cookie = req.cookie("auth"); + let user = if let Some(c) = cookie { + if let Ok(v) = UserCookie::parse(c.value()) { + data.user_manager.lock().unwrap().get_user_by_id(v.id) + } else { + None + } + } else { + None + }; let boards = (*data.boards.lock().unwrap()).clone(); - HttpResponse::Ok().body(format!( - r#" - - - - -
-
-

Board Index

-
- -
-New Board [+] - - -{} -
NameDescriptionLast Active
-"#, - boards.iter().map(|b| { - ab = !ab; - format!( - "{}{}{}", - if ab { "a" } else { "b" }, b.id, b.title, b.desc, - if let Some(a) = b.messages.borrow().last() { - format!("{:.2} minutes", now.checked_sub(Duration::from_secs(a.timestamp)).unwrap().as_secs()/60) - } else { - String::from("Never") - } - ) - }).collect::() - )) + let mut boards_json = to_json(&boards); + let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let boards_json = boards_json.as_array_mut().unwrap().iter_mut().zip(boards).map(|x| -> &serde_json::Value { + x.0.as_object_mut().unwrap().insert( + "time_meta".to_string(), + if let Some(t) = x.1.last_active { + to_json(std::time::Duration::from_secs(t).derive_time_since(now).map(|d| (format!("{:.2}", d.0), d.1))) + } else { + to_json::>(None) + } + ); + //println!("{:?}", x.0); + x.0 + }).collect::>(); + HttpResponse::Ok().body(tdata.handlebars.render("board_index", &btreemap!( + "boards" => to_json(boards_json), + "user" => to_json(user) + )).unwrap()) } #[get("/board/{board_id}/")] pub async fn get_board( req: HttpRequest, + tdata: web::Data>, data: web::Data, web::Path(board_id): web::Path ) -> HttpResponse { - let mut ab = false; - let now = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap(); let boards = data.boards.lock().unwrap(); let cookie = req.cookie("auth"); let uid = if cookie.is_some() { @@ -75,58 +75,15 @@ pub async fn get_board( UserCookie::parse("null") }; if let Some(a) = Board::search(boards.iter(), board_id) { - HttpResponse::Ok().body(format!( - r#" - - - - - -
- -
-{} -
- -
-
- -{} -
-
-
- - - -
-
-
-"#, - a.title, - a.desc, - a.messages.borrow().iter().map(|x| { - ab = !ab; - format!( - "{}{}{}", - if ab { "a" } else { "b" }, - { - let author = x.author.clone().into_inner(); - author.nick.clone().or_else(|| Some(author.username.clone())).unwrap() - }, - x.text.replace('\n', "
"), - format!("{:.2} min.", now.checked_sub(Duration::from_secs(x.timestamp)).unwrap().as_secs()/60)//x.timestamp - ) - }).collect::(), - board_id, - match uid { - Ok(a) => a.id.to_string(), - Err(_e) => "null".to_string() - } - )) + let data = btreemap!( + "messages" => to_json(&a.messages), + "user" => match uid { + Ok(a) => to_json(data.user_manager.lock().unwrap().get_user_by_id(a.id)), + Err(_) => to_json("null") + }, + "board" => to_json(a) + ); + HttpResponse::Ok().body(tdata.handlebars.render("board", &data).unwrap()) } else { HttpResponse::NotFound().body("That board could not be found") } @@ -150,11 +107,12 @@ pub async fn board_post( } }; let mut mf = data.msg_factory.lock().unwrap(); - let b = data.boards.lock().unwrap(); - let board = b.iter().find(|x| (x.id == form.board_id)); + let mut b = data.boards.lock().unwrap(); + let mut board = b.iter_mut().find(|x| (x.id == form.board_id)); let um = data.user_manager.lock().unwrap(); match board { - Some(a) => { + Some(ref mut a) => { + //let a: &mut Board = board.unwrap(); let msg = mf.create_message({ let user = um.get_user_by_id(form.user_id).ok_or_else(|| { HttpResponse::NotFound().body("Not Found: User does not exist") @@ -166,6 +124,8 @@ pub async fn board_post( } }, form.content.clone()); println!("new message"); + //println!("board: {{ id: {}, name: {} }}{:?}", a.id, a.title, msg); + //let a = a.clone().add_message(msg); a.add_message(msg); Ok(HttpResponse::SeeOther().header("location", format!("/board/{}/", a.id)).finish()) }, @@ -198,29 +158,7 @@ pub async fn new_board( return HttpResponse::BadRequest().body("Bad Request: User does not exist"); } - HttpResponse::Ok().body(r#" - - - - -
-

New Board

-
-
-
-
-
- -
-
- - -
- -
-
-
-"#) + HttpResponse::Ok().body(include_str!("../../static/new_board.html")) } #[post("/boards/new")] diff --git a/src/html/index.css b/src/html/index.css deleted file mode 100644 index 06a0f8b..0000000 --- a/src/html/index.css +++ /dev/null @@ -1,158 +0,0 @@ -body { - margin: unset !important; - padding: unset; - max-width: unset; -} - -.bar { - padding: 0.5em; - background-color: #111111; - z-index: 1; - display: flex; - flex-direction: row; -} - -.bar > div { - flex-grow: 1; - align-self: center; -} - -.bar > div > h1 { - font-size: xx-large; - margin: unset; -} - -.bar > div > p { - margin-left: auto; - margin-right: 0; - margin-right: 2em; -} - -.bar > h1 { - width: fit-content; - height: fit-content; -} - -.bar > h1 > a { - font-size: 0.5em; - vertical-align: middle; -} - -#login-status { - display: contents; -} - -#description { - color: #777777; -} - -#title { - width: 100%; - height: 3em; - position: inherit; - top: 0; - left: 0; - margin-bottom: 1rem; - padding-left: 1em; -} - -#title > h1 { - font-size: 2.5em !important; -} - -#nav { - width: 3em; - position: fixed; - right: 0; - top: 0; - margin-left: 3rem; - height: 100%; -} - -.arrow { - width: 2.5rem; - background-color: #07a6ea; - color: white; - padding: 0.25rem; - border-radius: 0.5em; -} - -.orange-arrow { - width: 2.5rem; - color: orange; - padding: 0.25rem; - border-radius: 0.5em; -} - -.down { - transform: rotate(180deg); - -webkit-transform:rotate(180deg); - -moz-transform: rotate(180deg); - -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); -} - -#navbar > img.arrow.down { - unset: top; - bottom: 0; - margin-bottom: 0.5em; -} - -#navbar > img.arrow { - position: relative; -} - -.message { - margin-top: 0.5em; - margin-bottom: 0.5em; -} - -.message.right { -} -.message.left { -} - -.vertical-center { - margin: 0; - position: absolute; - top: 50%; - -ms-transform: translateY(-50%); - transform: translateY(-50%); -} - -table { - font-size: 1em; -} - -table, th, td { - border-collapse: collapse; -} - -th { - text-align: left; - margin-bottom: 1em; -} - -tr.a { - background-color: #3A3A3A; -} -tr.b { - background-color: inherit; -} - -.auth { - margin: auto; - width: 20em; - display: flex; - align-items: center; - justify-content: center; - padding: 1rem; - border: 2px solid white; - border-radius: 5px; - background-color: #3A3A3A; -} - -#index { - padding: 3em; - padding-top: 0; -} diff --git a/src/html/mod.rs b/src/html/mod.rs index e11dea5..92bd974 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -1,2 +1,22 @@ pub mod accounts; pub mod boards; + +use std::time::Duration; + +trait TimeDerive { + fn derive_time_since(&self, time: Duration) -> Option<(f64, String)>; +} + +impl TimeDerive for Duration { + fn derive_time_since(&self, time: Duration) -> Option<(f64, String)> { + time.checked_sub(*self).map(|a| { + match a.as_secs() { + t if t > 604800 => (t as f64/60.0/60.0/24.0/7.0, "weeks".to_string()), + t if t > 86400 => (t as f64/60.0/60.0/24.0, "days".to_string()), + t if t > 3600 => (t as f64/60.0/60.0, "hours".to_string()), + t if t > 120 => (t as f64/60.0, "mins".to_string()), + t => (t as f64, "secs".to_string()) + } + }) + } +} diff --git a/src/html/navbar.html b/src/html/navbar.html deleted file mode 100644 index a42e963..0000000 --- a/src/html/navbar.html +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/src/lib/boards.rs b/src/lib/boards.rs index a74b8c2..78acd35 100644 --- a/src/lib/boards.rs +++ b/src/lib/boards.rs @@ -13,8 +13,6 @@ use super::{ messages::Message }; -//use colored::*; - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Board { pub title: String, @@ -23,15 +21,22 @@ pub struct Board { pub creation: u64, pub id: u32, pub path: String, - pub owner: u32 + pub owner: u32, + pub last_active: Option } impl Board { pub fn open

(path: P) -> Result where P: AsRef { - match File::open(&path) { + let mut b: std::result::Result = match File::open(&path) { Ok(a) => Ok(serde_json::from_reader(a).unwrap()), Err(e) => Err(e) + }; + if let Ok(ref mut b) = b { + if b.last_active.is_none() { + b.last_active = b.messages.borrow().last().map(|x| x.timestamp); + } } + b } pub fn search<'a, I>( @@ -44,8 +49,10 @@ impl Board { iter.find(|x| x.id == id) } - pub fn add_message(&self, msg: Message) { + pub fn add_message(&mut self, msg: Message) -> &Board { + self.last_active = Some(msg.timestamp); self.messages.borrow_mut().push(msg); + self } } @@ -89,21 +96,10 @@ impl BoardFactory { creation: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(), id: self.assign_id(), path: path.to_str().unwrap().to_string(), - owner: creator + owner: creator, + last_active: None }; Ok(b) - /* - println!("board path: {:?}", path); - fs::create_dir_all(&path).unwrap(); - path.push("board.json"); - Board::open(&path).or_else(|_e| -> Result { - if !path.exists() { - let mut file = File::create(&path).unwrap(); - file.write_all(serde_json::to_string(&b).unwrap().as_bytes())?; - } - Board::open(path.parent().unwrap()) - }) - */ } } diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 0bc428b..a2f262d 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -58,11 +58,7 @@ impl FactoryFactory { pub fn build(&self) -> (BoardFactory, MessageFactory, UserManager) { (BoardFactory::new(self.data_dir.clone()), - MessageFactory { - next_id: 0, - last_write: None, - data_dir: self.data_dir.clone() - }, + MessageFactory::new(self.data_dir.clone()), UserManager::new(self.data_dir.clone())) } } diff --git a/src/main.rs b/src/main.rs index 0b0a644..85e8a29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::{ ReadDir }, path::Path, + error::Error }; use actix_web::{ @@ -17,8 +18,7 @@ use lib::{ *, Archiveable, boards::{ - Board, - BoardFactory + Board, BoardFactory }, users::UserManager, messages::MessageFactory @@ -33,62 +33,34 @@ use html::{ boards::* }; -/* -struct PageBuilder { - body: &'static str, - style: Option, - table_labels: Option +use tokio::{ + sync::{oneshot, watch}, + net::{ + UnixStream, UnixListener, + }, + runtime::Runtime, + select +}; + +use handlebars::Handlebars; +use console::style; +use maplit::btreemap; + +pub struct ThreadData<'a> { + handlebars: Handlebars<'a> } -impl PageBuilder { - fn new(text: &'static str) -> PageBuilder { - PageBuilder { - body: text, - style: None, - table_labels: None - } - } - - fn build(self, style: S, table_labels: S) -> String where S: AsRef { - String::from(self.body) - .replacen("{}", style.as_ref(), 1) - .replacen("{}", table_labels.as_ref(), 1) - } -} -*/ +use simple_logger::SimpleLogger; #[get("/")] -async fn index() -> impl Responder { - HttpResponse::Ok().body(format!(r#" - - - - -

Boarders v{}

-

-

What is it?

-Boarders is an easily deployable message board written in Rust. -It is extremely efficient on both the frontend and backend. -On the frontent all pages are static and (at this time) have no JS, only CSS/HTML. -The backend uses the 5th fastest web framework, Actix, according to techempower.com. -Which ensures that response times will always be snappy. -

-

-


-

Rules

-
    -
  1. No NSFW
    -At least until NSFW channels are implemented
  2. -
  3. Don't spam boards or user accounts
    -It's just anoying, and if it gets too bad I may have to hard reset everything
    -Don't ruin the fun for everyone
  4. -
  5. Have Fun!
    Pretty much anything goes, but if people don't like you, that's your fault
  6. -
-

-

------[Get Started]------

- - -"#, env!("CARGO_PKG_VERSION"))) +async fn index( + tdata: web::Data> +) -> impl Responder { + HttpResponse::Ok().body( + tdata.handlebars.render("welcome", &btreemap!( + "version" => env!("CARGO_PKG_VERSION").to_string() + )).unwrap() + ) } #[get("/debug/")] @@ -100,29 +72,40 @@ async fn debug( #[actix_web::main] async fn main() -> std::io::Result<()> { + /* quick and dirty logging */ + SimpleLogger::new().init().unwrap(); + println!("Initializing..."); + /* + * TODO: config files + * TODO: allow host to specify the data dir via a config file + * why? why not? maybe you want multiple servers running on + * the same machine + * TODO: allow host to specify config file via cmd line (overrides default name) + */ let data_dir = Path::new("data/"); let mut pwd = env::current_dir().unwrap(); - let board_files: ReadDir = match fs::read_dir({ + /* open/create the directory where board data is placed */ + let board_files: ReadDir = fs::read_dir({ pwd.push(data_dir); pwd.push("boards/"); &pwd - }) { - Ok(a) => a, - Err(_e) => { - fs::create_dir_all(&pwd).unwrap(); - fs::read_dir(pwd).unwrap() - } - }; + }).unwrap_or({ + fs::create_dir_all(&pwd).unwrap(); + fs::read_dir(pwd).unwrap() + }); let mut boards: Vec = Vec::new(); + /* + * Iterate over all the files inside the board data dir and + * open/create them. Will panic! if there is an error opening + * a board + */ for file in board_files.map(|x| x.unwrap()) { - let mut path = file.path().to_path_buf(); - //let meta = fs::metadata(&path).unwrap(); - let ftype = file.file_type().unwrap(); - if ftype.is_dir() { + if file.file_type().unwrap().is_dir() { + let mut path = file.path().to_path_buf(); path.push("board.json"); println!("loading board: {:?}", path); boards.push( @@ -133,7 +116,9 @@ async fn main() -> std::io::Result<()> { } } + /* sort boards by id so they display correctly on the board index */ boards.sort_by(|a,b| a.id.cmp(&b.id)); + println!("{}", style(format!("Found and loaded {} boards!", boards.len())).green()); let mut board_data = std::path::PathBuf::new(); board_data.push(&data_dir); @@ -142,6 +127,7 @@ async fn main() -> std::io::Result<()> { board_data.push("boards/"); bf.set_data_dir(board_data); + /* Initialize all the the shared data crap. copy/pasting code time!! */ let web_data = web::Data::new(AppState { boards: Mutex::new(boards.clone()), board_factory: Mutex::new(BoardFactory::load_state({ @@ -164,39 +150,65 @@ async fn main() -> std::io::Result<()> { }).unwrap_or(um)) }); let wb = web_data.clone(); + println!("{}", style("Loaded/Created application data files!").green()); - println!("Finished initialization"); + println!("{}", style("Finished initialization").green().bold()); println!("Starting server..."); let server = HttpServer::new(move || { App::new() - .app_data(web_data.clone()) - .service(index) - .service(debug) - .service(list_boards) - .service(login) - .service(auth_html) - .service(sign_up) - .service(sign_up_result) - .service(new_board) - .service(new_board_result) - .service(get_board) - .service(board_post) - .service( - web::scope("/api") - .service(api::boards::new) - .service(api::boards::list) - .service(api::messages::new) - .service(api::users::new) - .service(api::users::list) - .service(api::users::get) - .service(api::users::auth)) + .data(ThreadData { + handlebars: { + let mut h = Handlebars::new(); + /* + * TODO: allow user to specify wether or not templates should be + * loaded dynamically or statically, probably via environment + * variable at compile time + */ + /* register handlebars partials here */ + h.register_partial("login_status", include_str!("res/snippets/login_status.hbs")).unwrap(); + h.register_partial("board_bar", include_str!("res/snippets/board_bar.hbs")).unwrap(); + /* register handlebars templates here */ + h.register_template_string("board_index", include_str!("res/board_index.hbs")).unwrap(); + h.register_template_string("welcome", include_str!("res/welcome.hbs")).unwrap(); + h.register_template_string("auth", include_str!("res/auth.hbs")).unwrap(); + h.register_template_string("board", include_str!("res/board.hbs")).unwrap(); + h + } + }).app_data(web_data.clone()) + .service(index) + .service(debug) + .service(list_boards) + .service(login) + .service(auth_html) + .service(sign_up) + .service(sign_up_result) + .service(new_board) + .service(new_board_result) + .service(get_board) + .service(board_post) + .service( + /* add all api services under the /api/ scope */ + web::scope("/api") + .service(api::boards::new) + .service(api::boards::list) + .service(api::messages::new) + .service(api::users::new) + .service(api::users::list) + .service(api::users::get) + .service(api::users::auth)) + /* serve all static files inside ../static/ */ .service(actix_files::Files::new("/static/", "./static/").show_files_listing()) - }).bind("127.0.0.1:8080")? - .run(); + }).bind("127.0.0.1:8080")?.run(); println!("Started server"); println!("{}v{}", include_str!("res/banner"), env!("CARGO_PKG_VERSION")); + + /* + * Program will block here until it is Ctrl-C 'd on the command line + * TODO: Block until cmd line exit OR client shutdown command + */ let res = server.await; + println!("\nExiting..."); /* * Note to self: handle all errors as much as possible @@ -214,28 +226,35 @@ async fn main() -> std::io::Result<()> { } wb.board_factory - .lock() - .unwrap() - .save_state("factory.json") - .unwrap_or_else( - |x| { println!("Could not save user factory state: {}", x) } - ); + .lock() + .unwrap() + .save_state("factory.json") + .unwrap_or_else( + |x| { println!("Could not save user factory state: {}", x) } + ); wb.msg_factory - .lock() - .unwrap() - .save_state("message_factory.json") - .unwrap_or_else( - |x| { println!("Could not save message factory state: {}", x) } - ); + .lock() + .unwrap() + .save_state("message_factory.json") + .unwrap_or_else( + |x| { println!("Could not save message factory state: {}", x) } + ); wb.user_manager - .lock() - .unwrap() - .save_state("users.json") - .unwrap_or_else( - |x| { println!("Could not save user data: {}", x) } - ); + .lock() + .unwrap() + .save_state("users.json") + .unwrap_or_else( + |x| { println!("Could not save user data: {}", x) } + ); + + /* send shutdown signal to all unix socket threads */ + println!("Shutting down client threads..."); + if s_tx.send(AppPhase::Shutdown).is_err() { + println!("{}", style("Could not update the app phase to Shutdown").red().bold()); + } + rt.shutdown_timeout(std::time::Duration::from_secs(10)); println!("Done!"); res } diff --git a/src/res/auth.hbs b/src/res/auth.hbs new file mode 100644 index 0000000..69a0e25 --- /dev/null +++ b/src/res/auth.hbs @@ -0,0 +1,19 @@ + + + + + + +
+ {{#if user.nick}} +

Hello, {{user.nick}}

+ {{else}} +

Hello, {{user.nickname}}

+ {{/if}} +
+

+ Logged in as {{user.username}}, return to index +

+
+ + diff --git a/src/res/banner b/src/res/banner index 801913f..847ae29 100644 --- a/src/res/banner +++ b/src/res/banner @@ -1,5 +1,6 @@ - ____ __ - / __ ) ____ ____ _ _____ ____/ /___ _____ _____ - / __ |/ __ \ / __ `// ___// __ // _ \ / ___// ___/ - / /_/ // /_/ // /_/ // / / /_/ // __// / (__ ) -/_____/ \____/ \__,_//_/ \__,_/ \___//_/ /____/ + ____ __ + / __ )\ ____ ____ _ _____ ____/ /\__ _____ _____ + / __ | / __ \ / __ `/\/ ___// __ // _ \ / ___// ___/\ + / /_/ / / /_/ // /_/ / / / _// /_/ // __/\/ / _/(__ )\/ +/_____/ /\____/ \__,_/ /_/ / \__,_/ \___/ /_/ / /____/\) +\_____\/ \___\/ \___\/\_\/ \___\/ \__\/\_\/ \____\/ diff --git a/src/res/board.hbs b/src/res/board.hbs new file mode 100644 index 0000000..f7f99a2 --- /dev/null +++ b/src/res/board.hbs @@ -0,0 +1,42 @@ + + + + + + + {{> board_bar board=board}} +
+ + + {{#each messages as | msg |}} + + + + + + {{/each}} +
+ {{#if msg.author.nick}} +
+ {{msg.author.nick}}
+
{{author.username}}
+
+ {{else}} + {{msg.author.username}} + {{/if}} +
+ {{msg.text}} + + {{msg.timestamp}} +
+
+
+ + + +
+ +
+
+ + diff --git a/src/res/board_index.hbs b/src/res/board_index.hbs new file mode 100644 index 0000000..585c722 --- /dev/null +++ b/src/res/board_index.hbs @@ -0,0 +1,31 @@ + + + + + + +
+
+

Board Index

+
+ {{> login_status}} +
+
+ New Board [+] + + + {{#each boards}} + + + + {{/each}} +
NameDescriptionLast Active
{{title}}{{desc}} + {{#if time_meta}} + {{time_meta.0}} {{time_meta.1}} + {{else}} + Never + {{/if}} +
+
+ + diff --git a/src/res/new_account_redirect.hbs b/src/res/new_account_redirect.hbs new file mode 100644 index 0000000..54fe1ca --- /dev/null +++ b/src/res/new_account_redirect.hbs @@ -0,0 +1,15 @@ + + + + + + +
+

Hello, {{name}}

+
+
+

+ Hooray! You can log into you new account here +

+ + diff --git a/src/res/snippets/board_bar.hbs b/src/res/snippets/board_bar.hbs new file mode 100644 index 0000000..5c35688 --- /dev/null +++ b/src/res/snippets/board_bar.hbs @@ -0,0 +1,11 @@ +
+
+

+ {{board.title}} | <- Back to index +

+
+
+ {{board.desc}} +
+ {{> login_status}} +
diff --git a/src/res/snippets/login_status.hbs b/src/res/snippets/login_status.hbs new file mode 100644 index 0000000..ddda45e --- /dev/null +++ b/src/res/snippets/login_status.hbs @@ -0,0 +1,17 @@ + diff --git a/src/res/welcome.hbs b/src/res/welcome.hbs new file mode 100644 index 0000000..c2229b2 --- /dev/null +++ b/src/res/welcome.hbs @@ -0,0 +1,50 @@ + + + + + +

+
+
+          
+    ____                             __
+   / __ )\ ____   ____ _  _____ ____/ /\__    _____ _____
+  / __  | / __ \ / __ `/\/ ___// __  // _ \  / ___// ___/\
+ / /_/ / / /_/ // /_/ / / / _// /_/ //  __/\/ / _/(__  )\/
+/_____/ /\____/ \__,_/ /_/ /  \__,_/ \___/ /_/ / /____/\)
+\_____\/  \___\/ \___\/\_\/    \___\/ \__\/\_\/  \____\/
+          
+        
+
+ v{{version}} +
+
+

+

+

What is it?

+ Boarders is an easily deployable message board written in Rust. + It is extremely efficient on both the frontend and backend. + On the frontent all pages are static and (at this time) have no JS, only CSS/HTML. + The backend uses the 5th fastest web framework, Actix, according to + techempower.com. + Which ensures that response times will always be snappy. +

+

+


+

Rules

+
    +
  1. No NSFW
    + At least until NSFW channels are implemented
  2. +
  3. + Don't spam boards or user accounts
    + It's just anoying, and if it gets too bad I may have to hard reset everything
    + Don't ruin the fun for everyone +
  4. +
  5. Have Fun!
    + Pretty much anything goes, but if people don't like you, that's your fault +
  6. +
+

+

------[Get Started]------

+ + diff --git a/static/account_login.html b/static/account_login.html new file mode 100644 index 0000000..b4bfeed --- /dev/null +++ b/static/account_login.html @@ -0,0 +1,25 @@ + + + + + + +
+

Login

+
+
+
+
+
+ + +
+
+ + +
+ +
+
+ + diff --git a/static/index.css b/static/index.css index 06a0f8b..0f1aec1 100644 --- a/static/index.css +++ b/static/index.css @@ -105,6 +105,22 @@ body { .message { margin-top: 0.5em; margin-bottom: 0.5em; + border-bottom: 1px solid #555; +} + +@keyframes msg-hover { + from { + background-color: inherit; + } + to { + background-color: #5a5a5a + } +} + +tr.message:hover { + animation-duration: 0.2s; + animation-name: msg-hover; + background-color: #5a5a5a } .message.right { @@ -156,3 +172,26 @@ tr.b { padding: 3em; padding-top: 0; } + +@keyframes board-hover { + from { + background-color: inherit; + } + to { + background-color: #444; + } +} + +tr.board { + border-bottom: 1px solid #555; +} + +tr.board:nth-child(1n+1) { + font-weight: lighter; +} + +tr.board:hover { + animation-duration: 0.25s; + animation-name: board-hover; + background-color: #444; +} diff --git a/static/new_account.html b/static/new_account.html new file mode 100644 index 0000000..94a3c79 --- /dev/null +++ b/static/new_account.html @@ -0,0 +1,24 @@ + + + + + + +
+

Sign Up

+
+
+
+
+ + +
+
+ + +
+ +
+
+ + diff --git a/static/new_board.html b/static/new_board.html new file mode 100644 index 0000000..7f10ed0 --- /dev/null +++ b/static/new_board.html @@ -0,0 +1,28 @@ + + + + + + +
+

New Board

+
+
+
+
+
+
+ +
+
+ + +
+ +
+
+ + +
+ +