Improve error handling
This commit is contained in:
parent
8a3e569fa3
commit
c90d0b70b9
11 changed files with 321 additions and 207 deletions
46
src/cdn/error.rs
Normal file
46
src/cdn/error.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use std::{convert::Infallible, string::FromUtf8Error};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::{Bytes, Empty},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
|
pub struct Error(StatusCode);
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Error(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for Error {
|
||||||
|
type Body = Empty<Bytes>;
|
||||||
|
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> axum::http::Response<Self::Body> {
|
||||||
|
self.0.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sqlx::Error> for Error {
|
||||||
|
fn from(err: sqlx::Error) -> Self {
|
||||||
|
match err {
|
||||||
|
sqlx::Error::RowNotFound => Error(StatusCode::NOT_FOUND),
|
||||||
|
_ => Error(StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(err: reqwest::Error) -> Self {
|
||||||
|
Error(err.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FromUtf8Error> for Error {
|
||||||
|
fn from(_: FromUtf8Error) -> Self {
|
||||||
|
Error(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,27 @@
|
||||||
|
use axum::{
|
||||||
use axum::{Router, body::Body, extract::{Extension, Path}, handler::get, http::HeaderMap, response::IntoResponse, routing::BoxRoute};
|
body::Body,
|
||||||
|
extract::{Extension, Path},
|
||||||
|
handler::get,
|
||||||
|
http::HeaderMap,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::BoxRoute,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use headers::{ContentType, HeaderMapExt};
|
use headers::{ContentType, HeaderMapExt};
|
||||||
use reqwest::{StatusCode, header::{CONTENT_LENGTH, HeaderName}};
|
use reqwest::{
|
||||||
use sqlx::{Error, MySqlPool};
|
header::{HeaderName, CONTENT_LENGTH},
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
|
use sqlx::MySqlPool;
|
||||||
|
|
||||||
use crate::config::ConfVars;
|
use crate::config::ConfVars;
|
||||||
|
|
||||||
use self::templates::{DirTemplate, HtmlTemplate};
|
use self::{
|
||||||
|
error::Error,
|
||||||
|
templates::{DirTemplate, HtmlTemplate},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod error;
|
||||||
mod sql;
|
mod sql;
|
||||||
mod templates;
|
mod templates;
|
||||||
|
|
||||||
|
@ -19,44 +33,40 @@ pub fn routes() -> Router<BoxRoute> {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn image(Path((user, filename)): Path<(String, String)>, Extension(db_pool): Extension<MySqlPool>, Extension(vars): Extension<ConfVars>) -> Result<impl IntoResponse, StatusCode> {
|
async fn image(
|
||||||
let filename = urlencoding::decode(&filename).map_err(|err| StatusCode::INTERNAL_SERVER_ERROR)?.into_owned();
|
Path((user, filename)): Path<(String, String)>,
|
||||||
let q = sql::get_cid(user, filename.clone(), &db_pool).await;
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
match q {
|
Extension(vars): Extension<ConfVars>,
|
||||||
Ok(cid) => {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
let ipfsapi = vars.ipfs_client();
|
let filename = urlencoding::decode(&filename)?.into_owned();
|
||||||
match ipfsapi {
|
let cid = sql::get_cid(user, filename.clone(), &db_pool).await?;
|
||||||
Ok(ipfs) => {
|
let ipfs = vars.ipfs_client()?;
|
||||||
let res = ipfs.cat(cid).await;
|
let res = ipfs.cat(cid).await?;
|
||||||
match res {
|
let clength = res
|
||||||
Ok(r) => {
|
.headers()
|
||||||
let clength = r.headers().get(HeaderName::from_static("x-content-length"));
|
.get(HeaderName::from_static("x-content-length"));
|
||||||
match clength {
|
match clength {
|
||||||
Some(h) => {
|
Some(h) => {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
let ctype = ContentType::from(new_mime_guess::from_path(filename).first_or_octet_stream());
|
let ctype =
|
||||||
|
ContentType::from(new_mime_guess::from_path(filename).first_or_octet_stream());
|
||||||
headers.typed_insert(ctype);
|
headers.typed_insert(ctype);
|
||||||
headers.insert(CONTENT_LENGTH, h.clone());
|
headers.insert(CONTENT_LENGTH, h.clone());
|
||||||
|
|
||||||
Ok((StatusCode::OK, headers, Body::wrap_stream(r.bytes_stream())))
|
Ok((
|
||||||
},
|
StatusCode::OK,
|
||||||
None => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
headers,
|
||||||
|
Body::wrap_stream(res.bytes_stream()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
},
|
None => Err(Error::new()),
|
||||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => match err {
|
|
||||||
Error::RowNotFound => Err(StatusCode::NOT_FOUND),
|
|
||||||
_ => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn users(Extension(db_pool): Extension<MySqlPool>, Extension(vars): Extension<ConfVars>) -> Result<impl IntoResponse, StatusCode> {
|
async fn users(
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
Extension(vars): Extension<ConfVars>,
|
||||||
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
let q = sql::get_users(&db_pool).await;
|
let q = sql::get_users(&db_pool).await;
|
||||||
match q {
|
match q {
|
||||||
Ok(users) => Ok(HtmlTemplate(DirTemplate {
|
Ok(users) => Ok(HtmlTemplate(DirTemplate {
|
||||||
|
@ -68,7 +78,10 @@ async fn users(Extension(db_pool): Extension<MySqlPool>, Extension(vars): Extens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn memes(Path(user): Path<String>, Extension(db_pool): Extension<MySqlPool>) -> Result<impl IntoResponse, StatusCode> {
|
async fn memes(
|
||||||
|
Path(user): Path<String>,
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
let q = sql::get_memes(user, &db_pool).await;
|
let q = sql::get_memes(user, &db_pool).await;
|
||||||
match q {
|
match q {
|
||||||
Ok(memes) => Ok(HtmlTemplate(DirTemplate {
|
Ok(memes) => Ok(HtmlTemplate(DirTemplate {
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
use sqlx::{MySqlPool, Result, Row, mysql::MySqlRow};
|
use sqlx::{mysql::MySqlRow, MySqlPool, Result, Row};
|
||||||
|
|
||||||
|
|
||||||
pub async fn get_cid(user: String, filename: String, pool: &MySqlPool) -> Result<String> {
|
pub async fn get_cid(user: String, filename: String, pool: &MySqlPool) -> Result<String> {
|
||||||
let q: String = sqlx::query("SELECT cid FROM memes WHERE user = ? AND filename = ? ORDER BY id DESC").bind(user).bind(filename)
|
let q: String =
|
||||||
|
sqlx::query("SELECT cid FROM memes WHERE user = ? AND filename = ? ORDER BY id DESC")
|
||||||
|
.bind(user)
|
||||||
|
.bind(filename)
|
||||||
.map(|row: MySqlRow| row.get("cid"))
|
.map(|row: MySqlRow| row.get("cid"))
|
||||||
.fetch_one(pool).await?;
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_memes(user: String, pool: &MySqlPool) -> Result<Vec<String>> {
|
pub async fn get_memes(user: String, pool: &MySqlPool) -> Result<Vec<String>> {
|
||||||
let q: Vec<String> = sqlx::query("SELECT filename FROM memes WHERE user = ? ORDER BY filename").bind(user)
|
let q: Vec<String> = sqlx::query("SELECT filename FROM memes WHERE user = ? ORDER BY filename")
|
||||||
|
.bind(user)
|
||||||
.map(|row: MySqlRow| row.get("filename"))
|
.map(|row: MySqlRow| row.get("filename"))
|
||||||
.fetch_all(pool).await?;
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_users(pool: &MySqlPool) -> Result<Vec<String>> {
|
pub async fn get_users(pool: &MySqlPool) -> Result<Vec<String>> {
|
||||||
let q: Vec<String> = sqlx::query("SELECT id FROM users ORDER BY id")
|
let q: Vec<String> = sqlx::query("SELECT id FROM users ORDER BY id")
|
||||||
.map(|row: MySqlRow| row.get("id"))
|
.map(|row: MySqlRow| row.get("id"))
|
||||||
.fetch_all(pool).await?;
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::response::{IntoResponse, Html};
|
use axum::body::{Bytes, Full};
|
||||||
use axum::body::{Full, Bytes};
|
|
||||||
use axum::http::{Response, StatusCode};
|
use axum::http::{Response, StatusCode};
|
||||||
|
use axum::response::{Html, IntoResponse};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
pub struct HtmlTemplate<T>(pub T);
|
pub struct HtmlTemplate<T>(pub T);
|
||||||
|
@ -16,7 +16,7 @@ impl<T> IntoResponse for HtmlTemplate<T>
|
||||||
fn into_response(self) -> Response<Self::Body> {
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
match self.0.render() {
|
match self.0.render() {
|
||||||
Ok(html) => Html(html).into_response(),
|
Ok(html) => Html(html).into_response(),
|
||||||
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "").into_response()
|
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "").into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::net::SocketAddr;
|
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -16,18 +16,19 @@ pub struct ConfVars {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|
||||||
pub fn vars(&self) -> ConfVars {
|
pub fn vars(&self) -> ConfVars {
|
||||||
ConfVars {
|
ConfVars {
|
||||||
cdn: self.cdn.clone(),
|
cdn: self.cdn.clone(),
|
||||||
ipfs_api: self.ipfs_api.clone(),
|
ipfs_api: self.ipfs_api.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for ConfVars {
|
impl Clone for ConfVars {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self { cdn: self.cdn.clone(), ipfs_api: self.ipfs_api.clone() }
|
Self {
|
||||||
|
cdn: self.cdn.clone(),
|
||||||
|
ipfs_api: self.ipfs_api.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
21
src/main.rs
21
src/main.rs
|
@ -1,14 +1,18 @@
|
||||||
use std::path::PathBuf;
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{header, HeaderValue, Request},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use sqlx::MySqlPool;
|
use sqlx::MySqlPool;
|
||||||
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use axum::{Router, body::Body, http::{HeaderValue, Request, header}};
|
|
||||||
use tower_http::{add_extension::AddExtensionLayer, set_header::SetResponseHeaderLayer};
|
use tower_http::{add_extension::AddExtensionLayer, set_header::SetResponseHeaderLayer};
|
||||||
|
|
||||||
mod v1;
|
|
||||||
mod cdn;
|
mod cdn;
|
||||||
mod config;
|
mod config;
|
||||||
mod ipfs;
|
mod ipfs;
|
||||||
|
mod v1;
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
|
@ -23,23 +27,26 @@ struct Opt {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
let config = std::fs::read(&opt.config).expect("Config file reading error");
|
let config = std::fs::read(&opt.config).expect("Config file reading error");
|
||||||
let config = toml::from_slice::<Config>(&config).expect("Config file parsing error");
|
let config = toml::from_slice::<Config>(&config).expect("Config file parsing error");
|
||||||
|
|
||||||
let db_pool = MySqlPool::new(&config.database).await.expect("Database connection error");
|
let db_pool = MySqlPool::new(&config.database)
|
||||||
|
.await
|
||||||
|
.expect("Database connection error");
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/api/v1", v1::routes())
|
.nest("/api/v1", v1::routes())
|
||||||
.nest("/cdn", cdn::routes())
|
.nest("/cdn", cdn::routes())
|
||||||
.layer(AddExtensionLayer::new(db_pool))
|
.layer(AddExtensionLayer::new(db_pool))
|
||||||
.layer(AddExtensionLayer::new(config.vars()))
|
.layer(AddExtensionLayer::new(config.vars()))
|
||||||
.layer(SetResponseHeaderLayer::<_, Request<Body>>::if_not_present(header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")));
|
.layer(SetResponseHeaderLayer::<_, Request<Body>>::if_not_present(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
HeaderValue::from_static("*"),
|
||||||
|
));
|
||||||
|
|
||||||
axum::Server::bind(&config.addr)
|
axum::Server::bind(&config.addr)
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await
|
.await
|
||||||
.expect("Something went wrong :(");
|
.expect("Something went wrong :(");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
44
src/v1/error.rs
Normal file
44
src/v1/error.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::{Bytes, Full},
|
||||||
|
response::IntoResponse,
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
|
use super::models::ErrorResponse;
|
||||||
|
|
||||||
|
impl ErrorResponse {
|
||||||
|
fn new(status: StatusCode, message: Option<String>) -> Self {
|
||||||
|
ErrorResponse {
|
||||||
|
status,
|
||||||
|
error: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for ErrorResponse {
|
||||||
|
type Body = Full<Bytes>;
|
||||||
|
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> axum::http::Response<Self::Body> {
|
||||||
|
let status = self.status.clone();
|
||||||
|
(status, Json(self)).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sqlx::Error> for ErrorResponse {
|
||||||
|
fn from(err: sqlx::Error) -> Self {
|
||||||
|
match err {
|
||||||
|
sqlx::Error::RowNotFound => {
|
||||||
|
Self::new(StatusCode::NOT_FOUND, Some("Not found".to_string()))
|
||||||
|
}
|
||||||
|
_ => Self::new(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Some("Internal Server Error".to_string()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
mod routes;
|
mod error;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
mod routes;
|
||||||
mod sql;
|
mod sql;
|
||||||
|
|
||||||
pub use routes::routes;
|
pub use routes::routes;
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use reqwest::StatusCode;
|
||||||
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
|
fn serialize_status<S>(x: &StatusCode, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
s.serialize_u16(x.as_u16())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct Meme {
|
pub struct Meme {
|
||||||
|
@ -7,7 +15,7 @@ pub struct Meme {
|
||||||
pub category: String,
|
pub category: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub timestamp: String,
|
pub timestamp: String,
|
||||||
pub ipfs: Option<String>,
|
pub ipfs: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -73,7 +81,14 @@ pub struct UserResponse {
|
||||||
pub struct UploadResponse {
|
pub struct UploadResponse {
|
||||||
pub status: i32,
|
pub status: i32,
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
pub files: Option<Vec<String>>
|
pub files: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ErrorResponse {
|
||||||
|
#[serde(serialize_with = "serialize_status")]
|
||||||
|
pub status: StatusCode,
|
||||||
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Query
|
//Query
|
||||||
|
@ -101,5 +116,3 @@ pub struct MemeFilterQuery {
|
||||||
pub user: Option<String>,
|
pub user: Option<String>,
|
||||||
pub search: Option<String>,
|
pub search: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
188
src/v1/routes.rs
188
src/v1/routes.rs
|
@ -1,128 +1,103 @@
|
||||||
use crate::config::ConfVars;
|
use crate::config::ConfVars;
|
||||||
use crate::v1::models::*;
|
use crate::v1::models::*;
|
||||||
use sqlx::{MySqlPool, Error};
|
use axum::extract::{ContentLengthLimit, Extension, Multipart, Query};
|
||||||
use axum::{Router, Json};
|
use axum::handler::{get, post};
|
||||||
use axum::routing::BoxRoute;
|
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::handler::get;
|
use axum::routing::BoxRoute;
|
||||||
use axum::extract::{Query, Extension};
|
use axum::{Json, Router};
|
||||||
use axum::http::StatusCode;
|
use sqlx::MySqlPool;
|
||||||
|
|
||||||
async fn meme(params: Query<MemeIDQuery>, Extension(db_pool): Extension<MySqlPool>, Extension(vars): Extension<ConfVars>) -> impl IntoResponse {
|
async fn meme(
|
||||||
let q = Meme::get(params.id, &db_pool, vars.cdn).await;
|
params: Query<MemeIDQuery>,
|
||||||
match q {
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
Ok(meme) => (StatusCode::OK, Json(MemeResponse {
|
Extension(vars): Extension<ConfVars>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let meme = Meme::get(params.id, &db_pool, vars.cdn).await?;
|
||||||
|
Ok(Json(MemeResponse {
|
||||||
status: 200,
|
status: 200,
|
||||||
error: None,
|
error: None,
|
||||||
meme: Some(meme)
|
meme: Some(meme),
|
||||||
})),
|
|
||||||
Err(err) => match err {
|
|
||||||
Error::RowNotFound => (StatusCode::NOT_FOUND, Json(MemeResponse {
|
|
||||||
status: 404,
|
|
||||||
error: Some(String::from("Meme not found")),
|
|
||||||
meme: None
|
|
||||||
})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(MemeResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
meme: None
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn memes(params: Query<MemeFilterQuery>, Extension(db_pool): Extension<MySqlPool>, Extension(vars): Extension<ConfVars>) -> impl IntoResponse {
|
async fn memes(
|
||||||
let q = Meme::get_all(params.0, &db_pool, vars.cdn).await;
|
params: Query<MemeFilterQuery>,
|
||||||
match q {
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
Ok(memes) => (StatusCode::OK, Json(MemesResponse {
|
Extension(vars): Extension<ConfVars>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let memes = Meme::get_all(params.0, &db_pool, vars.cdn).await?;
|
||||||
|
Ok(Json(MemesResponse {
|
||||||
status: 200,
|
status: 200,
|
||||||
error: None,
|
error: None,
|
||||||
memes: Some(memes)
|
memes: Some(memes),
|
||||||
})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(MemesResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
memes: None
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn category(params: Query<IDQuery>, Extension(db_pool): Extension<MySqlPool>) -> impl IntoResponse {
|
async fn category(
|
||||||
let q = Category::get(¶ms.id, &db_pool).await;
|
params: Query<IDQuery>,
|
||||||
match q {
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
Ok(category) => (StatusCode::OK, Json(CategoryResponse { status: 200, error: None, category: Some(category)})),
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
Err(err) => match err {
|
let category = Category::get(¶ms.id, &db_pool).await?;
|
||||||
Error::RowNotFound => (StatusCode::NOT_FOUND, Json(CategoryResponse {
|
Ok(Json(CategoryResponse {
|
||||||
status: 404,
|
|
||||||
error: Some(String::from("Category not found")),
|
|
||||||
category: None
|
|
||||||
})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(CategoryResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
category: None
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn categories(Extension(db_pool): Extension<MySqlPool>) -> impl IntoResponse {
|
|
||||||
let q = Category::get_all(&db_pool).await;
|
|
||||||
match q {
|
|
||||||
Ok(categories) => (StatusCode::OK, Json(CategoriesResponse { status: 200, error: None, categories: Some(categories)})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(CategoriesResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
categories: None
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn user(params: Query<UserIDQuery>, Extension(db_pool): Extension<MySqlPool>) -> impl IntoResponse {
|
|
||||||
let q = User::get(params.0, &db_pool).await;
|
|
||||||
match q {
|
|
||||||
Ok(user) => (StatusCode::OK, Json(UserResponse { status: 200, error: None, user: Some(user)})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(UserResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
user: None
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn users(Extension(db_pool): Extension<MySqlPool>) -> impl IntoResponse {
|
|
||||||
let q = User::get_all(&db_pool).await;
|
|
||||||
match q {
|
|
||||||
Ok(users) => (StatusCode::OK, Json(UsersResponse { status: 200, error: None, users: Some(users)})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(UsersResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
users: None
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn random(params: Query<MemeFilterQuery>, Extension(db_pool): Extension<MySqlPool>, Extension(vars): Extension<ConfVars>) -> impl IntoResponse {
|
|
||||||
let q = Meme::get_random(params.0, &db_pool, vars.cdn).await;
|
|
||||||
match q {
|
|
||||||
Ok(random) => (StatusCode::OK, Json(MemeResponse {
|
|
||||||
status: 200,
|
status: 200,
|
||||||
error: None,
|
error: None,
|
||||||
meme: Some(random)
|
category: Some(category),
|
||||||
})),
|
|
||||||
Err(err) => match err {
|
|
||||||
Error::RowNotFound => (StatusCode::NOT_FOUND, Json(MemeResponse {
|
|
||||||
status: 404,
|
|
||||||
error: Some(String::from("Meme not found")),
|
|
||||||
meme: None
|
|
||||||
})),
|
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, Json(MemeResponse {
|
|
||||||
status: 500,
|
|
||||||
error: Some(String::from("Internal Server Error")),
|
|
||||||
meme: None
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn categories(
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let categories = Category::get_all(&db_pool).await?;
|
||||||
|
Ok(Json(CategoriesResponse {
|
||||||
|
status: 200,
|
||||||
|
error: None,
|
||||||
|
categories: Some(categories),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn user(
|
||||||
|
params: Query<UserIDQuery>,
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let user = User::get(params.0, &db_pool).await?;
|
||||||
|
Ok(Json(UserResponse {
|
||||||
|
status: 200,
|
||||||
|
error: None,
|
||||||
|
user: Some(user),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn users(
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let users = User::get_all(&db_pool).await?;
|
||||||
|
Ok(Json(UsersResponse {
|
||||||
|
status: 200,
|
||||||
|
error: None,
|
||||||
|
users: Some(users),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn random(
|
||||||
|
params: Query<MemeFilterQuery>,
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
Extension(vars): Extension<ConfVars>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let random = Meme::get_random(params.0, &db_pool, vars.cdn).await?;
|
||||||
|
Ok(Json(MemeResponse {
|
||||||
|
status: 200,
|
||||||
|
error: None,
|
||||||
|
meme: Some(random),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn upload(
|
||||||
|
ContentLengthLimit(mut form): ContentLengthLimit<Multipart, { 1024 * 1024 * 1024 }>,
|
||||||
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
|
Extension(vars): Extension<ConfVars>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Implement upload endpoint
|
//TODO: Implement upload endpoint
|
||||||
|
@ -136,5 +111,6 @@ pub fn routes() -> Router<BoxRoute> {
|
||||||
.route("/user", get(user))
|
.route("/user", get(user))
|
||||||
.route("/users", get(users))
|
.route("/users", get(users))
|
||||||
.route("/random", get(random))
|
.route("/random", get(random))
|
||||||
|
.route("/upload", post(upload))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::v1::models::{Meme, MemeFilterQuery, Category, User, UserIDQuery};
|
use crate::v1::models::{Category, Meme, MemeFilterQuery, User, UserIDQuery};
|
||||||
use sqlx::{MySqlPool, Result, Row};
|
|
||||||
use sqlx::mysql::MySqlRow;
|
use sqlx::mysql::MySqlRow;
|
||||||
|
use sqlx::{MySqlPool, Result, Row};
|
||||||
|
|
||||||
pub struct DBMeme {
|
pub struct DBMeme {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
@ -9,11 +9,10 @@ pub struct DBMeme {
|
||||||
pub userdir: String,
|
pub userdir: String,
|
||||||
pub category: String,
|
pub category: String,
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
pub ipfs: Option<String>,
|
pub ipfs: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Meme {
|
impl Meme {
|
||||||
|
|
||||||
pub fn new(meme: DBMeme, cdn: String) -> Self {
|
pub fn new(meme: DBMeme, cdn: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: meme.id.to_string(),
|
id: meme.id.to_string(),
|
||||||
|
@ -40,7 +39,11 @@ impl Meme {
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all(params: MemeFilterQuery, pool: &MySqlPool, cdn: String) -> Result<Vec<Meme>> {
|
pub async fn get_all(
|
||||||
|
params: MemeFilterQuery,
|
||||||
|
pool: &MySqlPool,
|
||||||
|
cdn: String,
|
||||||
|
) -> Result<Vec<Meme>> {
|
||||||
let q: Vec<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 memes.id")
|
let q: Vec<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 memes.id")
|
||||||
.bind(params.category.unwrap_or(String::from("%")))
|
.bind(params.category.unwrap_or(String::from("%")))
|
||||||
.bind(format!("%{}%", params.user.unwrap_or(String::from(""))))
|
.bind(format!("%{}%", params.user.unwrap_or(String::from(""))))
|
||||||
|
@ -58,7 +61,11 @@ impl Meme {
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_random(params: MemeFilterQuery, pool: &MySqlPool, cdn: String) -> Result<Meme> {
|
pub async fn get_random(
|
||||||
|
params: MemeFilterQuery,
|
||||||
|
pool: &MySqlPool,
|
||||||
|
cdn: String,
|
||||||
|
) -> Result<Meme> {
|
||||||
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")
|
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(params.category.unwrap_or(String::from("%")))
|
||||||
.bind(format!("%{}%", params.user.unwrap_or(String::from(""))))
|
.bind(format!("%{}%", params.user.unwrap_or(String::from(""))))
|
||||||
|
@ -75,17 +82,18 @@ impl Meme {
|
||||||
.fetch_one(pool).await?;
|
.fetch_one(pool).await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Category {
|
impl Category {
|
||||||
pub async fn get(id: &String, pool: &MySqlPool) -> Result<Category> {
|
pub async fn get(id: &String, pool: &MySqlPool) -> Result<Category> {
|
||||||
let q: Category = sqlx::query("SELECT * FROM categories WHERE id=?").bind(id)
|
let q: Category = sqlx::query("SELECT * FROM categories WHERE id=?")
|
||||||
|
.bind(id)
|
||||||
.map(|row: MySqlRow| Category {
|
.map(|row: MySqlRow| Category {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
name: row.get("name"),
|
name: row.get("name"),
|
||||||
})
|
})
|
||||||
.fetch_one(pool).await?;
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,13 +103,13 @@ impl Category {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
name: row.get("name"),
|
name: row.get("name"),
|
||||||
})
|
})
|
||||||
.fetch_all(pool).await?;
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
|
||||||
pub async fn get(params: UserIDQuery, pool: &MySqlPool) -> Result<User> {
|
pub async fn get(params: UserIDQuery, pool: &MySqlPool) -> Result<User> {
|
||||||
let q: User = sqlx::query("SELECT id, name, MD5(token) AS hash, uploads FROM (SELECT id, name, IFNULL(count.uploads, 0) AS uploads FROM users LEFT JOIN (SELECT user, COUNT(*) AS uploads FROM memes WHERE DATE(timestamp) = CURDATE() GROUP BY (user)) AS count ON users.id = count.user) AS users, token WHERE users.id = token.uid AND (users.id LIKE ? OR token LIKE ? OR name LIKE ?) UNION SELECT id, name, 0 AS hash, 0 AS uploads FROM users WHERE id = '000'")
|
let q: User = sqlx::query("SELECT id, name, MD5(token) AS hash, uploads FROM (SELECT id, name, IFNULL(count.uploads, 0) AS uploads FROM users LEFT JOIN (SELECT user, COUNT(*) AS uploads FROM memes WHERE DATE(timestamp) = CURDATE() GROUP BY (user)) AS count ON users.id = count.user) AS users, token WHERE users.id = token.uid AND (users.id LIKE ? OR token LIKE ? OR name LIKE ?) UNION SELECT id, name, 0 AS hash, 0 AS uploads FROM users WHERE id = '000'")
|
||||||
.bind(params.id.unwrap_or(String::from("")))
|
.bind(params.id.unwrap_or(String::from("")))
|
||||||
|
@ -130,5 +138,4 @@ impl User {
|
||||||
.fetch_all(pool).await?;
|
.fetch_all(pool).await?;
|
||||||
Ok(q)
|
Ok(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue