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.
This commit is contained in:
parent
f10be383f9
commit
2404b04e76
19
Cargo.toml
19
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"
|
||||
|
||||
@ -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"))
|
||||
|
||||
3
src/bin/boarders_client.rs
Normal file
3
src/bin/boarders_client.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Client");
|
||||
}
|
||||
@ -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<crate::ThreadData<'_>>,
|
||||
data: web::Data<AppState>,
|
||||
form: web::Form<CryptoUserForm>
|
||||
) -> 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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head><body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Hello, {0}</h1>
|
||||
</div><div id="index">
|
||||
<p>
|
||||
Logged in as {0}, return to <a href="/boards/">index</a>
|
||||
</p>
|
||||
</body></html></div></body></html>
|
||||
"#, 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 <a href="/boards/">index</a>
|
||||
}
|
||||
|
||||
#[get("/account/login/")]
|
||||
pub async fn login() -> impl Responder {
|
||||
HttpResponse::Ok().body(r#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head><body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Login</h1>
|
||||
</div><div id="index">
|
||||
<div class="auth">
|
||||
<form action="/account/auth" method="post">
|
||||
<div>
|
||||
<label for="username">Username: </label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password: </label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</div>
|
||||
</body></html></div></body></html>
|
||||
"#)
|
||||
pub async fn login() -> actix_web::Result<NamedFile> {
|
||||
Ok(NamedFile::open("static/account_login.html")?)
|
||||
}
|
||||
|
||||
#[post("/account/new")]
|
||||
pub async fn sign_up_result(
|
||||
data: web::Data<AppState>,
|
||||
form: web::Form<CryptoUserForm>
|
||||
form: web::Form<CryptoUserForm>,
|
||||
tdata: web::Data<ThreadData<'_>>
|
||||
) -> 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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head><body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Hello, {0}</h1>
|
||||
</div><div id="index">
|
||||
<p>
|
||||
Hooray! You can log into you new account <a href="/account/login/">here</a>
|
||||
</p>
|
||||
</body></html></div></body></html>
|
||||
"#, 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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head><body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Sign Up</h1>
|
||||
</div><div id="index">
|
||||
<div class="auth">
|
||||
<form action="/account/new" method="post">
|
||||
<div>
|
||||
<label for="username">Username: </label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password: </label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<input type="submit" value="Sign Up">
|
||||
</form>
|
||||
</div>
|
||||
</body></html></div></body></html>
|
||||
"#)
|
||||
pub async fn sign_up() -> actix_web::Result<NamedFile> {
|
||||
Ok(NamedFile::open("static/new_account.html")?)
|
||||
}
|
||||
|
||||
@ -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<AppState>,
|
||||
tdata: web::Data<ThreadData<'_>>
|
||||
) -> 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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head><body>
|
||||
<div id="title" class="bar">
|
||||
<div>
|
||||
<h1>Board Index</h1>
|
||||
</div>
|
||||
<div id="login-status">
|
||||
<p><a href="/account/login/">Login</a>/<a href="/account/new/">Sign Up</a></p>
|
||||
</div>
|
||||
</div><div id="index">
|
||||
<a href="/boards/new/" style="margin-bottom: 0.5em;">New Board [+]</a>
|
||||
<table style="width: 100%;" cellpadding=8px>
|
||||
<tr><th>Name</th><th>Description</th><th>Last Active</th></tr>
|
||||
{}
|
||||
</table></body></html></div></body></html>
|
||||
"#,
|
||||
boards.iter().map(|b| {
|
||||
ab = !ab;
|
||||
format!(
|
||||
"<tr class=\"{}\"><td><a href=\"/board/{}/\">{}</a></td><td>{}</td><td>{}</td></tr>",
|
||||
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::<String>()
|
||||
))
|
||||
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::<Option<u8>>(None)
|
||||
}
|
||||
);
|
||||
//println!("{:?}", x.0);
|
||||
x.0
|
||||
}).collect::<Vec<&serde_json::Value>>();
|
||||
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<ThreadData<'_>>,
|
||||
data: web::Data<AppState>,
|
||||
web::Path(board_id): web::Path<u32>
|
||||
) -> 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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
<div><h1>
|
||||
{} | <a href="/boards/"><- Back to index</a>
|
||||
</h1></div>
|
||||
<div id="description">
|
||||
{}
|
||||
</div>
|
||||
<div id="login-status">
|
||||
<p><a href="/account/login/">Login</a>/<a href="/account/new/">Sign Up</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="index">
|
||||
<table style="width: 100%;" cellpadding=8px>
|
||||
{}
|
||||
</table>
|
||||
<form action="/boards/post" method="post">
|
||||
<label for="content">Content:</label><br/>
|
||||
<input type="hidden" id="board_id" name="board_id" value="{}">
|
||||
<input type="hidden" id="user_id" name="user_id" value="{}">
|
||||
<textarea name="content" id="content" rows=12 cols=50 placeholder="Enter message here..."></textarea>
|
||||
<br/><input type="submit" value="Post Message" style="margin: 0.5em;">
|
||||
</form>
|
||||
</div></body></html>
|
||||
"#,
|
||||
a.title,
|
||||
a.desc,
|
||||
a.messages.borrow().iter().map(|x| {
|
||||
ab = !ab;
|
||||
format!(
|
||||
"<tr class=\"message {}\"><td>{}</td><td>{}</td><td>{}</td></tr>",
|
||||
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', "<br>"),
|
||||
format!("{:.2} min.", now.checked_sub(Duration::from_secs(x.timestamp)).unwrap().as_secs()/60)//x.timestamp
|
||||
)
|
||||
}).collect::<String>(),
|
||||
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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head><body>
|
||||
<div id="title" class="bar">
|
||||
<h1>New Board</h1>
|
||||
</div><div id="index">
|
||||
<div class="auth">
|
||||
<form action="/boards/new" method="post">
|
||||
<div>
|
||||
<label for="name">Board Title: </label><br>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description">Board Description: </label>
|
||||
<input type="text" id="description" name="description" required>
|
||||
</div>
|
||||
<input type="submit" value="Create Board">
|
||||
</form>
|
||||
</div>
|
||||
</body></html></div></body></html>
|
||||
"#)
|
||||
HttpResponse::Ok().body(include_str!("../../static/new_board.html"))
|
||||
}
|
||||
|
||||
#[post("/boards/new")]
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
<div id="nav" class="bar">
|
||||
<img class="arrow up" src="https://static.solow.xyz/boards/arrow.svg">
|
||||
<img class="arrow down" style="bottom: 0; position: absolute; right: 0; margin-right: 9; margin-bottom: 28;" src="https://static.solow.xyz/boards/arrow.svg">
|
||||
<div class="vertical-center">
|
||||
<!--<img class="orange-arrow right" src="https://static.solow.xyz/boards/arrow.svg">-->
|
||||
<a href="../">
|
||||
Next<br>
|
||||
Page
|
||||
</a>
|
||||
<hr>
|
||||
<a href="../">
|
||||
Prev<br>
|
||||
Page
|
||||
</a>
|
||||
<!--<img class="orange-arrow left" src="https://static.solow.xyz/boards/arrow.svg">-->
|
||||
</div>
|
||||
</div>
|
||||
@ -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<u64>
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn open<P>(path: P) -> Result<Board> where P: AsRef<Path> {
|
||||
match File::open(&path) {
|
||||
let mut b: std::result::Result<Board, std::io::Error> = 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<Board> {
|
||||
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())
|
||||
})
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
235
src/main.rs
235
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<String>,
|
||||
table_labels: Option<String>
|
||||
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<S>(self, style: S, table_labels: S) -> String where S: AsRef<str> {
|
||||
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#"
|
||||
<html><head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Boarders v{}</h1>
|
||||
<p>
|
||||
<h2>What is it?</h2>
|
||||
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 <a href="https://www.techempower.com/benchmarks/">techempower.com</a>.
|
||||
Which ensures that response times will always be snappy.
|
||||
</p>
|
||||
<p>
|
||||
<hr>
|
||||
<h2>Rules</h2>
|
||||
<ol>
|
||||
<li>No NSFW<br>
|
||||
At least until NSFW channels are implemented</li>
|
||||
<li>Don't spam boards or user accounts<br>
|
||||
It's just anoying, and if it gets too bad I may have to hard reset everything<br>
|
||||
Don't ruin the fun for everyone</li>
|
||||
<li>Have Fun!<br>Pretty much anything goes, but if people don't like you, that's your fault</li>
|
||||
</ol>
|
||||
</p>
|
||||
<h3 style="text-align: center;">------<a href="/boards/">[Get Started]</a>------</h3>
|
||||
</body>
|
||||
</html>
|
||||
"#, env!("CARGO_PKG_VERSION")))
|
||||
async fn index(
|
||||
tdata: web::Data<ThreadData<'_>>
|
||||
) -> 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<Board> = 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
|
||||
}
|
||||
|
||||
19
src/res/auth.hbs
Normal file
19
src/res/auth.hbs
Normal file
@ -0,0 +1,19 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
{{#if user.nick}}
|
||||
<h1>Hello, {{user.nick}}</h1>
|
||||
{{else}}
|
||||
<h1>Hello, {{user.nickname}}</h1>
|
||||
{{/if}}
|
||||
</div><div id="index">
|
||||
<p>
|
||||
Logged in as {{user.username}}, return to <a href="/boards/">index</a>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,5 +1,6 @@
|
||||
____ __
|
||||
/ __ ) ____ ____ _ _____ ____/ /___ _____ _____
|
||||
/ __ |/ __ \ / __ `// ___// __ // _ \ / ___// ___/
|
||||
/ /_/ // /_/ // /_/ // / / /_/ // __// / (__ )
|
||||
/_____/ \____/ \__,_//_/ \__,_/ \___//_/ /____/
|
||||
____ __
|
||||
/ __ )\ ____ ____ _ _____ ____/ /\__ _____ _____
|
||||
/ __ | / __ \ / __ `/\/ ___// __ // _ \ / ___// ___/\
|
||||
/ /_/ / / /_/ // /_/ / / / _// /_/ // __/\/ / _/(__ )\/
|
||||
/_____/ /\____/ \__,_/ /_/ / \__,_/ \___/ /_/ / /____/\)
|
||||
\_____\/ \___\/ \___\/\_\/ \___\/ \__\/\_\/ \____\/
|
||||
|
||||
42
src/res/board.hbs
Normal file
42
src/res/board.hbs
Normal file
@ -0,0 +1,42 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
{{> board_bar board=board}}
|
||||
<div id="index">
|
||||
<table style="width: 100%;" cellpadding=8px>
|
||||
<!--messages-->
|
||||
{{#each messages as | msg |}}
|
||||
<tr style="border-bottom: 1px solid #555" class="message">
|
||||
<td>
|
||||
{{#if msg.author.nick}}
|
||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
||||
{{msg.author.nick}}<br>
|
||||
<div style="color: #777777;">{{author.username}}</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{msg.author.username}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
{{msg.text}}
|
||||
</td>
|
||||
<td>
|
||||
{{msg.timestamp}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
<form action="/boards/post" method="post">
|
||||
<label for="content">Content:</label><br/>
|
||||
<input type="hidden" id="board_id" name="board_id" value="{{board.id}}">
|
||||
<input type="hidden" id="user_id" name="user_id" value="{{user.user_id}}">
|
||||
<textarea name="content" id="content" rows=12 cols=50 placeholder="Enter message here..."></textarea>
|
||||
<br/>
|
||||
<input type="submit" value="Post Message" style="margin: 0.5em;">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
31
src/res/board_index.hbs
Normal file
31
src/res/board_index.hbs
Normal file
@ -0,0 +1,31 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
<div>
|
||||
<h1>Board Index</h1>
|
||||
</div>
|
||||
{{> login_status}}
|
||||
</div>
|
||||
<div id="index">
|
||||
<a href="/boards/new/" style="margin-bottom: 0.5em;">New Board [+]</a>
|
||||
<table style="width: 100%;" cellpadding=8px>
|
||||
<tr><th>Name</th><th>Description</th><th>Last Active</th></tr>
|
||||
{{#each boards}}
|
||||
<tr class="board"><td><a href="/board/{{id}}/">{{title}}</a></td><td>{{desc}}</td>
|
||||
<td>
|
||||
{{#if time_meta}}
|
||||
{{time_meta.0}} {{time_meta.1}}
|
||||
{{else}}
|
||||
Never
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
15
src/res/new_account_redirect.hbs
Normal file
15
src/res/new_account_redirect.hbs
Normal file
@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Hello, {{name}}</h1>
|
||||
</div>
|
||||
<div id="index">
|
||||
<p>
|
||||
Hooray! You can log into you new account <a href="/account/login/">here</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
11
src/res/snippets/board_bar.hbs
Normal file
11
src/res/snippets/board_bar.hbs
Normal file
@ -0,0 +1,11 @@
|
||||
<div id="title" class="bar">
|
||||
<div>
|
||||
<h1>
|
||||
{{board.title}} | <a href="/boards/"><- Back to index</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="description">
|
||||
{{board.desc}}
|
||||
</div>
|
||||
{{> login_status}}
|
||||
</div>
|
||||
17
src/res/snippets/login_status.hbs
Normal file
17
src/res/snippets/login_status.hbs
Normal file
@ -0,0 +1,17 @@
|
||||
<div id="login-status">
|
||||
<p>
|
||||
{{#if user}}
|
||||
<a href="/account/dashboard/">
|
||||
{{#with user}}
|
||||
{{#if nick}}
|
||||
Welcome, {{nick}}
|
||||
{{else}}
|
||||
Welcome, {{username}}
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/account/login/">Login</a>/<a href="/account/new/">Sign Up</a>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
50
src/res/welcome.hbs
Normal file
50
src/res/welcome.hbs
Normal file
@ -0,0 +1,50 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
<div style="display: flex; align-items: center; flex-direction: column;">
|
||||
<pre style="font-size: 1.5rem; background-color: unset; overflow-x: unset; margin: unset; padding: unset;">
|
||||
<b>
|
||||
____ __
|
||||
/ __ )\ ____ ____ _ _____ ____/ /\__ _____ _____
|
||||
/ __ | / __ \ / __ `/\/ ___// __ // _ \ / ___// ___/\
|
||||
/ /_/ / / /_/ // /_/ / / / _// /_/ // __/\/ / _/(__ )\/
|
||||
/_____/ /\____/ \__,_/ /_/ / \__,_/ \___/ /_/ / /____/\)
|
||||
\_____\/ \___\/ \___\/\_\/ \___\/ \__\/\_\/ \____\/
|
||||
</b>
|
||||
</pre>
|
||||
<div style="font-size: 2rem;">
|
||||
v{{version}}
|
||||
</div>
|
||||
</div>
|
||||
</h1>
|
||||
<p>
|
||||
<h2>What is it?</h2>
|
||||
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
|
||||
<a href="https://www.techempower.com/benchmarks/">techempower.com</a>.
|
||||
Which ensures that response times will always be snappy.
|
||||
</p>
|
||||
<p>
|
||||
<hr>
|
||||
<h2>Rules</h2>
|
||||
<ol>
|
||||
<li>No NSFW<br>
|
||||
At least until NSFW channels are implemented</li>
|
||||
<li>
|
||||
Don't spam boards or user accounts<br>
|
||||
It's just anoying, and if it gets too bad I may have to hard reset everything<br>
|
||||
Don't ruin the fun for everyone
|
||||
</li>
|
||||
<li>Have Fun!<br>
|
||||
Pretty much anything goes, but if people don't like you, that's your fault
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
<h3 style="text-align: center;">------<a href="/boards/">[Get Started]</a>------</h3>
|
||||
</body>
|
||||
</html>
|
||||
25
static/account_login.html
Normal file
25
static/account_login.html
Normal file
@ -0,0 +1,25 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Login</h1>
|
||||
</div>
|
||||
<div id="index">
|
||||
<div class="auth">
|
||||
<form action="/account/auth" method="post">
|
||||
<div>
|
||||
<label for="username">Username: </label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password: </label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
24
static/new_account.html
Normal file
24
static/new_account.html
Normal file
@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
<h1>Sign Up</h1>
|
||||
</div><div id="index">
|
||||
<div class="auth">
|
||||
<form action="/account/new" method="post">
|
||||
<div>
|
||||
<label for="username">Username: </label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password: </label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<input type="submit" value="Sign Up">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
static/new_board.html
Normal file
28
static/new_board.html
Normal file
@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="title" class="bar">
|
||||
<h1>New Board</h1>
|
||||
</div>
|
||||
<div id="index">
|
||||
<div class="auth">
|
||||
<form action="/boards/new" method="post">
|
||||
<div>
|
||||
<label for="name">Board Title: </label><br>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description">Board Description: </label>
|
||||
<input type="text" id="description" name="description" required>
|
||||
</div>
|
||||
<input type="submit" value="Create Board">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user