refactor: better separation of dependencies to crates

node stuff to html crate only
This commit is contained in:
Per Stark
2025-04-04 12:50:38 +02:00
parent 20fc43638b
commit 5bc48fb30b
160 changed files with 231 additions and 337 deletions

View File

@@ -0,0 +1,30 @@
use axum::{
extract::{Request, State},
middleware::Next,
response::Response,
};
use common::storage::{db::ProvidesDb, types::analytics::Analytics};
use crate::SessionType;
/// Middleware to count unique visitors and page loads
pub async fn analytics_middleware<S>(
State(state): State<S>,
session: SessionType,
request: Request,
next: Next,
) -> Response
where
S: ProvidesDb + Clone + Send + Sync + 'static,
{
let path = request.uri().path();
if !path.starts_with("/assets") && !path.contains('.') {
if !session.get::<bool>("counted_visitor").unwrap_or(false) {
let _ = Analytics::increment_visitors(state.db()).await;
session.set("counted_visitor", true);
}
let _ = Analytics::increment_page_loads(state.db()).await;
}
next.run(request).await
}

View File

@@ -0,0 +1,50 @@
use axum::{
async_trait,
extract::{FromRequestParts, Request},
http::request::Parts,
middleware::Next,
response::{IntoResponse, Response},
};
use common::storage::types::user::User;
use crate::AuthSessionType;
use super::response_middleware::TemplateResponse;
#[derive(Debug, Clone)]
pub struct RequireUser(pub User);
// Implement FromRequestParts for RequireUser
#[async_trait]
impl<S> FromRequestParts<S> for RequireUser
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
parts
.extensions
.get::<User>()
.cloned()
.map(RequireUser)
.ok_or_else(|| TemplateResponse::redirect("/signin").into_response())
}
}
// Auth middleware that adds the user to extensions
pub async fn require_auth(auth: AuthSessionType, mut request: Request, next: Next) -> Response {
// Check if user is authenticated
match auth.current_user {
Some(user) => {
// Add user to request extensions
request.extensions_mut().insert(user);
// Continue to the handler
next.run(request).await
}
None => {
// Redirect to login
TemplateResponse::redirect("/signin").into_response()
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod analytics_middleware;
pub mod auth_middleware;
pub mod response_middleware;

View File

@@ -0,0 +1,208 @@
use axum::{
extract::State,
http::StatusCode,
response::{Html, IntoResponse, Response},
Extension,
};
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
use minijinja::{context, Value};
use serde::Serialize;
use tracing::error;
#[derive(Clone)]
pub enum TemplateKind {
Full(String),
Partial(String, String),
Error(StatusCode),
Redirect(String),
}
#[derive(Clone)]
pub struct TemplateResponse {
template_kind: TemplateKind,
context: Value,
}
impl TemplateResponse {
pub fn new_template<T: Serialize>(name: impl Into<String>, context: T) -> Self {
Self {
template_kind: TemplateKind::Full(name.into()),
context: Value::from_serialize(&context),
}
}
pub fn new_partial<T: Serialize>(
template: impl Into<String>,
block: impl Into<String>,
context: T,
) -> Self {
Self {
template_kind: TemplateKind::Partial(template.into(), block.into()),
context: Value::from_serialize(&context),
}
}
pub fn error(status: StatusCode, title: &str, error: &str, description: &str) -> Self {
let ctx = context! {
status_code => status.as_u16(),
title => title,
error => error,
description => description
};
Self {
template_kind: TemplateKind::Error(status),
context: ctx,
}
}
pub fn not_found() -> Self {
Self::error(
StatusCode::NOT_FOUND,
"Page Not Found",
"Not Found",
"The page you're looking for doesn't exist or was removed.",
)
}
pub fn server_error() -> Self {
Self::error(
StatusCode::INTERNAL_SERVER_ERROR,
"Internal Server Error",
"Internal Server Error",
"Something went wrong on our end.",
)
}
pub fn unauthorized() -> Self {
Self::error(
StatusCode::UNAUTHORIZED,
"Unauthorized",
"Access Denied",
"You need to be logged in to access this page.",
)
}
pub fn bad_request(message: &str) -> Self {
Self::error(
StatusCode::BAD_REQUEST,
"Bad Request",
"Bad Request",
message,
)
}
pub fn redirect(path: impl Into<String>) -> Self {
Self {
template_kind: TemplateKind::Redirect(path.into()),
context: Value::from_serialize(()),
}
}
}
impl IntoResponse for TemplateResponse {
fn into_response(self) -> Response {
Extension(self).into_response()
}
}
pub async fn with_template_response<S>(State(state): State<S>, response: Response) -> Response
where
S: ProvidesTemplateEngine + Clone + Send + Sync + 'static,
{
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
let template_engine = state.template_engine();
match &template_response.template_kind {
TemplateKind::Full(name) => {
match template_engine.render(name, &template_response.context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render template '{}': {:?}", name, e);
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
}
}
}
TemplateKind::Partial(template, block) => {
match template_engine.render_block(template, block, &template_response.context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render block '{}/{}': {:?}", template, block, e);
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
}
}
}
TemplateKind::Error(status) => {
match template_engine.render("errors/error.html", &template_response.context) {
Ok(html) => (*status, Html(html)).into_response(),
Err(e) => {
error!("Failed to render error template: {:?}", e);
(*status, fallback_error()).into_response()
}
}
}
TemplateKind::Redirect(path) => {
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
}
}
} else {
response
}
}
#[derive(Debug)]
pub enum HtmlError {
AppError(AppError),
TemplateError(String),
}
impl From<AppError> for HtmlError {
fn from(err: AppError) -> Self {
HtmlError::AppError(err)
}
}
impl From<surrealdb::Error> for HtmlError {
fn from(err: surrealdb::Error) -> Self {
HtmlError::AppError(AppError::from(err))
}
}
impl From<minijinja::Error> for HtmlError {
fn from(err: minijinja::Error) -> Self {
HtmlError::TemplateError(err.to_string())
}
}
impl IntoResponse for HtmlError {
fn into_response(self) -> Response {
match self {
HtmlError::AppError(err) => match err {
AppError::NotFound(_) => TemplateResponse::not_found().into_response(),
AppError::Auth(_) => TemplateResponse::unauthorized().into_response(),
AppError::Validation(msg) => TemplateResponse::bad_request(&msg).into_response(),
_ => {
error!("Internal error: {:?}", err);
TemplateResponse::server_error().into_response()
}
},
HtmlError::TemplateError(err) => {
error!("Template error: {}", err);
TemplateResponse::server_error().into_response()
}
}
}
}
fn fallback_error() -> String {
r#"
<html>
<body>
<div class="container mx-auto p-4">
<h1 class="text-4xl text-error">Error</h1>
<p class="mt-4">Sorry, something went wrong displaying this page.</p>
</div>
</body>
</html>
"#
.to_string()
}