community icon/banner

This commit is contained in:
Felix Ableitner 2024-12-18 11:39:19 +01:00
parent 5f49b2aaec
commit a6f7e76bec
4 changed files with 80 additions and 22 deletions

View file

@ -1,3 +1,4 @@
use lemmy_db_schema::newtypes::CommunityId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
@ -44,3 +45,10 @@ pub struct UploadImageResponse {
pub filename: String, pub filename: String,
pub delete_token: String, pub delete_token: String,
} }
/// Parameter for setting community icon or banner. Can't use POST data here as it already contains
/// the image data.
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
pub struct CommunityIdQuery {
pub id: CommunityId,
}

View file

@ -13,7 +13,6 @@ use lemmy_api_common::{
is_admin, is_admin,
local_site_to_slur_regex, local_site_to_slur_regex,
process_markdown_opt, process_markdown_opt,
proxy_image_link_api,
EndpointType, EndpointType,
}, },
}; };
@ -31,7 +30,6 @@ use lemmy_db_schema::{
}, },
}, },
traits::{ApubActor, Crud, Followable, Joinable}, traits::{ApubActor, Crud, Followable, Joinable},
utils::diesel_url_create,
}; };
use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{ use lemmy_utils::{
@ -76,12 +74,6 @@ pub async fn create_community(
check_slurs(desc, &slur_regex)?; check_slurs(desc, &slur_regex)?;
} }
let icon = diesel_url_create(data.icon.as_deref())?;
let icon = proxy_image_link_api(icon, &context).await?;
let banner = diesel_url_create(data.banner.as_deref())?;
let banner = proxy_image_link_api(banner, &context).await?;
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?; is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
if let Some(desc) = &data.description { if let Some(desc) = &data.description {
@ -108,8 +100,6 @@ pub async fn create_community(
let community_form = CommunityInsertForm { let community_form = CommunityInsertForm {
sidebar, sidebar,
description, description,
icon,
banner,
nsfw: data.nsfw, nsfw: data.nsfw,
actor_id: Some(community_actor_id.clone()), actor_id: Some(community_actor_id.clone()),
private_key: Some(keypair.private_key), private_key: Some(keypair.private_key),

View file

@ -2,13 +2,15 @@ use super::utils::{adapt_request, delete_old_image, make_send};
use actix_web::{self, web::*, HttpRequest}; use actix_web::{self, web::*, HttpRequest};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
image::UploadImageResponse, image::{CommunityIdQuery, UploadImageResponse},
request::PictrsResponse, request::PictrsResponse,
utils::is_mod_or_admin,
LemmyErrorType, LemmyErrorType,
SuccessResponse, SuccessResponse,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::{Community, CommunityUpdateForm},
images::{LocalImage, LocalImageForm}, images::{LocalImage, LocalImageForm},
person::{Person, PersonUpdateForm}, person::{Person, PersonUpdateForm},
}, },
@ -50,11 +52,11 @@ pub async fn upload_user_avatar(
let image = do_upload_image(req, body, Avatar, &local_user_view, &context).await?; let image = do_upload_image(req, body, Avatar, &local_user_view, &context).await?;
delete_old_image(&local_user_view.person.avatar, &context).await?; delete_old_image(&local_user_view.person.avatar, &context).await?;
let person_form = PersonUpdateForm { let form = PersonUpdateForm {
avatar: Some(Some(image.image_url.into())), avatar: Some(Some(image.image_url.into())),
..Default::default() ..Default::default()
}; };
Person::update(&mut context.pool(), local_user_view.person.id, &person_form).await?; Person::update(&mut context.pool(), local_user_view.person.id, &form).await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }
@ -68,11 +70,55 @@ pub async fn upload_user_banner(
let image = do_upload_image(req, body, Banner, &local_user_view, &context).await?; let image = do_upload_image(req, body, Banner, &local_user_view, &context).await?;
delete_old_image(&local_user_view.person.banner, &context).await?; delete_old_image(&local_user_view.person.banner, &context).await?;
let person_form = PersonUpdateForm { let form = PersonUpdateForm {
banner: Some(Some(image.image_url.into())), banner: Some(Some(image.image_url.into())),
..Default::default() ..Default::default()
}; };
Person::update(&mut context.pool(), local_user_view.person.id, &person_form).await?; Person::update(&mut context.pool(), local_user_view.person.id, &form).await?;
Ok(Json(SuccessResponse::default()))
}
pub async fn upload_community_icon(
req: HttpRequest,
query: Query<CommunityIdQuery>,
body: Payload,
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> LemmyResult<Json<SuccessResponse>> {
let community: Community = Community::read(&mut context.pool(), query.id).await?;
is_mod_or_admin(&mut context.pool(), &local_user_view.person, community.id).await?;
let image = do_upload_image(req, body, Avatar, &local_user_view, &context).await?;
delete_old_image(&community.icon, &context).await?;
let form = CommunityUpdateForm {
icon: Some(Some(image.image_url.into())),
..Default::default()
};
Community::update(&mut context.pool(), community.id, &form).await?;
Ok(Json(SuccessResponse::default()))
}
pub async fn upload_community_banner(
req: HttpRequest,
query: Query<CommunityIdQuery>,
body: Payload,
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> LemmyResult<Json<SuccessResponse>> {
let community: Community = Community::read(&mut context.pool(), query.id).await?;
is_mod_or_admin(&mut context.pool(), &local_user_view.person, community.id).await?;
let image = do_upload_image(req, body, Banner, &local_user_view, &context).await?;
delete_old_image(&community.icon, &context).await?;
let form = CommunityUpdateForm {
banner: Some(Some(image.image_url.into())),
..Default::default()
};
Community::update(&mut context.pool(), community.id, &form).await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }
@ -84,29 +130,35 @@ pub async fn do_upload_image(
local_user_view: &LocalUserView, local_user_view: &LocalUserView,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<UploadImageResponse> { ) -> LemmyResult<UploadImageResponse> {
let pictrs_config = context.settings().pictrs()?; let pictrs = context.settings().pictrs()?;
let image_url = format!("{}image", pictrs_config.url); let image_url = format!("{}image", pictrs.url);
let mut client_req = adapt_request(&req, image_url); let mut client_req = adapt_request(&req, image_url);
client_req = match upload_type { client_req = match upload_type {
Avatar => { Avatar => {
let max_size = context.settings().pictrs()?.max_avatar_size.to_string(); let max_size = pictrs.max_avatar_size.to_string();
client_req.query(&[
("resize", max_size.as_ref()),
("allow_animation", "false"),
("allow_video", "false"),
])
}
Banner => {
let max_size = pictrs.max_banner_size.to_string();
client_req.query(&[ client_req.query(&[
("resize", max_size.as_ref()), ("resize", max_size.as_ref()),
("allow_animation", "false"), ("allow_animation", "false"),
("allow_video", "false"), ("allow_video", "false"),
]) ])
} }
// TODO: same as above but using `max_banner_size`
// Banner => {}
_ => client_req, _ => client_req,
}; };
if let Some(addr) = req.head().peer_addr { if let Some(addr) = req.head().peer_addr {
client_req = client_req.header("X-Forwarded-For", addr.to_string()) client_req = client_req.header("X-Forwarded-For", addr.to_string())
}; };
let res = client_req let res = client_req
.timeout(Duration::from_secs(pictrs_config.upload_timeout)) .timeout(Duration::from_secs(pictrs.upload_timeout))
.body(Body::wrap_stream(make_send(body))) .body(Body::wrap_stream(make_send(body)))
.send() .send()
.await? .await?

View file

@ -162,7 +162,13 @@ use lemmy_routes::images::{
delete_image, delete_image,
download::{get_image, image_proxy}, download::{get_image, image_proxy},
pictrs_health, pictrs_health,
upload::{upload_image, upload_user_avatar, upload_user_banner}, upload::{
upload_community_banner,
upload_community_icon,
upload_image,
upload_user_avatar,
upload_user_banner,
},
}; };
use lemmy_utils::rate_limit::RateLimitCell; use lemmy_utils::rate_limit::RateLimitCell;
@ -205,6 +211,8 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
.route("/transfer", post().to(transfer_community)) .route("/transfer", post().to(transfer_community))
.route("/ban_user", post().to(ban_from_community)) .route("/ban_user", post().to(ban_from_community))
.route("/mod", post().to(add_mod_to_community)) .route("/mod", post().to(add_mod_to_community))
.route("/icon", post().to(upload_community_icon))
.route("/banner", post().to(upload_community_banner))
.service( .service(
scope("/pending_follows") scope("/pending_follows")
.route("/count", get().to(get_pending_follows_count)) .route("/count", get().to(get_pending_follows_count))