Get rid of PerformCrud trait

This commit is contained in:
Felix Ableitner 2023-08-02 14:46:39 +02:00
parent e9b82dfe3c
commit 9ae3b2d56e
7 changed files with 233 additions and 302 deletions

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
@ -13,41 +13,38 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for CreateCustomEmoji {
type Response = CustomEmojiResponse;
#[tracing::instrument(skip(context))]
pub async fn create_custom_emoji(
data: Json<CreateCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<CustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> {
let data: &CreateCustomEmoji = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let shortcode = sanitize_html(data.shortcode.to_lowercase().trim());
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let shortcode = sanitize_html(data.shortcode.to_lowercase().trim());
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let emoji_form = CustomEmojiInsertForm::builder()
.local_site_id(local_site.id)
.shortcode(shortcode)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
let emoji_form = CustomEmojiInsertForm::builder()
.local_site_id(local_site.id)
.shortcode(shortcode)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
.build();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(CustomEmojiResponse { custom_emoji: view })
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(Json(CustomEmojiResponse { custom_emoji: view }))
}

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse},
@ -8,24 +8,18 @@ use lemmy_api_common::{
use lemmy_db_schema::source::custom_emoji::CustomEmoji;
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for DeleteCustomEmoji {
type Response = DeleteCustomEmojiResponse;
#[tracing::instrument(skip(context))]
pub async fn delete_custom_emoji(
data: Json<DeleteCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<DeleteCustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<DeleteCustomEmojiResponse, LemmyError> {
let data: &DeleteCustomEmoji = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
CustomEmoji::delete(&mut context.pool(), data.id).await?;
Ok(DeleteCustomEmojiResponse {
id: data.id,
success: true,
})
}
// Make sure user is an admin
is_admin(&local_user_view)?;
CustomEmoji::delete(&mut context.pool(), data.id).await?;
Ok(Json(DeleteCustomEmojiResponse {
id: data.id,
success: true,
}))
}

View file

@ -1,3 +1,3 @@
mod create;
mod delete;
mod update;
pub mod create;
pub mod delete;
pub mod update;

View file

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
@ -13,40 +13,37 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditCustomEmoji {
type Response = CustomEmojiResponse;
#[tracing::instrument(skip(context))]
pub async fn update_custom_emoji(
data: Json<EditCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<CustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> {
let data: &EditCustomEmoji = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let emoji_form = CustomEmojiUpdateForm::builder()
.local_site_id(local_site.id)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
let emoji_form = CustomEmojiUpdateForm::builder()
.local_site_id(local_site.id)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
.build();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(CustomEmojiResponse { custom_emoji: view })
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(Json(CustomEmojiResponse { custom_emoji: view }))
}

View file

@ -1,7 +1,3 @@
use actix_web::web::Data;
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::LemmyError;
pub mod comment;
pub mod community;
pub mod custom_emoji;
@ -9,10 +5,3 @@ pub mod post;
pub mod private_message;
pub mod site;
pub mod user;
#[async_trait::async_trait(?Send)]
pub trait PerformCrud {
type Response: serde::ser::Serialize + Send + Clone + Sync;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError>;
}

View file

@ -1,6 +1,5 @@
use crate::PerformCrud;
use activitypub_federation::http_signatures::generate_actor_keypair;
use actix_web::web::Data;
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::{LoginResponse, Register},
@ -38,177 +37,173 @@ use lemmy_utils::{
},
};
#[async_trait::async_trait(?Send)]
impl PerformCrud for Register {
type Response = LoginResponse;
#[tracing::instrument(skip(context))]
pub async fn register(
data: Json<Register>,
context: Data<LemmyContext>,
) -> Result<Json<LoginResponse>, LemmyError> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
let require_registration_application =
local_site.registration_mode == RegistrationMode::RequireApplication;
#[tracing::instrument(skip(self, context))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<LoginResponse, LemmyError> {
let data: &Register = self;
if local_site.registration_mode == RegistrationMode::Closed {
return Err(LemmyErrorType::RegistrationClosed)?;
}
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
let require_registration_application =
local_site.registration_mode == RegistrationMode::RequireApplication;
password_length_check(&data.password)?;
honeypot_check(&data.honeypot)?;
if local_site.registration_mode == RegistrationMode::Closed {
return Err(LemmyErrorType::RegistrationClosed)?;
}
if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyErrorType::EmailRequired)?;
}
password_length_check(&data.password)?;
honeypot_check(&data.honeypot)?;
if local_site.site_setup && require_registration_application && data.answer.is_none() {
return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?;
}
if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyErrorType::EmailRequired)?;
}
// Make sure passwords match
if data.password != data.password_verify {
return Err(LemmyErrorType::PasswordsDoNotMatch)?;
}
if local_site.site_setup && require_registration_application && data.answer.is_none() {
return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?;
}
// Make sure passwords match
if data.password != data.password_verify {
return Err(LemmyErrorType::PasswordsDoNotMatch)?;
}
if local_site.site_setup && local_site.captcha_enabled {
if let Some(captcha_uuid) = &data.captcha_uuid {
let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
let check = CaptchaAnswer::check_captcha(
&mut context.pool(),
CheckCaptchaAnswer {
uuid,
answer: data.captcha_answer.clone().unwrap_or_default(),
},
)
.await?;
if !check {
return Err(LemmyErrorType::CaptchaIncorrect)?;
}
} else {
if local_site.site_setup && local_site.captcha_enabled {
if let Some(captcha_uuid) = &data.captcha_uuid {
let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
let check = CaptchaAnswer::check_captcha(
&mut context.pool(),
CheckCaptchaAnswer {
uuid,
answer: data.captcha_answer.clone().unwrap_or_default(),
},
)
.await?;
if !check {
return Err(LemmyErrorType::CaptchaIncorrect)?;
}
} else {
return Err(LemmyErrorType::CaptchaIncorrect)?;
}
}
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?;
let username = sanitize_html(&data.username);
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?;
let username = sanitize_html(&data.username);
let actor_keypair = generate_actor_keypair()?;
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
let actor_id = generate_local_apub_endpoint(
EndpointType::Person,
&data.username,
&context.settings().get_protocol_and_hostname(),
)?;
let actor_keypair = generate_actor_keypair()?;
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
let actor_id = generate_local_apub_endpoint(
EndpointType::Person,
&data.username,
&context.settings().get_protocol_and_hostname(),
)?;
if let Some(email) = &data.email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
if let Some(email) = &data.email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
}
// We have to create both a person, and local_user
// We have to create both a person, and local_user
// Register the new person
let person_form = PersonInsertForm::builder()
.name(username)
.actor_id(Some(actor_id.clone()))
.private_key(Some(actor_keypair.private_key))
.public_key(actor_keypair.public_key)
.inbox_url(Some(generate_inbox_url(&actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?))
// If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup))
.instance_id(site_view.site.instance_id)
.build();
// Register the new person
let person_form = PersonInsertForm::builder()
.name(username)
.actor_id(Some(actor_id.clone()))
.private_key(Some(actor_keypair.private_key))
.public_key(actor_keypair.public_key)
.inbox_url(Some(generate_inbox_url(&actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?))
// If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup))
.instance_id(site_view.site.instance_id)
.build();
// insert the person
let inserted_person = Person::create(&mut context.pool(), &person_form)
.await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
// insert the person
let inserted_person = Person::create(&mut context.pool(), &person_form)
.await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
// Automatically set their application as accepted, if they created this with open registration.
// Also fixes a bug which allows users to log in when registrations are changed to closed.
let accepted_application = Some(!require_registration_application);
// Automatically set their application as accepted, if they created this with open registration.
// Also fixes a bug which allows users to log in when registrations are changed to closed.
let accepted_application = Some(!require_registration_application);
// Create the local user
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.build();
// Create the local user
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.build();
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;
if local_site.site_setup && require_registration_application {
// Create the registration application
let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id,
// We already made sure answer was not null above
answer: data.answer.clone().expect("must have an answer"),
};
RegistrationApplication::create(&mut context.pool(), &form).await?;
}
// Email the admins
if local_site.application_email_admins {
send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
.await?;
}
let mut login_response = LoginResponse {
jwt: None,
registration_created: false,
verify_email_sent: false,
if local_site.site_setup && require_registration_application {
// Create the registration application
let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id,
// We already made sure answer was not null above
answer: data.answer.clone().expect("must have an answer"),
};
// Log the user in directly if the site is not setup, or email verification and application aren't required
if !local_site.site_setup
|| (!require_registration_application && !local_site.require_email_verification)
{
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates::default(),
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
RegistrationApplication::create(&mut context.pool(), &form).await?;
}
send_verification_email(
&local_user_view,
&email,
&mut context.pool(),
context.settings(),
)
.await?;
login_response.verify_email_sent = true;
}
// Email the admins
if local_site.application_email_admins {
send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
.await?;
}
if require_registration_application {
login_response.registration_created = true;
}
let mut login_response = LoginResponse {
jwt: None,
registration_created: false,
verify_email_sent: false,
};
// Log the user in directly if the site is not setup, or email verification and application aren't required
if !local_site.site_setup
|| (!require_registration_application && !local_site.require_email_verification)
{
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates::default(),
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
send_verification_email(
&local_user_view,
&email,
&mut context.pool(),
context.settings(),
)
.await?;
login_response.verify_email_sent = true;
}
Ok(login_response)
if require_registration_application {
login_response.registration_created = true;
}
}
Ok(Json(login_response))
}

View file

@ -21,7 +21,6 @@ use lemmy_api::{
use lemmy_api_common::{
community::TransferCommunity,
context::LemmyContext,
custom_emoji::{CreateCustomEmoji, DeleteCustomEmoji, EditCustomEmoji},
person::{
AddAdmin,
BlockPerson,
@ -37,7 +36,6 @@ use lemmy_api_common::{
MarkPersonMentionAsRead,
PasswordChangeAfterReset,
PasswordReset,
Register,
SaveUserSettings,
VerifyEmail,
},
@ -76,6 +74,11 @@ use lemmy_api_crud::{
remove::remove_community,
update::update_community,
},
custom_emoji::{
create::create_custom_emoji,
delete::delete_custom_emoji,
update::update_custom_emoji,
},
post::{
create::create_post,
delete::delete_post,
@ -90,8 +93,7 @@ use lemmy_api_crud::{
update::update_private_message,
},
site::{create::create_site, read::get_site, update::update_site},
user::delete::delete_account,
PerformCrud,
user::{create::register, delete::delete_account},
};
use lemmy_apub::{
api::{
@ -253,7 +255,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
web::resource("/user/register")
.guard(guard::Post())
.wrap(rate_limit.register())
.route(web::post().to(route_post_crud::<Register>)),
.route(web::post().to(register)),
)
.service(
// Handle captcha separately
@ -333,12 +335,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.service(
web::scope("/custom_emoji")
.wrap(rate_limit.message())
.route("", web::post().to(route_post_crud::<CreateCustomEmoji>))
.route("", web::put().to(route_post_crud::<EditCustomEmoji>))
.route(
"/delete",
web::post().to(route_post_crud::<DeleteCustomEmoji>),
),
.route("", web::post().to(create_custom_emoji))
.route("", web::put().to(update_custom_emoji))
.route("/delete", web::post().to(delete_custom_emoji)),
),
);
}
@ -398,43 +397,3 @@ where
{
perform::<Data>(data.0, context, apub_data).await
}
async fn perform_crud<'a, Data>(
data: Data,
context: web::Data<LemmyContext>,
apub_data: activitypub_federation::config::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
Data: PerformCrud
+ SendActivity<Response = <Data as PerformCrud>::Response>
+ Clone
+ Deserialize<'a>
+ Send
+ 'static,
{
let res = data.perform(&context).await?;
let res_clone = res.clone();
let fed_task = async move { SendActivity::send_activity(&data, &res_clone, &apub_data).await };
if *SYNCHRONOUS_FEDERATION {
fed_task.await?;
} else {
spawn_try_task(fed_task);
}
Ok(HttpResponse::Ok().json(&res))
}
async fn route_post_crud<'a, Data>(
data: web::Json<Data>,
context: web::Data<LemmyContext>,
apub_data: activitypub_federation::config::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
Data: PerformCrud
+ SendActivity<Response = <Data as PerformCrud>::Response>
+ Clone
+ Deserialize<'a>
+ Send
+ 'static,
{
perform_crud::<Data>(data.0, context, apub_data).await
}