2m2d/src/main.rs

105 lines
3 KiB
Rust

mod users;
use axum::extract::Extension;
use axum::response::Html;
use axum::response::IntoResponse;
use axum::response::Redirect;
use axum::response::Response;
use axum::{
routing::{get, post},
Router,
};
use axum_sessions::extractors::ReadableSession;
use axum_sessions::{async_session::MemoryStore, SessionLayer};
use sqlx::postgres::PgPoolOptions;
use std::sync::Arc;
use tera::Tera;
use thiserror::Error as ThisError;
use tower::ServiceBuilder;
#[derive(ThisError, Debug)]
pub enum Error {
Database(#[from] sqlx::Error),
Tera(#[from] tera::Error),
Tokio(#[from] tokio::task::JoinError),
Pbkdf2(pbkdf2::password_hash::Error),
Session(#[from] axum_sessions::async_session::serde_json::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#?}", self)
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
self.to_string().into_response()
}
}
// We need to implement this manually because pbkdf2's Error type does not meet the bounds required
// for `thiserror`
impl From<pbkdf2::password_hash::Error> for Error {
fn from(e: pbkdf2::password_hash::Error) -> Self {
Self::Pbkdf2(e)
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let pool = Arc::new(
PgPoolOptions::new()
.connect("postgresql://tmtd@192.168.1.133/tmtd")
.await?,
);
sqlx::query("create table if not exists users(id serial primary key, username text not null unique, password_hash text not null)").execute(&*pool).await?;
let mut tera = Arc::new(Tera::new("templates/**/*.html")?);
{
Arc::get_mut(&mut tera).unwrap().full_reload()?;
}
let store = MemoryStore::new();
// TODO: make this configurable
let secret = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let session_layer = SessionLayer::new(store, secret).with_cookie_name("2m2d_session");
let app = Router::new()
.route("/", get(homepage))
.route("/register", post(users::create_user))
.route("/register", get(users::register_form))
.route("/login", get(users::login_form))
.route("/login", post(users::login_backend))
.layer(
ServiceBuilder::new()
.layer(Extension(pool))
.layer(Extension(tera))
.layer(session_layer),
);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
Ok(())
}
async fn homepage(
session: ReadableSession,
Extension(tera): Extension<Arc<Tera>>,
) -> Result<Html<String>, Response> {
if let Some(_username) = session.get::<String>("logged_in_as") {
let rendered = tera
.render("home.html", &tera::Context::default())
.map_err(|e| Error::from(e).into_response())?;
return Ok(Html(rendered));
}
Err(Redirect::to("/login").into_response())
}