summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock104
-rw-r--r--Cargo.toml3
-rw-r--r--src/db/mod.rs16
-rw-r--r--src/handlers/admin.rs92
-rw-r--r--src/handlers/mod.rs16
-rw-r--r--src/main.rs2
6 files changed, 202 insertions, 31 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 72a89f5..0a0b4fa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -100,9 +100,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
-version = "0.7.5"
+version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
+checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
dependencies = [
"async-trait",
"axum-core",
@@ -117,6 +117,7 @@ dependencies = [
"matchit",
"memchr",
"mime",
+ "multer",
"percent-encoding",
"pin-project-lite",
"rustversion",
@@ -126,7 +127,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
- "tower",
+ "tower 0.5.1",
"tower-layer",
"tower-service",
"tracing",
@@ -134,9 +135,9 @@ dependencies = [
[[package]]
name = "axum-core"
-version = "0.4.3"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
@@ -147,7 +148,30 @@ dependencies = [
"mime",
"pin-project-lite",
"rustversion",
- "sync_wrapper 0.1.2",
+ "sync_wrapper 1.0.1",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-extra"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9"
+dependencies = [
+ "axum",
+ "axum-core",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "serde",
+ "serde_html_form",
+ "tower 0.5.1",
"tower-layer",
"tower-service",
"tracing",
@@ -381,6 +405,15 @@ dependencies = [
]
[[package]]
+name = "encoding_rs"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -728,6 +761,8 @@ dependencies = [
"hyper",
"pin-project-lite",
"tokio",
+ "tower 0.4.13",
+ "tower-service",
]
[[package]]
@@ -919,6 +954,23 @@ dependencies = [
]
[[package]]
+name = "multer"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
+dependencies = [
+ "bytes",
+ "encoding_rs",
+ "futures-util",
+ "http",
+ "httparse",
+ "memchr",
+ "mime",
+ "spin",
+ "version_check",
+]
+
+[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1166,6 +1218,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"axum",
+ "axum-extra",
"dotenvy",
"maud",
"serde",
@@ -1374,6 +1427,19 @@ dependencies = [
]
[[package]]
+name = "serde_html_form"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5"
+dependencies = [
+ "form_urlencoded",
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
name = "serde_json"
version = "1.0.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1858,6 +1924,22 @@ dependencies = [
]
[[package]]
+name = "tower"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper 0.1.2",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
name = "tower-http"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1881,7 +1963,7 @@ dependencies = [
"pin-project-lite",
"tokio",
"tokio-util",
- "tower",
+ "tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
@@ -1890,15 +1972,15 @@ dependencies = [
[[package]]
name = "tower-layer"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
diff --git a/Cargo.toml b/Cargo.toml
index 47f2399..0445a55 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,8 +10,9 @@ serde = {version = "1.0", features = ["derive"]}
anyhow = "1.0"
tokio = { version = "1.39.2", features = ["full"] }
maud = { version = "0.26.0", features = ["axum"] }
-axum = "0.7.5"
+axum = { version = "0.7.5", features = ["multipart"] }
tower-http = { version = "0.5.2", features = ["full", "fs"] }
sqlx = { version = "0.8.2", features = ["runtime-tokio", "tls-native-tls", "sqlite", "macros"] }
dotenvy = "0.15.7"
subtle = "2.6.1"
+axum-extra = { version = "0.9.4", features = ["query"] }
diff --git a/src/db/mod.rs b/src/db/mod.rs
index b5b1488..01111d8 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1,8 +1,8 @@
-mod game;
-mod gametag;
-mod product;
-mod story;
-mod storytag;
-mod tag;
-mod tool;
-mod tooltag;
+pub mod game;
+pub mod gametag;
+pub mod product;
+pub mod story;
+pub mod storytag;
+pub mod tag;
+pub mod tool;
+pub mod tooltag;
diff --git a/src/handlers/admin.rs b/src/handlers/admin.rs
index 349275b..bdb2b40 100644
--- a/src/handlers/admin.rs
+++ b/src/handlers/admin.rs
@@ -1,11 +1,21 @@
+use std::collections::HashMap;
use std::env;
-use axum::extract::Request;
+use anyhow::Context;
+use axum::body::Bytes;
+use axum::extract::{Multipart, Request};
use axum::http::{HeaderMap, StatusCode};
use axum::middleware::Next;
-use axum::response::Response;
+use axum::response::{IntoResponse, Response};
+use axum::{Extension, Json};
use subtle::ConstantTimeEq;
+use crate::db::product::Product;
+use crate::db::story::Story;
+use crate::error::AppError;
+
+use super::ApiContext;
+
pub async fn admin_middleware(
headers: HeaderMap,
request: Request,
@@ -21,9 +31,9 @@ pub async fn admin_middleware(
.and_then(|header| header.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
- if !expected_token
+ if expected_token
.as_bytes()
- .ct_eq(token.as_bytes())
+ .ct_ne(token.as_bytes())
.unwrap_u8()
== 1
{
@@ -34,6 +44,76 @@ pub async fn admin_middleware(
Ok(next.run(request).await)
}
-pub async fn admin_handler() -> &'static str {
- "Hello World"
+pub async fn admin_healthcheck() -> impl IntoResponse {
+ StatusCode::OK
+}
+
+pub async fn upload_story(
+ ctx: Extension<ApiContext>,
+ mut multipart: Multipart,
+) -> Result<impl IntoResponse, AppError> {
+ let mut form: HashMap<String, Bytes> = 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") {
+ String::from_utf8(name_value.to_vec()).with_context(|| "Failed to parse name")?
+ } else {
+ return Ok(StatusCode::BAD_REQUEST.into_response());
+ };
+
+ let description = if let Some(description_value) = form.get("description") {
+ String::from_utf8(description_value.to_vec())
+ .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") {
+ pdf_value.to_vec()
+ } else {
+ return Ok(StatusCode::BAD_REQUEST.into_response());
+ };
+
+ let epub = if let Some(epub_value) = form.get("epub") {
+ epub_value.to_vec()
+ } else {
+ return Ok(StatusCode::BAD_REQUEST.into_response());
+ };
+
+ let time = std::time::SystemTime::now();
+ let uploaddate = time.duration_since(std::time::UNIX_EPOCH)?.as_millis() as i64;
+
+ let mut tx = ctx.pool.begin().await?;
+ let product = sqlx::query_as!(
+ Product,
+ "INSERT INTO product (name, description, uploaddate, updatedate) VALUES ($1, $2, $3, $4) RETURNING id, name, description, uploaddate, updatedate",
+ name,
+ description,
+ uploaddate,
+ uploaddate)
+ .fetch_one(&mut *tx)
+ .await?;
+
+ let story = sqlx::query_as!(
+ Story,
+ "INSERT INTO story (language, pdf, epub, pid) VALUES ($1, $2, $3, $4) RETURNING id, language, pdf, epub, pid", language,
+ pdf,
+ epub,
+ product.id)
+ .fetch_one(&mut *tx)
+ .await?;
+
+ tx.commit().await?;
+
+ Ok((StatusCode::CREATED, Json(story)).into_response())
}
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index d69f590..d346512 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -1,21 +1,29 @@
use axum::extract::DefaultBodyLimit;
use axum::middleware::{self};
-use axum::routing::get;
+use axum::routing::{get, post};
use axum::Router;
use sqlx::SqlitePool;
+use tower_http::add_extension::AddExtensionLayer;
use tower_http::services::ServeDir;
-use admin::{admin_handler, admin_middleware};
+use admin::{admin_healthcheck, admin_middleware, upload_story};
use frontend::home;
mod admin;
mod frontend;
+#[derive(Debug, Clone)]
+struct ApiContext {
+ pool: SqlitePool,
+}
+
pub async fn router(pool: SqlitePool) -> Router {
Router::new()
- .route("/admin", get(admin_handler))
+ .route("/admin/story", post(upload_story))
+ .route("/admin", get(admin_healthcheck))
.layer(middleware::from_fn(admin_middleware))
.route("/", get(home))
.nest_service("/static", ServeDir::new("src/static"))
- .layer(DefaultBodyLimit::max(10000))
+ .layer(DefaultBodyLimit::max(1074000000))
+ .layer(AddExtensionLayer::new(ApiContext { pool }))
}
diff --git a/src/main.rs b/src/main.rs
index b4d7567..dfdf658 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,7 @@ use std::env;
use dotenvy::dotenv;
use sqlx::sqlite::SqlitePoolOptions;
-mod db;
+pub mod db;
mod error;
mod handlers;