mascarpone/src/web/settings.rs
2025-12-01 15:23:56 -06:00

168 lines
5.3 KiB
Rust

use axum::{
Router,
extract::State,
routing::{delete, get, post, put},
};
use axum_extra::extract::Form;
use cache_bust::asset;
use maud::{Markup, html};
use serde::Deserialize;
use serde_json::json;
use short_uuid::ShortUuid;
use super::Layout;
use crate::models::user::{AuthSession, Credentials};
use crate::{AppError, AppState};
pub fn router() -> Router<AppState> {
Router::new()
.route("/settings", get(self::get::settings))
.route("/settings/ics_path", post(self::post::ics_path))
.route("/settings/ics_path", delete(self::delete::ics_path))
.route("/password", put(self::put::password))
}
fn calendar_link(path: Option<String>) -> Markup {
if let Some(path) = path {
html! {
#cal-link x-data=(json!({ "path": path })) hx-target="this" hx-swap="outerHTML" {
a x-bind:href="window.location.origin + '/cal/' + path" {
span x-text="window.location.origin + '/cal/'" {}
span { (path) }
}
p {
"Warning: These actions unrecoverably change your calendar's URL."
}
button hx-post="/settings/ics_path" { "Regenerate path" }
button hx-delete="/settings/ics_path" { "Destroy calendar" }
}
}
} else {
html! {
#cal-link hx-target="this" hx-swap="outerHTML" {
div { "Birthdays calendar is disabled." }
button hx-post="/settings/ics_path" { "Enable calendar" }
}
}
}
}
mod get {
use super::*;
pub async fn settings(
auth_session: AuthSession,
State(state): State<AppState>,
layout: Layout,
) -> Result<Markup, AppError> {
let pool = &state.db(&auth_session.user.unwrap()).pool;
let ics_path: (Option<String>,) = sqlx::query_as("select ics_path from settings")
.fetch_one(pool)
.await?;
let ics_path: Option<String> = ics_path.0;
Ok(layout.render(
Some(vec![asset!("settings.css")]),
html! {
h2 { "Birthdays Calendar URL" }
(calendar_link(ics_path))
h2 { "Change Password" }
form x-data="{ old_p: '', new_p: '', confirm: '' }" hx-put="/password"
hx-on::after-request="if(event.detail.successful) { this.reset(); setTimeout(() => window.location.reload(), 5000); }"
hx-target="this" hx-target-error="this" hx-swap="beforeend" {
label for="old" { "Current password:" }
input id="old" name="current" x-model="old_p" type="password";
label for="new" { "New password:" }
input id="new" name="new_password" x-model="new_p" type="password";
label for="confirm" { "Confirm:" }
input id="confirm" x-model="confirm" type="password";
button type="submit" x-bind:disabled="!(new_p.length && new_p === confirm)" { "Submit" }
.error x-show="new_p.length && confirm.length && new_p !== confirm" {
"Passwords do not match"
}
}
},
))
}
}
mod post {
use super::*;
pub async fn ics_path(
auth_session: AuthSession,
State(state): State<AppState>,
) -> Result<Markup, AppError> {
let user = auth_session.user.unwrap();
let pool = &state.db(&user).pool;
let ics_path = format!("{}-{}.ics", &user.username, ShortUuid::generate());
sqlx::query!("update settings set ics_path=$1", ics_path)
.execute(pool)
.await?;
Ok(calendar_link(Some(ics_path)))
}
}
mod delete {
use super::*;
pub async fn ics_path(
auth_session: AuthSession,
State(state): State<AppState>,
) -> Result<Markup, AppError> {
let pool = &state.db(&auth_session.user.unwrap()).pool;
sqlx::query!("update settings set ics_path=null")
.execute(pool)
.await?;
Ok(calendar_link(None))
}
}
mod put {
use super::*;
#[derive(Deserialize)]
pub struct PassChange {
current: String,
new_password: String,
}
pub async fn password(
auth_session: AuthSession,
Form(payload): Form<PassChange>,
) -> Result<Markup, AppError> {
let username = auth_session.user.as_ref().unwrap().username.clone();
tracing::debug!("Resetting password for {}...", username);
let current_creds = Credentials {
username: username.clone(),
password: payload.current,
};
let new_creds = Credentials {
username: username,
password: payload.new_password,
};
match auth_session.authenticate(current_creds).await {
Err(_) => Ok(html! { .error { "Server error; could not verify authentication." } }),
Ok(None) => Ok(html! { .error { "Current password is incorrect." } }),
Ok(Some(_)) => {
auth_session.backend.set_password(new_creds).await?;
Ok(html! { .msg {
"Password changed successfully. Redirecting to login page after 5 seconds..."
} })
}
}
}
}