From f491ed95a3bc82c31c70b0f38ed841408576ec0c Mon Sep 17 00:00:00 2001 From: WriterV2 Date: Sat, 21 Mar 2026 11:56:19 +0100 Subject: Purge + prepare for local website builder --- src/handlers/admin.rs | 143 --------------------------------------- src/handlers/frontend_builder.rs | 121 --------------------------------- src/handlers/mod.rs | 53 --------------- 3 files changed, 317 deletions(-) delete mode 100644 src/handlers/admin.rs delete mode 100644 src/handlers/frontend_builder.rs delete mode 100644 src/handlers/mod.rs (limited to 'src/handlers') diff --git a/src/handlers/admin.rs b/src/handlers/admin.rs deleted file mode 100644 index b0863af..0000000 --- a/src/handlers/admin.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::collections::HashMap; -use std::io::{BufWriter, Write}; - -use anyhow::Context; -use axum::body::Bytes; -use axum::extract::{Multipart, Path, Request, State}; -use axum::http::{HeaderMap, StatusCode}; -use axum::middleware::Next; -use axum::response::{IntoResponse, Response}; -use axum::{Extension, Json}; -use rand::Rng; -use subtle::ConstantTimeEq; - -use crate::db::product::get_all_products; -use crate::db::story::{format_filepath, Story}; -use crate::db::ProductDatabaseHandler; -use crate::error::AppError; - -use super::{ApiContext, AppState}; - -pub fn generate_token() -> String { - const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz\ - 0123456789)(*&^%$#@!~"; - let mut rng = rand::thread_rng(); - let token: String = (0..32) - .map(|_| { - let idx = rng.gen_range(0..CHARSET.len()); - CHARSET[idx] as char - }) - .collect(); - println!("New Token generated: {}", token); - token -} - -pub async fn admin_middleware( - State(state): State, - headers: HeaderMap, - request: Request, - next: Next, -) -> Result { - let mut expected_token = state.admin_token.lock().await; - - let token = headers - .get("X-Admin-Token") - .and_then(|header| header.to_str().ok()) - .ok_or(StatusCode::UNAUTHORIZED)?; - - if expected_token - .as_bytes() - .ct_ne(token.as_bytes()) - .unwrap_u8() - == 1 - { - eprintln!("Warning: Unauthorized access attempt with invalid token"); - *expected_token = generate_token(); - return Err(StatusCode::UNAUTHORIZED); - } - *expected_token = generate_token(); - - Ok(next.run(request).await) -} - -pub async fn admin_healthcheck() -> impl IntoResponse { - StatusCode::OK -} - -pub async fn upload_story( - ctx: Extension, - mut multipart: Multipart, -) -> Result { - let mut form: HashMap = HashMap::new(); - while let Some(field) = multipart.next_field().await? { - if let Some(label) = field.name() { - form.insert(label.to_string(), field.bytes().await?); - } - } - - let name = if let Some(name_value) = form.get("name") { - std::str::from_utf8(name_value).with_context(|| "Failed to parse name")? - } else { - return Ok(StatusCode::BAD_REQUEST.into_response()); - }; - - let description = if let Some(description_value) = form.get("description") { - std::str::from_utf8(description_value).with_context(|| "Failed to parse description")? - } else { - return Ok(StatusCode::BAD_REQUEST.into_response()); - }; - - let language = if let Some(language_value) = form.get("language") { - String::from_utf8(language_value.to_vec()).with_context(|| "Failed to parse language")? - } else { - return Ok(StatusCode::BAD_REQUEST.into_response()); - }; - - let pdf = if let Some(pdf_value) = form.get("pdf") { - let filename = format_filepath(name, "pdf"); - let file = std::fs::File::create_new(filename)?; - BufWriter::new(file).write_all(pdf_value)?; - pdf_value.to_vec() - } else { - return Ok(StatusCode::BAD_REQUEST.into_response()); - }; - - let mut tags: Vec<&str> = Vec::new(); - if let Some(tags_value) = form.get("tag") { - let tagstring = std::str::from_utf8(tags_value)?; - for tag in tagstring.split(",") { - tags.push(tag.trim()); - } - } - - let story_template = Story { - id: 0, - pid: 0, - language, - pdf, - }; - - let story = story_template - .post(&ctx.pool, name, description, tags) - .await?; - Ok((StatusCode::CREATED, Json(story)).into_response()) -} - -pub async fn delete_story( - ctx: Extension, - Path(story_id): Path, -) -> Result { - Story::delete(&ctx.pool, story_id).await?; - Ok((StatusCode::NO_CONTENT).into_response()) -} - -pub async fn get_stories(ctx: Extension) -> Result { - let stories = Story::get_all(&ctx.pool).await?; - Ok((StatusCode::OK, Json(stories)).into_response()) -} - -pub async fn get_products(ctx: Extension) -> Result { - let products = get_all_products(&ctx.pool).await?; - Ok((StatusCode::OK, Json(products)).into_response()) -} diff --git a/src/handlers/frontend_builder.rs b/src/handlers/frontend_builder.rs deleted file mode 100644 index dd49854..0000000 --- a/src/handlers/frontend_builder.rs +++ /dev/null @@ -1,121 +0,0 @@ -use axum::response::IntoResponse; -use axum::Extension; -use chrono::DateTime; -use maud::{html, Markup, DOCTYPE}; - -use crate::db::product::{get_all_specificproducts, ProductMarker, SpecificProduct}; -use crate::db::ProductDatabaseHandler; -use crate::error::AppError; - -pub async fn home() -> Result { - Ok(html!((page("Home", Markup::default())))) -} - -fn page(page_title: &str, content: Markup) -> Markup { - html!( - (head(page_title)) - body class="bg-[#eae3d2] text-[#333] dark:bg-[#333] dark:text-[#eae3d2] font-mono text-base" { - (header()) - (navbar()) - (content) - } - ) -} - -fn header() -> Markup { - html!( - header { - h1 class="underline decoration-[#601B69] dark:decoration-[#e1b1e7] text-center tracking-widest font-serif p-3 text-xl" {"Vithuran Vishnuthas"} - } - ) -} - -fn navbar() -> Markup { - html!({ - navbar class="p-3 text-sm flex flex-row flex-wrap content-center justify-around decoration-[#eae3d2] tracking-wide" { - a class="hover:underline" href="/" { "Home" } - a class="hover:underline" href="/stories" { "Stories" } - a class="hover:underline" href="/" { "Tools" } - a class="hover:underline" href="/" { "Games" } - } - }) -} - -fn cards<'a, T: PageBuilder<'a> + ProductMarker>( - products: Vec>, - category: &str, -) -> Markup { - html!( - div class="m-12" { - h1 class="p-6 font-serif text-xl tracking-wider underline decoration-[#601B69] dark:decoration-[#E1B1E7]" { (category) } - div class="grid grid-cols-2 gap-8" { - @for product in products { - (card(product)) - } - } - } - ) -} - -fn card<'a, T: PageBuilder<'a> + ProductMarker>(specific_product: SpecificProduct) -> Markup { - let date = |i: i64| -> String { - if let Some(datetime) = DateTime::from_timestamp(i / 1_000, 0) { - return format!("{}", datetime.format("%Y/%m/%d")).to_string(); - } - "Unknown".to_string() - }; - let upload_date = date(specific_product.product.uploaddate); - let update_date = date(specific_product.product.updatedate); - html!( - div class="p-9 shadow" { - h2 class="text-[#601B69] dark:text-[#E1B1E7]" { - (specific_product.product.name) - } - div { - (specific_product.product.description) - } - div { - (specific_product.detail.product_specific_card_content(&specific_product)) - } - div class="flex flex-row space-x-5 mt-9 text-xs" { - span {"Upload: " (upload_date)} - @if upload_date != update_date { - span {"Update: " (update_date)} - } - } - } - ) -} - -fn head(page_title: &str) -> Markup { - html! { - head { - (DOCTYPE) - meta charset="utf-8"; - link rel="stylesheet" type="text/css" href="/static/styles.css"; - title { (page_title) } - } - } -} - -pub async fn build_page<'a, T: Sized + ProductMarker + PageBuilder<'a> + ProductDatabaseHandler>( - ctx: Extension, -) -> Result { - Ok(html!( - (page( - T::page_title(), - cards( - get_all_specificproducts::(&ctx.pool).await?, - T::page_title() - ) - )) - )) -} - -pub trait PageBuilder<'a> { - fn page_title() -> &'a str; - fn product_specific_card_content + ProductMarker>( - &self, - specific_product: &SpecificProduct, - ) -> Markup; -} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs deleted file mode 100644 index 3235f24..0000000 --- a/src/handlers/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::sync::Arc; -use tokio::sync::Mutex; - -use axum::extract::DefaultBodyLimit; -use axum::middleware::{self}; -use axum::routing::{delete, get, post}; -use axum::Router; -use sqlx::SqlitePool; -use tower_http::add_extension::AddExtensionLayer; -use tower_http::services::ServeDir; - -use admin::{admin_healthcheck, admin_middleware, delete_story, generate_token, upload_story}; -use frontend_builder::home; - -use crate::db::story::Story; - -use self::admin::{get_products, get_stories}; -use self::frontend_builder::build_page; - -mod admin; -pub mod frontend_builder; - -#[derive(Debug, Clone)] -pub struct ApiContext { - pool: SqlitePool, -} - -#[derive(Clone)] -struct AppState { - admin_token: Arc>, -} - -pub async fn router(pool: SqlitePool) -> Router { - let state = AppState { - admin_token: Arc::new(Mutex::new(generate_token())), - }; - - Router::new() - .route("/admin/story/:story_id", delete(delete_story)) - .route("/admin/story", post(upload_story).get(get_stories)) - .route("/admin/product", get(get_products)) - .route("/admin", get(admin_healthcheck)) - .layer(middleware::from_fn_with_state( - state.clone(), - admin_middleware, - )) - .with_state(state) - .route("/", get(home)) - .route("/stories", get(build_page::)) - .nest_service("/static", ServeDir::new("static")) - .layer(DefaultBodyLimit::max(1074000000)) - .layer(AddExtensionLayer::new(ApiContext { pool })) -} -- cgit v1.2.3