Session powers

This commit is contained in:
Daniel Flanagan 2024-07-13 20:32:37 -05:00
parent b19f5dd31e
commit 6b0e87e8f8
8 changed files with 50 additions and 30 deletions

4
Cargo.lock generated
View file

@ -183,9 +183,9 @@ dependencies = [
[[package]] [[package]]
name = "axum-login" name = "axum-login"
version = "0.15.1" version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fbc0d7bd2577dda9aa9cac096e53b30342725d8eea5798169ff2537a214f45" checksum = "4012877d9672b7902aa6567960208756f68a09de81e988fa18fe369e92f90471"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",

View file

@ -20,7 +20,7 @@ panic = "abort"
[dependencies] [dependencies]
argon2 = { version = "0.5.3", features = ["std"] } argon2 = { version = "0.5.3", features = ["std"] }
axum-login = "0.15.1" axum-login = "0.15.3"
bincode = "1.3.3" bincode = "1.3.3"
chrono = { version = "0.4.38", features = ["serde"] } chrono = { version = "0.4.38", features = ["serde"] }
clap = { version = "4.5.4", features = ["derive", "env"] } clap = { version = "4.5.4", features = ["derive", "env"] }

View file

@ -1,17 +1,22 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use super::song::{Plan, Song, Verse}; use super::song::{Plan, Song, Verse};
#[derive(Serialize, Deserialize, Debug)]
pub struct PlaylistEntry { pub struct PlaylistEntry {
pub song: Song, pub song: Song,
pub plan_name: Option<String>, pub plan_name: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct PlaylistVerseRef { pub struct PlaylistVerseRef {
pub song_index: usize, pub song_index: usize,
pub map_verse_index: usize, pub map_verse_index: usize,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct Display { pub struct Display {
pub playlist: VecDeque<PlaylistEntry>, pub playlist: VecDeque<PlaylistEntry>,
current: PlaylistVerseRef, current: PlaylistVerseRef,

View file

@ -5,8 +5,9 @@ use std::{
}; };
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct Verse { pub struct Verse {
// pub background: String, // url // pub background: String, // url
pub content: String, pub content: String,
@ -21,6 +22,7 @@ impl Verse {
/// Sequence of verse names. /// Sequence of verse names.
pub type Plan = VecDeque<String>; pub type Plan = VecDeque<String>;
#[derive(Serialize, Deserialize, Debug)]
pub struct Song { pub struct Song {
pub name: String, pub name: String,
pub verses: BTreeMap<String, Verse>, pub verses: BTreeMap<String, Verse>,
@ -64,14 +66,14 @@ impl FromStr for Song {
// would probably best be done with an actual AST // would probably best be done with an actual AST
static COMMENT_REGEX: OnceLock<Regex> = OnceLock::new(); static COMMENT_REGEX: OnceLock<Regex> = OnceLock::new();
let comment_re = COMMENT_REGEX.get_or_init(|| Regex::new(r"(?s)#[^\n]*").unwrap()); let comment_re = COMMENT_REGEX.get_or_init(|| Regex::new(r"(?s)\r?\n?#[^\r\n]*").unwrap());
let s = comment_re.replace_all(s, "").into_owned(); let s = comment_re.replace_all(s, "").into_owned();
dbg!(&s); dbg!(&s);
static HUNK_REGEX: OnceLock<Regex> = OnceLock::new(); static HUNK_REGEX: OnceLock<Regex> = OnceLock::new();
let hunk_re = HUNK_REGEX.get_or_init(|| Regex::new(r"\s*[\n\r]\s*[\n\r]\s*").unwrap()); let hunk_re = HUNK_REGEX.get_or_init(|| Regex::new(r"\s*[\r\n]\s*[\r\n]\s*").unwrap());
let mut hunks = VecDeque::new(); let mut hunks = VecDeque::new();
let mut last_end: usize = 0; let mut last_end: usize = 0;
@ -170,6 +172,7 @@ A verse"#
#[test] #[test]
fn parses_song_with_comments() { fn parses_song_with_comments() {
let song: Song = r#"Song Title let song: Song = r#"Song Title
# this is a comment # this is a comment
A verse"# A verse"#
.parse() .parse()

View file

@ -1,4 +1,4 @@
use crate::router::ReqResult; use crate::{router::ReqResult, service::accounts::AuthSession};
use axum::response::Html; use axum::response::Html;
use maud::{html, Markup, PreEscaped, DOCTYPE}; use maud::{html, Markup, PreEscaped, DOCTYPE};
@ -33,7 +33,12 @@ pub fn foot() -> Markup {
} }
} }
pub fn page(title: &str, content: Markup) -> ReqResult<Html<String>> { pub fn page(
title: &str,
content: Markup,
auth_session: Option<AuthSession>,
) -> ReqResult<Html<String>> {
let current_user = auth_session.map(|s| s.user).flatten();
Ok(Html( Ok(Html(
html! { html! {
(head(title)) (head(title))
@ -41,8 +46,13 @@ pub fn page(title: &str, content: Markup) -> ReqResult<Html<String>> {
header class="drop-shadow border-b-2 border-surface0 bg-blue-500 flex overflow-x-scroll" { header class="drop-shadow border-b-2 border-surface0 bg-blue-500 flex overflow-x-scroll" {
a class="flex p-2 text-3xl font-mono text-mauve opacity-80 hover:bg-mauve hover:text-bg" href="/" { "lyrs" } a class="flex p-2 text-3xl font-mono text-mauve opacity-80 hover:bg-mauve hover:text-bg" href="/" { "lyrs" }
nav class="flex flex-1 justify-start" { nav class="flex flex-1 justify-start" {
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/auth/login" { "Login" } @if let Some(user) = current_user {
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/auth/register" { "Register" } a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/dashboard" {( user.username )}
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/accounts/logout" { "Logout" }
} @else {
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/accounts/login" { "Login" }
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/accounts/register" { "Register" }
}
} }
} }
main class="flex flex-col flex-1 relative overflow-x-scroll bg-mantle" { main class="flex flex-col flex-1 relative overflow-x-scroll bg-mantle" {

View file

@ -1,10 +1,11 @@
use crate::partials::page; use crate::partials::page;
use crate::service::accounts::AuthSession;
use crate::user::User; use crate::user::User;
use crate::{db, user}; use crate::{db, user};
use crate::{ use crate::{
file_watcher::FileWatcher, file_watcher::FileWatcher,
prelude::*, prelude::*,
service::{auth, static_files}, service::{accounts, static_files},
state::State as AppState, state::State as AppState,
}; };
use axum::extract::State; use axum::extract::State;
@ -79,7 +80,7 @@ pub async fn router(
}; };
let (static_file_service, static_file_watcher) = static_files::router(orl())?; let (static_file_service, static_file_watcher) = static_files::router(orl())?;
let auth_service = auth::router(state.clone()).unwrap(); let accounts_service = accounts::router(state.clone()).unwrap();
let session_store = tower_sessions_sled_store::SledStore::new(state.db.tree("session")?); let session_store = tower_sessions_sled_store::SledStore::new(state.db.tree("session")?);
let session_layer = SessionManagerLayer::new(session_store); let session_layer = SessionManagerLayer::new(session_store);
@ -88,11 +89,11 @@ pub async fn router(
let mut result = Router::new() let mut result = Router::new()
.route("/dashboard", get(dashboard)) .route("/dashboard", get(dashboard))
.route_layer(login_required!(AppState, login_url = "/auth/login")) .route_layer(login_required!(AppState, login_url = "/accounts/login"))
.route("/", get(index)) .route("/", get(index))
.route("/about", get(about)) .route("/about", get(about))
.route("/users", get(users)) .route("/users", get(users))
.nest_service("/auth", auth_service) .nest_service("/accounts", accounts_service)
.nest_service("/static", static_file_service) .nest_service("/static", static_file_service)
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())
.layer(auth_layer) .layer(auth_layer)
@ -107,16 +108,16 @@ pub async fn router(
Ok((result, watchers)) Ok((result, watchers))
} }
async fn index() -> ReqResult<Html<String>> { async fn index(auth_session: Option<AuthSession>) -> ReqResult<Html<String>> {
page("index", html! { "Index" }) page("index", html! { "Index" }, auth_session)
} }
async fn about() -> ReqResult<Html<String>> { async fn about(auth_session: Option<AuthSession>) -> ReqResult<Html<String>> {
page("about", html! { "About" }) page("about", html! { "About" }, auth_session)
} }
async fn dashboard() -> ReqResult<Html<String>> { async fn dashboard(auth_session: Option<AuthSession>) -> ReqResult<Html<String>> {
page("dashboard", html! { "Dashboard" }) page("dashboard", html! { "Dashboard" }, auth_session)
} }
async fn users(State(state): State<AppState>) -> ReqResult<String> { async fn users(State(state): State<AppState>) -> ReqResult<String> {

View file

@ -1,2 +1,2 @@
pub mod auth; pub mod accounts;
pub mod static_files; pub mod static_files;

View file

@ -1,27 +1,27 @@
use crate::router::ReqResult; use crate::router::ReqResult;
use crate::state::{Creds, State as AppState}; use crate::state::{Creds, State as AppState};
use crate::{partials::*, user::User}; use crate::{partials::*, user::User};
use crate::{prelude::*, user};
use axum::extract::State; use axum::extract::State;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::{Html, IntoResponse, Redirect}; use axum::response::{Html, IntoResponse, Redirect};
use axum::Form; use axum::Form;
use maud::html; use maud::html;
use std::convert::Infallible; use std::convert::Infallible;
use tracing::instrument;
use axum::{ use axum::{
routing::{get, post}, routing::{get, post},
Router, Router,
}; };
use crate::{prelude::*, user};
pub fn router(state: AppState) -> Result<Router, Infallible> { pub fn router(state: AppState) -> Result<Router, Infallible> {
Ok(Router::new() Ok(Router::new()
.route("/logout", get(logout))
.route("/login", get(login)) .route("/login", get(login))
.route("/login", post(authenticate)) .route("/login", post(authenticate))
.route("/register", get(register)) .route("/register", get(register))
.route("/register", post(create_user)) .route("/register", post(create_user))
.route("/logout", get(logout))
.with_state(state)) .with_state(state))
} }
@ -37,11 +37,11 @@ async fn login() -> ReqResult<Html<String>> {
let subaction = html! { let subaction = html! {
small class="mt-4" { small class="mt-4" {
"Need an account? " "Need an account? "
a href="/auth/register" {"Get one"} a href="/accounts/register" {"Get one"}
"." "."
} }
}; };
page("login", center_hero_form("Login", form, subaction)) page("login", center_hero_form("Login", form, subaction), None)
} }
async fn register() -> ReqResult<Html<String>> { async fn register() -> ReqResult<Html<String>> {
@ -56,14 +56,14 @@ async fn register() -> ReqResult<Html<String>> {
let subaction = html! { let subaction = html! {
small class="mt-4" { small class="mt-4" {
"Already have an account? " "Already have an account? "
a href="/auth/login" {"Login"} a href="/accounts/login" {"Login"}
"." "."
} }
}; };
page("login", center_hero_form("Register", form, subaction)) page("login", center_hero_form("Register", form, subaction), None)
} }
type AuthSession = axum_login::AuthSession<AppState>; pub type AuthSession = axum_login::AuthSession<AppState>;
#[instrument(skip(auth_session))] #[instrument(skip(auth_session))]
async fn authenticate( async fn authenticate(
@ -111,7 +111,8 @@ async fn create_user(
pub async fn logout(mut auth_session: AuthSession) -> impl IntoResponse { pub async fn logout(mut auth_session: AuthSession) -> impl IntoResponse {
match auth_session.logout().await { match auth_session.logout().await {
Ok(_) => Redirect::to("/auth/login").into_response(), Ok(Some(_u)) => Redirect::to("/accounts/login?done").into_response(),
Ok(None) => Redirect::to("/accounts/login?unauthenticated").into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
} }
} }