summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWriterV2 <primary-v2@protonmail.com>2024-10-28 19:28:41 +0100
committerWriterV2 <primary-v2@protonmail.com>2024-10-28 19:28:41 +0100
commit977c93ef0568aa488c226915d14e6f164519fd98 (patch)
tree276dd1237f51d1b7fab526b3f12a9b798d0f5209 /src
parent04e19765aca471fd85199714699aaf827d04608d (diff)
Feat: Add admin API route to add story
Diffstat (limited to 'src')
-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
4 files changed, 107 insertions, 19 deletions
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;