add: custom emojis

fix: don't show reposts of posts from blocked users
fix: don't show questions when they're from users you've blocked
This commit is contained in:
trisua 2025-05-10 21:58:02 -04:00
parent 9f187039e6
commit 275dd0a1eb
25 changed files with 697 additions and 61 deletions

View file

@ -1,4 +1,19 @@
use axum::response::IntoResponse;
use std::fs::exists;
use image::ImageFormat;
use pathbufd::PathBufD;
use crate::{
get_user_from_token,
image::{save_buffer, Image},
routes::api::v1::{auth::images::read_image, UpdateEmojiName},
State,
};
use axum::{body::Body, extract::Path, response::IntoResponse, Extension, Json};
use axum_extra::extract::CookieJar;
use tetratto_core::model::{
uploads::{CustomEmoji, MediaType, MediaUpload},
ApiReturn, Error,
};
/// Expand a unicode emoji into its Gemoji shortcode.
pub async fn get_emoji_shortcode(emoji: String) -> impl IntoResponse {
@ -10,3 +25,209 @@ pub async fn get_emoji_shortcode(emoji: String) -> impl IntoResponse {
None => String::new(),
}
}
pub async fn get_request(
Path((community, name)): Path<(usize, String)>,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
if community == 0 {
return Err((
[("Content-Type", "image/svg+xml")],
Body::from(read_image(PathBufD::current().extend(&[
data.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
]))),
));
}
let emoji = match data.get_emoji_by_community_name(community, &name).await {
Ok(ua) => ua,
Err(_) => {
return Err((
[("Content-Type", "image/svg+xml")],
Body::from(read_image(PathBufD::current().extend(&[
data.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
]))),
));
}
};
let upload = data
.get_upload_by_id(emoji.0.unwrap().upload_id)
.await
.unwrap();
let path = upload.path(&data.0);
if !exists(&path).unwrap() {
return Err((
[("Content-Type", "image/svg+xml")],
Body::from(read_image(PathBufD::current().extend(&[
data.0.dirs.media.as_str(),
"images",
"default-avatar.svg",
]))),
));
}
Ok((
[("Content-Type", upload.what.mime())],
Body::from(read_image(path)),
))
}
// maximum file dimensions: 512x512px (256KiB)
pub const MAXIUMUM_FILE_SIZE: usize = 262144;
pub async fn create_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path((community, name)): Path<(usize, String)>,
img: Image,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
// check file size
if img.0.len() > MAXIUMUM_FILE_SIZE {
return Json(Error::DataTooLong("image".to_string()).into());
}
// make sure emoji doesn't already exist in community
if data
.get_emoji_by_community_name(community, &name)
.await
.is_ok()
{
return Json(
Error::MiscError("This emoji name is already in use in this community".to_string())
.into(),
);
}
// create upload
let upload = match data
.create_upload(MediaUpload::new(
if img.1 == "image/gif" {
MediaType::Gif
} else {
MediaType::Webp
},
user.id,
))
.await
{
Ok(u) => u,
Err(e) => return Json(e.into()),
};
// create emoji
let is_animated = img.1 == "image/gif";
match data
.create_emoji(CustomEmoji::new(
user.id,
community,
upload.id,
name,
is_animated,
))
.await
{
Ok(_) => {
if let Err(e) = save_buffer(
&upload.path(&data.0).to_string(),
img.0.to_vec(),
if is_animated {
ImageFormat::Gif
} else {
ImageFormat::WebP
},
) {
return Json(Error::MiscError(e.to_string()).into());
}
Json(ApiReturn {
ok: true,
message: "Emoji created".to_string(),
payload: (),
})
}
Err(e) => {
if let Err(e) = upload.remove(&data.0) {
return Json(e.into());
}
Json(e.into())
}
}
}
pub async fn update_name_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Json(req): Json<UpdateEmojiName>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.update_emoji_name(id, user, &req.name).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "Emoji updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn delete_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.delete_emoji(id, &user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "Emoji deleted".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn get_my_request(
jar: CookieJar,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.get_user_emojis(user.id).await {
Ok(d) => Json(ApiReturn {
ok: true,
message: d.len().to_string(),
payload: Some(d),
}),
Err(e) => Json(e.into()),
}
}