diff --git a/.gitignore b/.gitignore index 405d8d3..f38a7fa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea *.iml Cargo.lock +config.toml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index aaf06bd..64d949a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,12 @@ edition = "2018" [dependencies] tokio = { version = "1.0", features = ["full"] } -axum = "0.2.1" +axum = { version = "0.2.8", features = ["headers"] } tower = { version = "0.4", features = ["util", "timeout"] } -tower-http = { version = "0.1", features = ["add-extension", "trace"] } +tower-http = { version = "0.1", features = ["add-extension", "trace", "fs", "set-header"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.51" sqlx = { version = "0.3", features = [ "mysql" ] } rand = "0.8.0" +structopt = "0.3.22" +toml = "0.5.8" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..c8e4b24 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,29 @@ +use std::net::SocketAddr; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + pub addr: SocketAddr, + pub database: String, + pub cdn: String, +} + +pub struct ConfVars { + pub cdn: String, +} + +impl Config { + + pub fn vars(&self) -> ConfVars { + ConfVars { + cdn: self.cdn.clone(), + } + } + +} + +impl Clone for ConfVars { + fn clone(&self) -> Self { + Self { cdn: self.cdn.clone() } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 964c5ac..ef8d189 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,42 @@ -use std::{io, env}; +use std::{env, io, path::PathBuf}; +use config::Config; use sqlx::MySqlPool; -use std::net::SocketAddr; -use axum::Router; -use tower_http::add_extension::AddExtensionLayer; +use structopt::StructOpt; +use axum::{Router, body::Body, http::{HeaderValue, Request, header}}; +use tower_http::{add_extension::AddExtensionLayer, set_header::SetResponseHeaderLayer}; mod v1; +mod config; + +#[derive(StructOpt)] +struct Opt { + #[structopt( + short, + long, + help = "config file to use", + default_value = "./config.toml" + )] + config: PathBuf, +} #[tokio::main] async fn main() { - let database_url = env::var("DBURL").unwrap(); - let db_pool = MySqlPool::new(&database_url).await.unwrap(); + let opt = Opt::from_args(); + let config = std::fs::read(&opt.config).expect("Config file reading error"); + let config = toml::from_slice::(&config).expect("Config file parsing error"); + + let db_pool = MySqlPool::new(&config.database).await.expect("Database connection error"); let app = Router::new() .nest("/v1", v1::routes()) - .layer(AddExtensionLayer::new(db_pool)); + .layer(AddExtensionLayer::new(db_pool)) + .layer(AddExtensionLayer::new(config.vars())) + .layer(SetResponseHeaderLayer::<_, Request>::if_not_present(header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"))); - let addr: SocketAddr = env::var("LISTEN").expect("The LISTEN env var ist not set").parse().expect("The LISTEN env var is set incorrectly"); - - axum::Server::bind(&addr) + axum::Server::bind(&config.addr) .serve(app.into_make_service()) .await .expect("Something went wrong :("); } + diff --git a/src/v1/models.rs b/src/v1/models.rs index b65c3bd..2647451 100644 --- a/src/v1/models.rs +++ b/src/v1/models.rs @@ -7,6 +7,7 @@ pub struct Meme { pub category: String, pub user: String, pub timestamp: String, + pub ipfs: Option, } #[derive(Serialize)] diff --git a/src/v1/routes.rs b/src/v1/routes.rs index 249f7e3..9c3e43d 100644 --- a/src/v1/routes.rs +++ b/src/v1/routes.rs @@ -1,3 +1,4 @@ +use crate::config::ConfVars; use crate::v1::models::*; use sqlx::{MySqlPool, Error}; use axum::{Router, Json}; @@ -7,8 +8,8 @@ use axum::handler::get; use axum::extract::{Query, Extension}; use axum::http::StatusCode; -async fn meme(params: Query, Extension(db_pool): Extension) -> impl IntoResponse { - let q = Meme::get(params.id, &db_pool).await; +async fn meme(params: Query, Extension(db_pool): Extension, Extension(vars): Extension) -> impl IntoResponse { + let q = Meme::get(params.id, &db_pool, vars.cdn).await; match q { Ok(meme) => (StatusCode::OK, Json(MemeResponse { status: 200, @@ -30,8 +31,8 @@ async fn meme(params: Query, Extension(db_pool): Extension, Extension(db_pool): Extension) -> impl IntoResponse { - let q = Meme::get_all(params.0, &db_pool).await; +async fn memes(params: Query, Extension(db_pool): Extension, Extension(vars): Extension) -> impl IntoResponse { + let q = Meme::get_all(params.0, &db_pool, vars.cdn).await; match q { Ok(memes) => (StatusCode::OK, Json(MemesResponse { status: 200, @@ -101,8 +102,8 @@ async fn users(Extension(db_pool): Extension) -> impl IntoResponse { } } -async fn random(params: Query, Extension(db_pool): Extension) -> impl IntoResponse { - let q = Meme::get_random(params.0, &db_pool).await; +async fn random(params: Query, Extension(db_pool): Extension, Extension(vars): Extension) -> impl IntoResponse { + let q = Meme::get_random(params.0, &db_pool, vars.cdn).await; match q { Ok(random) => (StatusCode::OK, Json(MemeResponse { status: 200, diff --git a/src/v1/sql.rs b/src/v1/sql.rs index 5f80189..069e241 100644 --- a/src/v1/sql.rs +++ b/src/v1/sql.rs @@ -10,72 +10,75 @@ pub struct DBMeme { pub userdir: String, pub category: String, pub timestamp: i64, + pub ipfs: Option, } - impl Meme { - pub async fn get(id: i32, pool: &MySqlPool) -> Result { - let q: Meme = sqlx::query("SELECT memes.id, user, filename, category, name, UNIX_TIMESTAMP(timestamp) AS ts FROM memes, users WHERE memes.user = users.id AND memes.id=?").bind(id) - .map(|row: MySqlRow| Meme::from(DBMeme { + + pub fn new(meme: DBMeme, cdn: String) -> Self { + Meme { + id: meme.id.to_string(), + link: format!("{}/{}/{}", cdn, meme.userdir, meme.filename), + category: meme.category, + user: meme.user, + timestamp: meme.timestamp.to_string(), + ipfs: meme.ipfs, + } + } + + pub async fn get(id: i32, pool: &MySqlPool, cdn: String) -> Result { + let q: Meme = sqlx::query("SELECT memes.id, user, filename, category, name, UNIX_TIMESTAMP(timestamp) AS ts, cid FROM memes, users WHERE memes.user = users.id AND memes.id=?").bind(id) + .map(|row: MySqlRow| Meme::new(DBMeme { id: row.get("id"), filename: row.get("filename"), user: row.get("name"), userdir: row.get("user"), category: row.get("category"), timestamp: row.get("ts"), - })) + ipfs: row.get("cid"), + }, cdn.clone())) .fetch_one(pool).await?; Ok(q) } - pub async fn get_all(params: MemeFilterQuery, pool: &MySqlPool) -> Result> { - let q: Vec = sqlx::query("SELECT memes.id, user, filename, category, name, UNIX_TIMESTAMP(timestamp) AS ts FROM memes, users WHERE memes.user = users.id AND (category LIKE ? AND name LIKE ? AND filename LIKE ?) ORDER BY memes.id") + pub async fn get_all(params: MemeFilterQuery, pool: &MySqlPool, cdn: String) -> Result> { + let q: Vec = sqlx::query("SELECT memes.id, user, filename, category, name, UNIX_TIMESTAMP(timestamp) AS ts, cid FROM memes, users WHERE memes.user = users.id AND (category LIKE ? AND name LIKE ? AND filename LIKE ?) ORDER BY memes.id") .bind(params.category.unwrap_or(String::from("%"))) .bind(format!("%{}%", params.user.unwrap_or(String::from("")))) .bind(format!("%{}%", params.search.unwrap_or(String::from("")))) - .map(|row: MySqlRow| Meme::from(DBMeme { + .map(|row: MySqlRow| Meme::new(DBMeme { id: row.get("id"), filename: row.get("filename"), user: row.get("name"), userdir: row.get("user"), category: row.get("category"), timestamp: row.get("ts"), - })) + ipfs: row.get("cid"), + }, cdn.clone())) .fetch_all(pool).await?; Ok(q) } - pub async fn get_random(params: MemeFilterQuery, pool: &MySqlPool) -> Result { - let q: Meme = sqlx::query("SELECT memes.id, user, filename, category, name, UNIX_TIMESTAMP(timestamp) AS ts FROM memes, users WHERE memes.user = users.id AND (category LIKE ? AND name LIKE ? AND filename LIKE ?) ORDER BY RAND() LIMIT 1") + pub async fn get_random(params: MemeFilterQuery, pool: &MySqlPool, cdn: String) -> Result { + let q: Meme = sqlx::query("SELECT memes.id, user, filename, category, name, UNIX_TIMESTAMP(timestamp) AS ts, cid FROM memes, users WHERE memes.user = users.id AND (category LIKE ? AND name LIKE ? AND filename LIKE ?) ORDER BY RAND() LIMIT 1") .bind(params.category.unwrap_or(String::from("%"))) .bind(format!("%{}%", params.user.unwrap_or(String::from("")))) .bind(format!("%{}%", params.search.unwrap_or(String::from("")))) - .map(|row: MySqlRow| Meme::from(DBMeme { + .map(|row: MySqlRow| Meme::new(DBMeme { id: row.get("id"), filename: row.get("filename"), user: row.get("name"), userdir: row.get("user"), category: row.get("category"), timestamp: row.get("ts"), - })) + ipfs: row.get("cid"), + }, cdn.clone())) .fetch_one(pool).await?; Ok(q) } } -impl From for Meme { - fn from(meme: DBMeme) -> Self { - Meme { - id: meme.id.to_string(), - link: format!("{}/{}/{}", env::var("CDNURL").unwrap(), meme.userdir, meme.filename), - category: meme.category, - user: meme.user, - timestamp: meme.timestamp.to_string(), - } - } -} - impl Category { pub async fn get(id: &String, pool: &MySqlPool) -> Result { let q: Category = sqlx::query("SELECT * FROM categories WHERE id=?").bind(id)