summaryrefslogtreecommitdiff
path: root/src/handlers/admin.rs
blob: bdb2b4010ccacbbf71340639be1d9a32b764a498 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::collections::HashMap;
use std::env;

use anyhow::Context;
use axum::body::Bytes;
use axum::extract::{Multipart, Request};
use axum::http::{HeaderMap, StatusCode};
use axum::middleware::Next;
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,
    next: Next,
) -> Result<Response, StatusCode> {
    let expected_token = env::var("TOKEN").map_err(|_| {
        eprintln!("Error: TOKEN environment variable is not set");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    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");
        return Err(StatusCode::UNAUTHORIZED);
    }

    Ok(next.run(request).await)
}

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())
}