chore: improve html-router auth, caching, and analytics while centralizing search labels in common.

small fix
This commit is contained in:
Per Stark
2026-05-29 14:42:20 +02:00
parent 920d7b5efb
commit ec80a4e540
27 changed files with 510 additions and 344 deletions
@@ -1,3 +1,5 @@
use std::sync::Arc;
use axum::{
extract::{Request, State},
http::Method,
@@ -10,7 +12,7 @@ use common::storage::{db::ProvidesDb, types::analytics::Analytics};
use crate::SessionType;
/// Middleware to count unique visitors and page loads
/// Middleware to count unique visitors and page loads.
pub async fn analytics_middleware<S>(
State(state): State<S>,
session: SessionType,
@@ -21,17 +23,18 @@ where
S: ProvidesDb + Clone + Send + Sync + 'static,
{
let path = request.uri().path();
// Only count visits/page loads for GET requests to non-asset, non-static paths
if request.method() == Method::GET && !path.starts_with("/assets") && !path.contains('.') {
if !session.get::<bool>("counted_visitor").unwrap_or(false) {
if let Err(e) = Analytics::increment_visitors(state.db()).await {
warn!("failed to increment visitor count: {e}");
}
let is_new_visitor = !session.get::<bool>("counted_visitor").unwrap_or(false);
if is_new_visitor {
session.set("counted_visitor", true);
}
if let Err(e) = Analytics::increment_page_loads(state.db()).await {
warn!("failed to increment page load count: {e}");
}
let db = Arc::clone(state.db());
tokio::spawn(async move {
if let Err(error) = Analytics::record_page_view(&db, is_new_visitor).await {
warn!("failed to record page view: {error}");
}
});
}
next.run(request).await
}
@@ -11,6 +11,7 @@ use crate::AuthSessionType;
use super::response_middleware::TemplateResponse;
#[derive(Debug, Clone)]
/// Authenticated user extracted from request extensions by [`require_auth`].
pub struct RequireUser(pub User);
// Implement FromRequestParts for RequireUser
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::sync::Arc;
use axum::{
extract::{Request, State},
@@ -36,6 +37,7 @@ pub enum TemplateKind {
}
#[derive(Clone)]
/// Handler response that the template middleware renders into HTML.
pub struct TemplateResponse {
template_kind: TemplateKind,
context: Value,
@@ -180,6 +182,7 @@ fn context_to_map(
}
}
#[allow(clippy::too_many_lines)]
pub async fn with_template_response<S>(
State(state): State<S>,
HxRequest(is_htmx): HxRequest,
@@ -221,14 +224,15 @@ where
if let Some(cached_archive) =
html_state.get_cached_conversation_archive(user_id).await
{
conversation_archive = cached_archive;
conversation_archive = cached_archive.to_vec();
} else if let Ok(archive) =
Conversation::get_user_sidebar_conversations(user_id, &html_state.db).await
{
let cached = Arc::from(archive);
html_state
.set_cached_conversation_archive(user_id, archive.clone())
.set_cached_conversation_archive(user_id, Arc::clone(&cached))
.await;
conversation_archive = archive;
conversation_archive = cached.to_vec();
}
}
}
@@ -245,8 +249,8 @@ where
};
let context = ContextWrapper {
user_theme: &user_theme,
initial_theme: &initial_theme,
user_theme,
initial_theme,
is_authenticated,
user: current_user.as_ref(),
conversation_archive,
@@ -290,13 +294,13 @@ where
.context
.get_attr("title")
.ok()
.and_then(|v| v.as_str().map(|s| s.to_string()))
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_else(|| "Error".to_string());
let description = template_response
.context
.get_attr("description")
.ok()
.and_then(|v| v.as_str().map(|s| s.to_string()))
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_else(|| "An error occurred.".to_string());
let trigger_payload = json!({"toast": {"title": title, "description": description, "type": "error"}});