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:
parent
9f187039e6
commit
275dd0a1eb
25 changed files with 697 additions and 61 deletions
|
@ -1,7 +1,12 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use axum::{http::HeaderMap, response::IntoResponse, Extension, Json};
|
||||
use tetratto_core::model::{auth::Notification, permissions::FinePermission, ApiReturn, Error};
|
||||
use tetratto_core::model::{
|
||||
auth::{User, Notification},
|
||||
moderation::AuditLogEntry,
|
||||
permissions::FinePermission,
|
||||
ApiReturn, Error,
|
||||
};
|
||||
use stripe::{EventObject, EventType};
|
||||
use crate::State;
|
||||
|
||||
|
@ -70,14 +75,50 @@ pub async fn stripe_webhook(
|
|||
|
||||
let customer_id = invoice.customer.unwrap().id();
|
||||
|
||||
// allow 30s for everything to finalize
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
|
||||
// pull user and update role
|
||||
let user = match data.get_user_by_stripe_id(customer_id.as_str()).await {
|
||||
Ok(ua) => ua,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
let mut retries: usize = 0;
|
||||
let mut user: Option<User> = None;
|
||||
|
||||
loop {
|
||||
if retries >= 5 {
|
||||
// we've already tried 5 times (10 seconds of waiting)... it's not
|
||||
// going to happen
|
||||
//
|
||||
// we're going to report this error to the audit log so someone can
|
||||
// check manually later
|
||||
if let Err(e) = data
|
||||
.create_audit_log_entry(AuditLogEntry::new(
|
||||
0,
|
||||
format!("invoice tier update failed: stripe {customer_id}"),
|
||||
))
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
|
||||
return Json(Error::GeneralNotFound("user".to_string()).into());
|
||||
}
|
||||
|
||||
match data.get_user_by_stripe_id(customer_id.as_str()).await {
|
||||
Ok(ua) => {
|
||||
if !user.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
user = Some(ua);
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::info!("checkout session not stored in db yet");
|
||||
retries += 1;
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let user = user.unwrap();
|
||||
tracing::info!("found subscription user in {retries} tries");
|
||||
|
||||
if user.permissions.check(FinePermission::SUPPORTER) {
|
||||
return Json(ApiReturn {
|
||||
|
|
|
@ -216,7 +216,7 @@ pub async fn logout_request(
|
|||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Goodbye!".to_string(),
|
||||
payload: (),
|
||||
payload: Some(user.username.clone()),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -322,6 +322,23 @@ pub fn routes() -> Router {
|
|||
"/lookup_emoji",
|
||||
post(communities::emojis::get_emoji_shortcode),
|
||||
)
|
||||
.route("/my_emojis", get(communities::emojis::get_my_request))
|
||||
.route(
|
||||
"/communities/{id}/emojis/{name}",
|
||||
get(communities::emojis::get_request),
|
||||
)
|
||||
.route(
|
||||
"/communities/{id}/emojis/{name}",
|
||||
post(communities::emojis::create_request),
|
||||
)
|
||||
.route(
|
||||
"/emojis_id/{id}/name",
|
||||
post(communities::emojis::update_name_request),
|
||||
)
|
||||
.route(
|
||||
"/emojis_id/{id}",
|
||||
delete(communities::emojis::delete_request),
|
||||
)
|
||||
// stacks
|
||||
.route("/stacks", post(stacks::create_request))
|
||||
.route("/stacks/{id}/name", post(stacks::update_name_request))
|
||||
|
@ -547,3 +564,8 @@ pub struct UpdateStackSort {
|
|||
pub struct AddOrRemoveStackUser {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateEmojiName {
|
||||
pub name: String,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue