mascarpone/src/web/mod.rs

108 lines
3.6 KiB
Rust
Raw Normal View History

2025-11-27 13:45:21 -06:00
use axum::RequestPartsExt;
use axum::extract::FromRequestParts;
2025-12-01 15:23:56 -06:00
use cache_bust::asset;
2025-11-27 13:45:21 -06:00
use http::request::Parts;
use maud::{DOCTYPE, Markup, html};
use super::models::user::{AuthSession, User};
use super::{AppError, AppState};
2026-01-26 22:14:58 -06:00
use crate::db::DbId;
2025-11-27 13:45:21 -06:00
pub mod auth;
pub mod contact;
2026-01-23 21:20:27 -06:00
pub mod group;
2025-11-27 13:45:21 -06:00
pub mod home;
pub mod ics;
pub mod journal;
pub mod settings;
2026-01-26 22:14:58 -06:00
#[derive(Debug)]
2025-11-27 13:45:21 -06:00
struct ContactLink {
name: String,
2026-01-26 22:14:58 -06:00
contact_id: DbId,
2025-11-27 13:45:21 -06:00
}
pub struct Layout {
contact_links: Vec<ContactLink>,
user: User,
}
impl FromRequestParts<AppState> for Layout {
type Rejection = AppError;
async fn from_request_parts(
parts: &mut Parts,
state: &AppState,
) -> Result<Self, Self::Rejection> {
let auth_session = parts
.extract::<AuthSession>()
.await
.map_err(|_| anyhow::Error::msg("could not get session"))?;
let user = auth_session.user.unwrap();
2026-01-26 22:14:58 -06:00
let contact_links = sqlx::query_as!(
ContactLink,
2025-11-27 13:45:21 -06:00
"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<Vec<&str>>, content: Markup) -> Markup {
html! {
(DOCTYPE)
html {
head {
2025-12-01 15:23:56 -06:00
link rel="stylesheet" type="text/css" href=(format!("/static/{}", asset!("index.css")));
2025-11-27 13:45:21 -06:00
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 {
2025-12-01 15:23:56 -06:00
link rel="stylesheet" type="text/css" href=(format!("/static/{}", href));
2025-11-27 13:45:21 -06:00
}
}
}
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)
}
}
}
}
}
}
}