use axum::{Extension, Json, body::Body, extract::Path, response::IntoResponse}; use axum_extra::extract::CookieJar; use pathbufd::{PathBufD, pathd}; use std::{ fs::{File, exists}, io::Read, }; use tetratto_core::model::{ApiReturn, Error}; use crate::{ State, avif::{Image, save_avif_buffer}, get_user_from_token, }; pub fn read_image(path: PathBufD) -> Vec { let mut bytes = Vec::new(); for byte in File::open(path).unwrap().bytes() { bytes.push(byte.unwrap()) } bytes } /// Get a profile's avatar image /// `/api/v1/auth/profile/{id}/avatar` pub async fn avatar_request( Path(username): Path, Extension(data): Extension, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match data.get_user_by_username(&username).await { Ok(ua) => ua, Err(_) => { return ( [("Content-Type", "image/svg+xml")], Body::from(read_image(PathBufD::current().extend(&[ data.0.dirs.media.as_str(), "images", "default-avatar.svg", ]))), ); } }; let path = PathBufD::current().extend(&["avatars", &data.0.dirs.media, &format!("{}.avif", &user.id)]); if !exists(&path).unwrap() { return ( [("Content-Type", "image/svg+xml")], Body::from(read_image(PathBufD::current().extend(&[ data.0.dirs.media.as_str(), "images", "default-avatar.svg", ]))), ); } ( [("Content-Type", "image/avif")], Body::from(read_image(path)), ) } /// Get a profile's banner image /// `/api/v1/auth/profile/{id}/banner` pub async fn banner_request( Path(username): Path, Extension(data): Extension, ) -> impl IntoResponse { let data = &(data.read().await).0; let user = match data.get_user_by_username(&username).await { Ok(ua) => ua, Err(_) => { return ( [("Content-Type", "image/svg+xml")], Body::from(read_image(PathBufD::current().extend(&[ data.0.dirs.media.as_str(), "images", "default-banner.svg", ]))), ); } }; let path = PathBufD::current().extend(&["avatars", &data.0.dirs.media, &format!("{}.avif", &user.id)]); if !exists(&path).unwrap() { return ( [("Content-Type", "image/svg+xml")], Body::from(read_image(PathBufD::current().extend(&[ data.0.dirs.media.as_str(), "images", "default-banner.svg", ]))), ); } ( [("Content-Type", "image/avif")], Body::from(read_image(path)), ) } static MAXIUMUM_FILE_SIZE: usize = 8388608; /// Upload avatar pub async fn upload_avatar_request( jar: CookieJar, Extension(data): Extension, img: Image, ) -> impl IntoResponse { // get user from token let data = &(data.read().await).0; let auth_user = match get_user_from_token!(jar, data) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let path = pathd!("{}/avatars/{}.avif", data.0.dirs.media, &auth_user.id); // check file size if img.0.len() > MAXIUMUM_FILE_SIZE { return Json(Error::DataTooLong("image".to_string()).into()); } // upload image let mut bytes = Vec::new(); for byte in img.0 { bytes.push(byte); } match save_avif_buffer(&path, bytes) { Ok(_) => Json(ApiReturn { ok: true, message: "Avatar uploaded. It might take a bit to update".to_string(), payload: (), }), Err(e) => Json(Error::MiscError(e.to_string()).into()), } } /// Upload banner pub async fn upload_banner_request( jar: CookieJar, Extension(data): Extension, img: Image, ) -> impl IntoResponse { // get user from token let data = &(data.read().await).0; let auth_user = match get_user_from_token!(jar, data) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; let path = pathd!("{}/banners/{}.avif", data.0.dirs.media, &auth_user.id); // check file size if img.0.len() > MAXIUMUM_FILE_SIZE { return Json(Error::DataTooLong("image".to_string()).into()); } // upload image let mut bytes = Vec::new(); for byte in img.0 { bytes.push(byte); } match save_avif_buffer(&path, bytes) { Ok(_) => Json(ApiReturn { ok: true, message: "Banner uploaded. It might take a bit to update".to_string(), payload: (), }), Err(e) => Json(Error::MiscError(e.to_string()).into()), } }