diff --git a/src/cdn/error.rs b/src/cdn/error.rs new file mode 100644 index 0000000..8b4f078 --- /dev/null +++ b/src/cdn/error.rs @@ -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; + + type BodyError = Infallible; + + fn into_response(self) -> axum::http::Response { + self.0.into_response() + } +} + +impl From for Error { + fn from(err: sqlx::Error) -> Self { + match err { + sqlx::Error::RowNotFound => Error(StatusCode::NOT_FOUND), + _ => Error(StatusCode::INTERNAL_SERVER_ERROR), + } + } +} + +impl From for Error { + fn from(err: reqwest::Error) -> Self { + Error(err.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)) + } +} + +impl From for Error { + fn from(_: FromUtf8Error) -> Self { + Error(StatusCode::INTERNAL_SERVER_ERROR) + } +} diff --git a/src/cdn/mod.rs b/src/cdn/mod.rs index ade1a2f..f9a3178 100644 --- a/src/cdn/mod.rs +++ b/src/cdn/mod.rs @@ -1,65 +1,75 @@ - -use axum::{Router, body::Body, extract::{Extension, Path}, handler::get, http::HeaderMap, response::IntoResponse, routing::BoxRoute}; +use axum::{ + body::Body, + extract::{Extension, Path}, + handler::get, + http::HeaderMap, + response::IntoResponse, + routing::BoxRoute, + Router, +}; use headers::{ContentType, HeaderMapExt}; -use reqwest::{StatusCode, header::{CONTENT_LENGTH, HeaderName}}; -use sqlx::{Error, MySqlPool}; +use reqwest::{ + header::{HeaderName, CONTENT_LENGTH}, + StatusCode, +}; +use sqlx::MySqlPool; use crate::config::ConfVars; -use self::templates::{DirTemplate, HtmlTemplate}; +use self::{ + error::Error, + templates::{DirTemplate, HtmlTemplate}, +}; +mod error; mod sql; mod templates; pub fn routes() -> Router { Router::new() - .route("/", get(users)) - .route("/:user/", get(memes)) - .route("/:user/:filename", get(image)) - .boxed() + .route("/", get(users)) + .route("/:user/", get(memes)) + .route("/:user/:filename", get(image)) + .boxed() } -async fn image(Path((user, filename)): Path<(String, String)>, Extension(db_pool): Extension, Extension(vars): Extension) -> Result { - let filename = urlencoding::decode(&filename).map_err(|err| StatusCode::INTERNAL_SERVER_ERROR)?.into_owned(); - let q = sql::get_cid(user, filename.clone(), &db_pool).await; - match q { - Ok(cid) => { - let ipfsapi = vars.ipfs_client(); - match ipfsapi { - Ok(ipfs) => { - let res = ipfs.cat(cid).await; - match res { - Ok(r) => { - let clength = r.headers().get(HeaderName::from_static("x-content-length")); - match clength { - Some(h) => { - let mut headers = HeaderMap::new(); - let ctype = ContentType::from(new_mime_guess::from_path(filename).first_or_octet_stream()); - headers.typed_insert(ctype); - headers.insert(CONTENT_LENGTH, h.clone()); - - Ok((StatusCode::OK, headers, Body::wrap_stream(r.bytes_stream()))) - }, - None => Err(StatusCode::INTERNAL_SERVER_ERROR), - } - }, - 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 image( + Path((user, filename)): Path<(String, String)>, + Extension(db_pool): Extension, + Extension(vars): Extension, +) -> Result { + let filename = urlencoding::decode(&filename)?.into_owned(); + let cid = sql::get_cid(user, filename.clone(), &db_pool).await?; + let ipfs = vars.ipfs_client()?; + let res = ipfs.cat(cid).await?; + let clength = res + .headers() + .get(HeaderName::from_static("x-content-length")); + match clength { + Some(h) => { + let mut headers = HeaderMap::new(); + let ctype = + ContentType::from(new_mime_guess::from_path(filename).first_or_octet_stream()); + headers.typed_insert(ctype); + headers.insert(CONTENT_LENGTH, h.clone()); + + Ok(( + StatusCode::OK, + headers, + Body::wrap_stream(res.bytes_stream()), + )) + } + None => Err(Error::new()), } } -async fn users(Extension(db_pool): Extension, Extension(vars): Extension) -> Result { +async fn users( + Extension(db_pool): Extension, + Extension(vars): Extension, +) -> Result { let q = sql::get_users(&db_pool).await; match q { - Ok(users) => Ok(HtmlTemplate(DirTemplate { + Ok(users) => Ok(HtmlTemplate(DirTemplate { entries: users, prefix: vars.cdn, suffix: "/".to_string(), @@ -68,14 +78,17 @@ async fn users(Extension(db_pool): Extension, Extension(vars): Extens } } -async fn memes(Path(user): Path, Extension(db_pool): Extension) -> Result { +async fn memes( + Path(user): Path, + Extension(db_pool): Extension, +) -> Result { let q = sql::get_memes(user, &db_pool).await; match q { - Ok(memes) => Ok(HtmlTemplate(DirTemplate { + Ok(memes) => Ok(HtmlTemplate(DirTemplate { entries: memes, prefix: ".".to_string(), suffix: "".to_string(), })), Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), } -} \ No newline at end of file +} diff --git a/src/cdn/sql.rs b/src/cdn/sql.rs index ab8e7f4..eaf0713 100644 --- a/src/cdn/sql.rs +++ b/src/cdn/sql.rs @@ -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 { - 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")) - .fetch_one(pool).await?; + 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")) + .fetch_one(pool) + .await?; Ok(q) } pub async fn get_memes(user: String, pool: &MySqlPool) -> Result> { - let q: Vec = sqlx::query("SELECT filename FROM memes WHERE user = ? ORDER BY filename").bind(user) + let q: Vec = sqlx::query("SELECT filename FROM memes WHERE user = ? ORDER BY filename") + .bind(user) .map(|row: MySqlRow| row.get("filename")) - .fetch_all(pool).await?; + .fetch_all(pool) + .await?; Ok(q) } pub async fn get_users(pool: &MySqlPool) -> Result> { let q: Vec = sqlx::query("SELECT id FROM users ORDER BY id") .map(|row: MySqlRow| row.get("id")) - .fetch_all(pool).await?; + .fetch_all(pool) + .await?; Ok(q) -} \ No newline at end of file +} diff --git a/src/cdn/templates.rs b/src/cdn/templates.rs index ad41fd7..f6c87d9 100644 --- a/src/cdn/templates.rs +++ b/src/cdn/templates.rs @@ -1,14 +1,14 @@ use askama::Template; -use axum::response::{IntoResponse, Html}; -use axum::body::{Full, Bytes}; +use axum::body::{Bytes, Full}; use axum::http::{Response, StatusCode}; +use axum::response::{Html, IntoResponse}; use std::convert::Infallible; pub struct HtmlTemplate(pub T); impl IntoResponse for HtmlTemplate - where - T: Template, +where + T: Template, { type Body = Full; type BodyError = Infallible; @@ -16,7 +16,7 @@ impl IntoResponse for HtmlTemplate fn into_response(self) -> Response { match self.0.render() { Ok(html) => Html(html).into_response(), - Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "").into_response() + Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "").into_response(), } } } @@ -27,4 +27,4 @@ pub struct DirTemplate { pub entries: Vec, pub prefix: String, pub suffix: String, -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index ff75e9f..877a2f1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ -use std::net::SocketAddr; use reqwest::Url; use serde::Deserialize; +use std::net::SocketAddr; #[derive(Deserialize)] pub struct Config { @@ -16,18 +16,19 @@ pub struct ConfVars { } impl Config { - pub fn vars(&self) -> ConfVars { ConfVars { cdn: self.cdn.clone(), ipfs_api: self.ipfs_api.clone(), } } - } impl Clone for ConfVars { 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(), + } } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index ab6b6a6..b410d08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,45 +1,52 @@ -use std::path::PathBuf; +use axum::{ + body::Body, + http::{header, HeaderValue, Request}, + Router, +}; use config::Config; use sqlx::MySqlPool; +use std::path::PathBuf; use structopt::StructOpt; -use axum::{Router, body::Body, http::{HeaderValue, Request, header}}; use tower_http::{add_extension::AddExtensionLayer, set_header::SetResponseHeaderLayer}; -mod v1; mod cdn; mod config; mod ipfs; +mod v1; #[derive(StructOpt)] struct Opt { #[structopt( - short, - long, - help = "config file to use", - default_value = "./config.toml" + short, + long, + help = "config file to use", + default_value = "./config.toml" )] config: PathBuf, } #[tokio::main] async fn main() { - 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 db_pool = MySqlPool::new(&config.database) + .await + .expect("Database connection error"); let app = Router::new() .nest("/api/v1", v1::routes()) .nest("/cdn", cdn::routes()) .layer(AddExtensionLayer::new(db_pool)) .layer(AddExtensionLayer::new(config.vars())) - .layer(SetResponseHeaderLayer::<_, Request>::if_not_present(header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"))); + .layer(SetResponseHeaderLayer::<_, Request>::if_not_present( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("*"), + )); axum::Server::bind(&config.addr) .serve(app.into_make_service()) .await .expect("Something went wrong :("); } - diff --git a/src/v1/error.rs b/src/v1/error.rs new file mode 100644 index 0000000..94daaab --- /dev/null +++ b/src/v1/error.rs @@ -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) -> Self { + ErrorResponse { + status, + error: message, + } + } +} + +impl IntoResponse for ErrorResponse { + type Body = Full; + + type BodyError = Infallible; + + fn into_response(self) -> axum::http::Response { + let status = self.status.clone(); + (status, Json(self)).into_response() + } +} + +impl From 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()), + ), + } + } +} diff --git a/src/v1/mod.rs b/src/v1/mod.rs index 3564051..db74d77 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -1,5 +1,6 @@ -mod routes; +mod error; pub mod models; +mod routes; mod sql; pub use routes::routes; diff --git a/src/v1/models.rs b/src/v1/models.rs index 2647451..7bfa3a3 100644 --- a/src/v1/models.rs +++ b/src/v1/models.rs @@ -1,4 +1,12 @@ -use serde::{Deserialize, Serialize}; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize, Serializer}; + +fn serialize_status(x: &StatusCode, s: S) -> Result +where + S: Serializer, +{ + s.serialize_u16(x.as_u16()) +} #[derive(Serialize)] pub struct Meme { @@ -7,7 +15,7 @@ pub struct Meme { pub category: String, pub user: String, pub timestamp: String, - pub ipfs: Option, + pub ipfs: String, } #[derive(Serialize)] @@ -73,7 +81,14 @@ pub struct UserResponse { pub struct UploadResponse { pub status: i32, pub error: Option, - pub files: Option> + pub files: Option>, +} + +#[derive(Serialize)] +pub struct ErrorResponse { + #[serde(serialize_with = "serialize_status")] + pub status: StatusCode, + pub error: Option, } //Query @@ -101,5 +116,3 @@ pub struct MemeFilterQuery { pub user: Option, pub search: Option, } - - diff --git a/src/v1/routes.rs b/src/v1/routes.rs index 9c3e43d..e13c891 100644 --- a/src/v1/routes.rs +++ b/src/v1/routes.rs @@ -1,128 +1,103 @@ use crate::config::ConfVars; use crate::v1::models::*; -use sqlx::{MySqlPool, Error}; -use axum::{Router, Json}; -use axum::routing::BoxRoute; +use axum::extract::{ContentLengthLimit, Extension, Multipart, Query}; +use axum::handler::{get, post}; use axum::response::IntoResponse; -use axum::handler::get; -use axum::extract::{Query, Extension}; -use axum::http::StatusCode; +use axum::routing::BoxRoute; +use axum::{Json, Router}; +use sqlx::MySqlPool; -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, - error: None, - 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 meme( + params: Query, + Extension(db_pool): Extension, + Extension(vars): Extension, +) -> Result { + let meme = Meme::get(params.id, &db_pool, vars.cdn).await?; + Ok(Json(MemeResponse { + status: 200, + error: None, + meme: Some(meme), + })) } -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, - error: None, - memes: Some(memes) - })), - _ => (StatusCode::INTERNAL_SERVER_ERROR, Json(MemesResponse { - status: 500, - error: Some(String::from("Internal Server Error")), - memes: None - })) - } +async fn memes( + params: Query, + Extension(db_pool): Extension, + Extension(vars): Extension, +) -> Result { + let memes = Meme::get_all(params.0, &db_pool, vars.cdn).await?; + Ok(Json(MemesResponse { + status: 200, + error: None, + memes: Some(memes), + })) } -async fn category(params: Query, Extension(db_pool): Extension) -> impl IntoResponse { - let q = Category::get(¶ms.id, &db_pool).await; - match q { - Ok(category) => (StatusCode::OK, Json(CategoryResponse { status: 200, error: None, category: Some(category)})), - Err(err) => match err { - Error::RowNotFound => (StatusCode::NOT_FOUND, 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 category( + params: Query, + Extension(db_pool): Extension, +) -> Result { + let category = Category::get(¶ms.id, &db_pool).await?; + Ok(Json(CategoryResponse { + status: 200, + error: None, + category: Some(category), + })) } -async fn categories(Extension(db_pool): Extension) -> 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 categories( + Extension(db_pool): Extension, +) -> Result { + let categories = Category::get_all(&db_pool).await?; + Ok(Json(CategoriesResponse { + status: 200, + error: None, + categories: Some(categories), + })) } -async fn user(params: Query, Extension(db_pool): Extension) -> 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 user( + params: Query, + Extension(db_pool): Extension, +) -> Result { + 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) -> 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 users( + Extension(db_pool): Extension, +) -> Result { + let users = User::get_all(&db_pool).await?; + Ok(Json(UsersResponse { + status: 200, + error: None, + users: Some(users), + })) } -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, - error: None, - meme: Some(random) - })), - 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 random( + params: Query, + Extension(db_pool): Extension, + Extension(vars): Extension, +) -> Result { + 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, + Extension(db_pool): Extension, + Extension(vars): Extension, +) -> impl IntoResponse { + todo!(); } //TODO: Implement upload endpoint @@ -136,5 +111,6 @@ pub fn routes() -> Router { .route("/user", get(user)) .route("/users", get(users)) .route("/random", get(random)) + .route("/upload", post(upload)) .boxed() } diff --git a/src/v1/sql.rs b/src/v1/sql.rs index 5468be1..f603b6b 100644 --- a/src/v1/sql.rs +++ b/src/v1/sql.rs @@ -1,6 +1,6 @@ -use crate::v1::models::{Meme, MemeFilterQuery, Category, User, UserIDQuery}; -use sqlx::{MySqlPool, Result, Row}; +use crate::v1::models::{Category, Meme, MemeFilterQuery, User, UserIDQuery}; use sqlx::mysql::MySqlRow; +use sqlx::{MySqlPool, Result, Row}; pub struct DBMeme { pub id: i32, @@ -9,11 +9,10 @@ pub struct DBMeme { pub userdir: String, pub category: String, pub timestamp: i64, - pub ipfs: Option, + pub ipfs: String, } impl Meme { - pub fn new(meme: DBMeme, cdn: String) -> Self { Self { id: meme.id.to_string(), @@ -40,7 +39,11 @@ impl Meme { Ok(q) } - pub async fn get_all(params: MemeFilterQuery, pool: &MySqlPool, cdn: String) -> Result> { + 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("")))) @@ -58,7 +61,11 @@ impl Meme { Ok(q) } - pub async fn get_random(params: MemeFilterQuery, pool: &MySqlPool, cdn: String) -> Result { + 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("")))) @@ -75,17 +82,18 @@ impl Meme { .fetch_one(pool).await?; Ok(q) } - } impl Category { pub async fn get(id: &String, pool: &MySqlPool) -> Result { - 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 { id: row.get("id"), name: row.get("name"), }) - .fetch_one(pool).await?; + .fetch_one(pool) + .await?; Ok(q) } @@ -95,13 +103,13 @@ impl Category { id: row.get("id"), name: row.get("name"), }) - .fetch_all(pool).await?; + .fetch_all(pool) + .await?; Ok(q) } } impl User { - pub async fn get(params: UserIDQuery, pool: &MySqlPool) -> Result { 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(""))) @@ -117,7 +125,7 @@ impl User { .fetch_one(pool).await?; Ok(q) } - + pub async fn get_all(pool: &MySqlPool) -> Result> { let q: Vec = 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 UNION SELECT id, name, 0 AS hash, 0 AS uploads FROM users WHERE id = '000'") .map(|row: MySqlRow| User { @@ -130,5 +138,4 @@ impl User { .fetch_all(pool).await?; Ok(q) } - }