169 lines
5 KiB
Rust
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))
|
|
}
|