refactor: reorganize Rust codebase into modular handlers and database layers

- Split monolithic src/db.rs (1122 lines) into domain modules: projects, tags, settings
- Extract API handlers from main.rs into separate handler modules by domain
- Add proxy module for ISR/SSR coordination with Bun process
- Introduce AppState for shared application context
- Add utility functions for asset serving and request classification
- Remove obsolete middleware/auth.rs in favor of session checks in handlers
This commit is contained in:
2026-01-07 13:55:23 -06:00
parent 4663b00942
commit cf599d09d6
45 changed files with 3525 additions and 3326 deletions
+129
View File
@@ -0,0 +1,129 @@
use axum::{
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use crate::assets;
/// Check if a path represents a static asset
pub fn is_static_asset(path: &str) -> bool {
path.starts_with("/node_modules/")
|| path.starts_with("/@") // Vite internals like /@vite/client, /@fs/, /@id/
|| path.starts_with("/.svelte-kit/")
|| path.starts_with("/.well-known/")
|| path.ends_with(".woff2")
|| path.ends_with(".woff")
|| path.ends_with(".ttf")
|| path.ends_with(".ico")
|| path.ends_with(".png")
|| path.ends_with(".jpg")
|| path.ends_with(".svg")
|| path.ends_with(".webp")
|| path.ends_with(".css")
|| path.ends_with(".js")
|| path.ends_with(".map")
}
/// Check if a path represents a page route (not an asset)
pub fn is_page_route(path: &str) -> bool {
!path.starts_with("/node_modules/")
&& !path.starts_with("/@")
&& !path.starts_with("/.svelte-kit/")
&& !path.contains('.')
}
/// Check if the request accepts HTML responses
pub fn accepts_html(headers: &HeaderMap) -> bool {
if let Some(accept) = headers.get(axum::http::header::ACCEPT) {
if let Ok(accept_str) = accept.to_str() {
return accept_str.contains("text/html") || accept_str.contains("*/*");
}
}
// Default to true for requests without Accept header (browsers typically send it)
true
}
/// Determines if request prefers raw content (CLI tools) over HTML
pub fn prefers_raw_content(headers: &HeaderMap) -> bool {
// Check User-Agent for known CLI tools first (most reliable)
if let Some(ua) = headers.get(axum::http::header::USER_AGENT) {
if let Ok(ua_str) = ua.to_str() {
let ua_lower = ua_str.to_lowercase();
if ua_lower.starts_with("curl/")
|| ua_lower.starts_with("wget/")
|| ua_lower.starts_with("httpie/")
|| ua_lower.contains("curlie")
{
return true;
}
}
}
// Check Accept header - if it explicitly prefers text/html, serve HTML
if let Some(accept) = headers.get(axum::http::header::ACCEPT) {
if let Ok(accept_str) = accept.to_str() {
// If text/html appears before */* in the list, they prefer HTML
if let Some(html_pos) = accept_str.find("text/html") {
if let Some(wildcard_pos) = accept_str.find("*/*") {
return html_pos > wildcard_pos;
}
// Has text/html but no */* → prefers HTML
return false;
}
// Has */* but no text/html → probably a CLI tool
if accept_str.contains("*/*") && !accept_str.contains("text/html") {
return true;
}
}
}
// No Accept header → assume browser (safer default)
false
}
/// Serve a prerendered error page for the given status code
pub fn serve_error_page(status: StatusCode) -> Response {
let status_code = status.as_u16();
if let Some(html) = assets::get_error_page(status_code) {
let mut headers = HeaderMap::new();
headers.insert(
axum::http::header::CONTENT_TYPE,
axum::http::HeaderValue::from_static("text/html; charset=utf-8"),
);
headers.insert(
axum::http::header::CACHE_CONTROL,
axum::http::HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
(status, headers, html).into_response()
} else {
// Fallback for undefined error codes (500 generic page)
tracing::warn!(
status_code,
"No prerendered error page found for status code - using fallback"
);
if let Some(fallback_html) = assets::get_error_page(500) {
let mut headers = HeaderMap::new();
headers.insert(
axum::http::header::CONTENT_TYPE,
axum::http::HeaderValue::from_static("text/html; charset=utf-8"),
);
headers.insert(
axum::http::header::CACHE_CONTROL,
axum::http::HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
(status, headers, fallback_html).into_response()
} else {
// Last resort: plaintext (should never happen if 500.html exists)
(status, format!("Error {}", status_code)).into_response()
}
}
}
/// Validate hex color format (6 characters, no hash, no alpha)
pub fn validate_hex_color(color: &str) -> bool {
color.len() == 6 && color.chars().all(|c| c.is_ascii_hexdigit())
}