hyaenidae/server/src/main.rs

169 lines
5 KiB
Rust

use actix_web::{
dev::HttpResponseBuilder,
http::header::{CacheControl, CacheDirective, ContentType, LastModified},
middleware::{Compress, Logger},
web, App, HttpResponse, HttpServer,
};
use hyaenidae_accounts::{Auth, Authenticated};
use sled::Db;
use std::{fmt, time::SystemTime};
mod accounts;
mod error;
use error::{Error, ResultExt, StateError};
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
const HOURS: u32 = 60 * 60;
const DAYS: u32 = 24 * HOURS;
#[actix_web::main]
async fn main() -> anyhow::Result<()> {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
env_logger::init();
let db = sled::open("sled/db-0-34")?;
let accounts_config = hyaenidae_accounts::Config {
toolkit_path: format!(
"/toolkit/{}",
hyaenidae_toolkit::templates::statics::toolkit_css.name
),
domain: "localhost".to_owned(),
key: vec![0; 64],
https: false,
pages: std::sync::Arc::new(accounts::Pages),
};
let state = State::new(&db);
let accounts_state = hyaenidae_accounts::state(&accounts_config, db)?;
HttpServer::new(move || {
let accounts_config = accounts_config.clone();
let accounts_state = accounts_state.clone();
App::new()
.wrap(Logger::default())
.wrap(Compress::default())
.data(state.clone())
.wrap(Auth(accounts_state.clone()))
.data(accounts_state)
.data(SystemTime::now())
.wrap(hyaenidae_accounts::cookie_middlware(&accounts_config))
.route("/", web::get().to(home))
.route("/404", web::get().to(not_found))
.route("/500", web::get().to(serve_error))
.route("/toolkit/{name}", web::get().to(toolkit))
.route("/static/{name}", web::get().to(statics))
.service(accounts::scope())
})
.bind("0.0.0.0:8084")?
.run()
.await?;
Ok(())
}
#[derive(Clone)]
struct State {
db: Db,
}
impl State {
fn new(db: &Db) -> Self {
State { db: db.clone() }
}
}
impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("State").field("db", &"Db").finish()
}
}
fn toolkit_path(name: &str) -> String {
format!("/toolkit/{}", name)
}
fn statics_path(name: &str) -> String {
format!("/static/{}", name)
}
async fn toolkit(path: web::Path<String>, startup: web::Data<SystemTime>) -> HttpResponse {
if let Some(file) = hyaenidae_toolkit::templates::statics::StaticFile::get(&path) {
return HttpResponse::Ok()
.set(LastModified(SystemTime::clone(&startup).into()))
.set(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(365 * DAYS),
CacheDirective::Extension("immutable".to_owned(), None),
]))
.set(ContentType(file.mime.clone()))
.body(file.content);
}
HttpResponse::NotFound().finish()
}
async fn statics(path: web::Path<String>, startup: web::Data<SystemTime>) -> HttpResponse {
if let Some(file) = templates::statics::StaticFile::get(&path) {
return HttpResponse::Ok()
.set(LastModified(SystemTime::clone(&startup).into()))
.set(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(365 * DAYS),
CacheDirective::Extension("immutable".to_owned(), None),
]))
.set(ContentType(file.mime.clone()))
.body(file.content);
}
HttpResponse::NotFound().finish()
}
async fn home(
state: web::Data<State>,
authenticated: Option<Authenticated>,
logout_args: Option<hyaenidae_accounts::LogoutPageArgs>,
) -> Result<HttpResponse, StateError> {
let logout_opt = logout_args.map(|args| hyaenidae_accounts::logout_page(args));
let authenticated_opt = authenticated.and_then(|a| logout_opt.map(|l| (a.user(), l)));
rendered(HttpResponse::Ok(), |cursor| {
templates::index(cursor, authenticated_opt)
})
.state(&state)
}
async fn not_found(state: web::Data<State>) -> Result<HttpResponse, StateError> {
rendered(HttpResponse::NotFound(), |cursor| {
templates::not_found(cursor)
})
.state(&state)
}
async fn serve_error(state: web::Data<State>) -> Result<HttpResponse, StateError> {
rendered(HttpResponse::InternalServerError(), |cursor| {
templates::error(cursor, "Hyaenidae encountered a problem".to_owned())
})
.state(&state)
}
fn rendered(
mut builder: HttpResponseBuilder,
f: impl FnOnce(&mut std::io::Cursor<Vec<u8>>) -> std::io::Result<()>,
) -> Result<HttpResponse, Error> {
let mut cursor = std::io::Cursor::new(vec![]);
(f)(&mut cursor).map_err(Error::Render)?;
let mut html = cursor.into_inner();
let len = minify_html::in_place(&mut html, &minify_html::Cfg { minify_js: false })?;
html.truncate(len);
Ok(builder
.content_type(mime::TEXT_HTML.essence_str())
.body(html))
}