feat: Baseline added with config support

This commit is contained in:
Cyper committed 2026-02-27 21:25:11 -05:00
1 parent 76934a6d76
commit 91ee0abc15
20 files changed
+1168

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
use axum::{Router, extract::OriginalUri, routing::get};
use tower_http::services::ServeDir;
use crate::models::config::Config;
use crate::{error::AppError, routes};
async fn not_found(OriginalUri(uri): OriginalUri) -> AppError {
AppError::NotFound {
path: uri.path().to_string(),
}
}
pub fn app() -> Router<Config> {
let static_files = ServeDir::new("static").not_found_service(get(not_found));
Router::new()
.merge(routes::router())
.fallback_service(static_files)
}
+63
View File
@@ -0,0 +1,63 @@
use askama::Template;
use axum::{
http::StatusCode,
response::{Html, IntoResponse},
};
#[derive(Template)]
#[template(path = "errors/401.html")]
struct Error401;
#[derive(Template)]
#[template(path = "errors/404.html")]
struct Error404 {
path: String,
}
#[derive(Template)]
#[template(path = "errors/500.html")]
struct Error500 {
message: Option<String>,
}
pub enum AppError {
// Unauthorized,
NotFound { path: String },
Internal { message: Option<String> },
}
impl AppError {
pub fn internal() -> Self {
AppError::Internal { message: None }
}
// pub fn internal_msg(msg: String) -> Self {
// AppError::Internal { message: Some(msg.into()) }
// }
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
match self {
// AppError::Unauthorized => (
// StatusCode::UNAUTHORIZED,
// Html(Error401.render().unwrap())
// ).into_response(),
AppError::NotFound { path } => {
let response = (
StatusCode::NOT_FOUND,
Html(Error404 { path }.render().unwrap()),
);
response.into_response()
}
AppError::Internal { message } => {
let response = (
StatusCode::INTERNAL_SERVER_ERROR,
Html(Error500 { message }.render().unwrap()),
);
response.into_response()
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
use crate::{models::config::Config, util::config::get_app_config};
mod app;
mod error;
mod models;
mod routes;
mod util;
#[tokio::main]
async fn main() {
// Read once
let config: Config = get_app_config().expect("Unable to read app config");
// Build app with shared state
let app = app::app().with_state(config);
let listener = tokio::net::TcpListener::bind("0.0.0.0:7878").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
+17
View File
@@ -0,0 +1,17 @@
use serde::Deserialize;
#[derive(Clone, Deserialize)]
pub struct Config {
pub metadata: SiteMetadata,
}
#[derive(Clone, Deserialize)]
pub struct SiteMetadata {
pub app_name: String,
pub app_version: String,
pub author: String,
pub description: String,
pub keywords: String,
pub site_title: String,
pub site_url: String,
}
+1
View File
@@ -0,0 +1 @@
pub mod config;
+8
View File
@@ -0,0 +1,8 @@
use crate::models::config::Config;
use axum::Router;
mod root;
pub fn router() -> Router<Config> {
Router::new().merge(root::router())
}
+30
View File
@@ -0,0 +1,30 @@
use askama::Template;
use axum::{Router, extract::State, response::Html, routing::get};
use crate::{
error::AppError,
models::config::{Config, SiteMetadata},
};
#[derive(Template)]
#[template(path = "index.html")]
struct HomePage {
metadata: SiteMetadata,
title: String,
}
async fn home(State(config): State<Config>) -> Result<Html<String>, AppError> {
let template = HomePage {
metadata: config.metadata.clone(),
title: String::from("Home"),
};
template
.render()
.map(Html)
.map_err(|_| AppError::internal())
}
pub fn router() -> Router<Config> {
Router::new().route("/", get(home))
}
+17
View File
@@ -0,0 +1,17 @@
use std::fs;
use serde::Deserialize;
use crate::models::config::Config;
// Temporary class to map from entire "Cargo.toml" file to only grab the portions under [app]
#[derive(Deserialize)]
struct TomlConfig {
app: Config
}
pub fn get_app_config() -> Result<Config, Box<dyn std::error::Error>> {
let cargo_content = fs::read_to_string("Cargo.toml")?;
let config: TomlConfig = toml::from_str(&cargo_content)?;
return Ok(config.app);
}
+1
View File
@@ -0,0 +1 @@
pub mod config;