Session powers
This commit is contained in:
parent
b19f5dd31e
commit
6b0e87e8f8
8 changed files with 50 additions and 30 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod auth;
|
pub mod accounts;
|
||||||
pub mod static_files;
|
pub mod static_files;
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue