diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | .sqlx/query-40e5b26d2df5aa2cc92c1a45c47560092b4cbb50dd1aa24320afc6a729f16cfe.json | 44 | ||||
| -rw-r--r-- | .sqlx/query-710176c919e6c108a8c0c982821a9c17e71ea118e5b67265136e04e2a59a5df9.json | 32 | ||||
| -rw-r--r-- | Dockerfile | 6 | ||||
| -rw-r--r-- | src/db/story.rs | 28 | ||||
| -rw-r--r-- | src/handlers/admin.rs | 11 | ||||
| -rw-r--r-- | src/main.rs | 6 |
7 files changed, 126 insertions, 3 deletions
@@ -2,3 +2,5 @@ /target *.db* .env +static/*.pdf +static/*.epub diff --git a/.sqlx/query-40e5b26d2df5aa2cc92c1a45c47560092b4cbb50dd1aa24320afc6a729f16cfe.json b/.sqlx/query-40e5b26d2df5aa2cc92c1a45c47560092b4cbb50dd1aa24320afc6a729f16cfe.json new file mode 100644 index 0000000..331604f --- /dev/null +++ b/.sqlx/query-40e5b26d2df5aa2cc92c1a45c47560092b4cbb50dd1aa24320afc6a729f16cfe.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT * FROM product", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "uploaddate", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "updatedate", + "ordinal": 4, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "40e5b26d2df5aa2cc92c1a45c47560092b4cbb50dd1aa24320afc6a729f16cfe" +} diff --git a/.sqlx/query-710176c919e6c108a8c0c982821a9c17e71ea118e5b67265136e04e2a59a5df9.json b/.sqlx/query-710176c919e6c108a8c0c982821a9c17e71ea118e5b67265136e04e2a59a5df9.json new file mode 100644 index 0000000..c0d5519 --- /dev/null +++ b/.sqlx/query-710176c919e6c108a8c0c982821a9c17e71ea118e5b67265136e04e2a59a5df9.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "SELECT p.name, s.pdf, s.epub FROM story s INNER JOIN product p ON s.pid = p.id", + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "pdf", + "ordinal": 1, + "type_info": "Blob" + }, + { + "name": "epub", + "ordinal": 2, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "710176c919e6c108a8c0c982821a9c17e71ea118e5b67265136e04e2a59a5df9" +} @@ -1,5 +1,5 @@ # Planner stage - Compute dependencies and create recipe -FROM lukemathwalker/cargo-chef:latest-rust-1.75 AS planner +FROM lukemathwalker/cargo-chef:latest-rust-1.82 AS planner WORKDIR /app # Copy the minimal files needed for dependency calculation COPY Cargo.* ./ @@ -7,13 +7,13 @@ COPY src/ src/ RUN cargo chef prepare --recipe-path recipe.json # Cache dependencies stage -FROM lukemathwalker/cargo-chef:latest-rust-1.75 AS cacher +FROM lukemathwalker/cargo-chef:latest-rust-1.82 AS cacher WORKDIR /app COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json # Builder stage -FROM lukemathwalker/cargo-chef:latest-rust-1.75 AS builder +FROM lukemathwalker/cargo-chef:latest-rust-1.82 AS builder WORKDIR /app # Copy the entire project context diff --git a/src/db/story.rs b/src/db/story.rs index 686c33f..0680e94 100644 --- a/src/db/story.rs +++ b/src/db/story.rs @@ -1,3 +1,7 @@ +use std::ffi::OsStr; +use std::fs::{read_dir, remove_file}; +use std::io::{BufWriter, Write}; + use axum::async_trait; use maud::html; use sqlx::SqlitePool; @@ -25,6 +29,30 @@ impl ProductMarker for Story { } } +pub async fn synchronize_story_files(pool: &SqlitePool) -> Result<(), anyhow::Error> { + for entry in read_dir("static")? { + let path = entry?.path(); + if path.extension().is_some_and(|ext| ext == OsStr::new("pdf") || ext == OsStr::new("epub")) { + remove_file(path)?; + } + } + let results = sqlx::query!("SELECT p.name, s.pdf, s.epub FROM story s INNER JOIN product p ON s.pid = p.id").fetch_all(pool).await?; + for result in results.iter() { + let mut pdf_filename = format!("{}.pdf", result.name).to_lowercase(); + pdf_filename.retain(|c| !c.is_whitespace()); + + let file = std::fs::File::create_new(format!("static/{}", pdf_filename))?; + BufWriter::new(file).write_all(&result.pdf.to_vec())?; + + let mut epub_filename = format!("{}.epub", result.name).to_lowercase(); + epub_filename.retain(|c| !c.is_whitespace()); + + let file = std::fs::File::create_new(format!("static/{}", epub_filename))?; + BufWriter::new(file).write_all(&result.epub.to_vec())?; + } + Ok(()) +} + #[async_trait] impl ProductDatabaseHandler for Story { async fn get_all(pool: &SqlitePool) -> Result<Vec<Self>, AppError> diff --git a/src/handlers/admin.rs b/src/handlers/admin.rs index af7aa42..6cfe133 100644 --- a/src/handlers/admin.rs +++ b/src/handlers/admin.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::io::{BufWriter, Write}; use anyhow::Context; use axum::body::Bytes; @@ -94,12 +95,22 @@ pub async fn upload_story( }; let pdf = if let Some(pdf_value) = form.get("pdf") { + let mut filename = format!("{}.pdf", name).to_lowercase(); + filename.retain(|c| !c.is_whitespace()); + + let file = std::fs::File::create_new(format!("static/{}", filename))?; + BufWriter::new(file).write_all(pdf_value)?; pdf_value.to_vec() } else { return Ok(StatusCode::BAD_REQUEST.into_response()); }; let epub = if let Some(epub_value) = form.get("epub") { + let mut filename = format!("{}.epub", name).to_lowercase(); + filename.retain(|c| !c.is_whitespace()); + + let file = std::fs::File::create_new(format!("static/{}", filename))?; + BufWriter::new(file).write_all(epub_value)?; epub_value.to_vec() } else { return Ok(StatusCode::BAD_REQUEST.into_response()); diff --git a/src/main.rs b/src/main.rs index ffdce27..dfe1447 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use std::env; use dotenvy::dotenv; use sqlx::sqlite::SqlitePoolOptions; +use self::db::story; + pub mod db; mod error; mod handlers; @@ -23,6 +25,10 @@ async fn main() { .await .expect("Failed to run migrations"); + story::synchronize_story_files(&pool) + .await + .expect("Failed to synchronize files"); + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, handlers::router(pool).await.into_make_service()) .await |
