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]]
name = "axum-login"
version = "0.15.1"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fbc0d7bd2577dda9aa9cac096e53b30342725d8eea5798169ff2537a214f45"
checksum = "4012877d9672b7902aa6567960208756f68a09de81e988fa18fe369e92f90471"
dependencies = [
"async-trait",
"axum",

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use crate::router::ReqResult;
use crate::{router::ReqResult, service::accounts::AuthSession};
use axum::response::Html;
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(
html! {
(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" {
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" {
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/auth/login" { "Login" }
a class="flex items-center p-2 hover:bg-mauve hover:text-bg" href="/auth/register" { "Register" }
@if let Some(user) = current_user {
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" {

View file

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

View file

@ -1,27 +1,27 @@
use crate::router::ReqResult;
use crate::state::{Creds, State as AppState};
use crate::{partials::*, user::User};
use crate::{prelude::*, user};
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse, Redirect};
use axum::Form;
use maud::html;
use std::convert::Infallible;
use tracing::instrument;
use axum::{
routing::{get, post},
Router,
};
use crate::{prelude::*, user};
pub fn router(state: AppState) -> Result<Router, Infallible> {
Ok(Router::new()
.route("/logout", get(logout))
.route("/login", get(login))
.route("/login", post(authenticate))
.route("/register", get(register))
.route("/register", post(create_user))
.route("/logout", get(logout))
.with_state(state))
}
@ -37,11 +37,11 @@ async fn login() -> ReqResult<Html<String>> {
let subaction = html! {
small class="mt-4" {
"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>> {
@ -56,14 +56,14 @@ async fn register() -> ReqResult<Html<String>> {
let subaction = html! {
small class="mt-4" {
"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))]
async fn authenticate(
@ -111,7 +111,8 @@ async fn create_user(
pub async fn logout(mut auth_session: AuthSession) -> impl IntoResponse {
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(),
}
}