use axum::RequestPartsExt; use axum::extract::FromRequestParts; use cache_bust::asset; use http::request::Parts; use maud::{DOCTYPE, Markup, html}; use sqlx::FromRow; use super::models::user::{AuthSession, User}; use super::{AppError, AppState}; pub mod auth; pub mod contact; pub mod home; pub mod ics; pub mod journal; pub mod settings; #[derive(Debug, FromRow)] struct ContactLink { name: String, contact_id: u32, } pub struct Layout { contact_links: Vec, user: User, } impl FromRequestParts for Layout { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { let auth_session = parts .extract::() .await .map_err(|_| anyhow::Error::msg("could not get session"))?; let user = auth_session.user.unwrap(); let contact_links: Vec = sqlx::query_as( "select c.id as contact_id, coalesce(n.name, '(unnamed)') as name from contacts c left join names n on c.id = n.contact_id where n.sort is null or n.sort = 0 order by name asc", ) .fetch_all(&state.db(&user).pool) .await?; Ok(Layout { contact_links, user, }) } } impl Layout { pub fn render(&self, css: Option>, content: Markup) -> Markup { html! { (DOCTYPE) html { head { link rel="stylesheet" type="text/css" href=(format!("/static/{}", asset!("index.css"))); meta name="viewport" content="width=device-width"; script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js" {} script src="https://cdn.jsdelivr.net/npm/htmx-ext-response-targets@2.0.4" integrity="sha384-T41oglUPvXLGBVyRdZsVRxNWnOOqCynaPubjUVjxhsjFTKrFJGEMm3/0KGmNQ+Pg" crossorigin="anonymous" {} script src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/dist/cdn.min.js" defer {} @if let Some(hrefs) = css { @for href in hrefs { link rel="stylesheet" type="text/css" href=(format!("/static/{}", href)); } } } body x-data="{ sidebar: false }" { header { input #sidebar-show-hide type="button" x-on:click="sidebar = !sidebar" value="☰"; h1 { a href="/" { "Mascarpone" } } span { (self.user.username) } a href="/settings" { "Settings" } a href="/logout" { "Logout" } } section #content { nav #contacts-sidebar x-bind:class="sidebar ? 'show' : 'hide'" { ul { li { button hx-post="/contact/new" { "+ Add Contact" } } @for link in &self.contact_links { li { a href=(format!("/contact/{}", link.contact_id)) { (link.name) } } } } } main { (content) } } } } } } }