use crate::Perform;
use actix_web::web::Data;
-use anyhow::Context;
+use diesel::NotFound;
use lemmy_api_common::{
blocking,
build_federated_instances,
+ check_private_instance,
get_local_user_view_from_jwt,
get_local_user_view_from_jwt_opt,
is_admin,
+ send_application_approved_email,
site::*,
};
-use lemmy_apub::{build_actor_id_from_shortname, fetcher::search::search_by_apub_id, EndpointType};
-use lemmy_db_queries::{
+use lemmy_apub::{
+ fetcher::{
+ search::{search_by_apub_id, SearchableObjects},
+ webfinger::webfinger_resolve,
+ },
+ objects::community::ApubCommunity,
+ EndpointType,
+};
+use lemmy_db_schema::{
+ diesel_option_overwrite,
from_opt_str_to_opt_enum,
- source::site::Site_,
- Crud,
- DeleteableOrRemoveable,
+ newtypes::PersonId,
+ source::{
+ local_user::{LocalUser, LocalUserForm},
+ moderator::*,
+ person::Person,
+ registration_application::{RegistrationApplication, RegistrationApplicationForm},
+ site::Site,
+ },
+ traits::{Crud, DeleteableOrRemoveable},
+ DbPool,
ListingType,
SearchType,
SortType,
};
-use lemmy_db_schema::source::{moderator::*, site::Site};
use lemmy_db_views::{
- comment_view::CommentQueryBuilder,
- post_view::PostQueryBuilder,
+ comment_view::{CommentQueryBuilder, CommentView},
+ local_user_view::LocalUserView,
+ post_view::{PostQueryBuilder, PostView},
+ registration_application_view::{
+ RegistrationApplicationQueryBuilder,
+ RegistrationApplicationView,
+ },
site_view::SiteView,
};
use lemmy_db_views_actor::{
- community_view::CommunityQueryBuilder,
+ community_view::{CommunityQueryBuilder, CommunityView},
person_view::{PersonQueryBuilder, PersonViewSafe},
};
use lemmy_db_views_moderator::{
mod_sticky_post_view::ModStickyPostView,
mod_transfer_community_view::ModTransferCommunityView,
};
-use lemmy_utils::{
- location_info,
- settings::structs::Settings,
- version,
- ApiError,
- ConnectionId,
- LemmyError,
-};
+use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
use lemmy_websocket::LemmyContext;
#[async_trait::async_trait(?Send)]
impl Perform for GetModlog {
type Response = GetModlogResponse;
+ #[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<GetModlogResponse, LemmyError> {
let data: &GetModlog = self;
+ let local_user_view =
+ get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+ .await?;
+
+ check_private_instance(&local_user_view, context.pool()).await?;
+
let community_id = data.community_id;
let mod_person_id = data.mod_person_id;
let page = data.page;
impl Perform for Search {
type Response = SearchResponse;
+ #[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<SearchResponse, LemmyError> {
let data: &Search = self;
- let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
+ let local_user_view =
+ get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+ .await?;
+
+ check_private_instance(&local_user_view, context.pool()).await?;
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
let show_bot_accounts = local_user_view
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
let community_id = data.community_id;
- let community_actor_id = data
- .community_name
- .as_ref()
- .map(|t| build_actor_id_from_shortname(EndpointType::Community, t).ok())
- .unwrap_or(None);
+ let community_actor_id = if let Some(name) = &data.community_name {
+ webfinger_resolve::<ApubCommunity>(name, EndpointType::Community, context, &mut 0)
+ .await
+ .ok()
+ } else {
+ None
+ };
let creator_id = data.creator_id;
match search_type {
SearchType::Posts => {
}
};
- // Blank out deleted or removed info
- for cv in comments
- .iter_mut()
- .filter(|cv| cv.comment.deleted || cv.comment.removed)
- {
- cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
- }
+ // Blank out deleted or removed info for non logged in users
+ if person_id.is_none() {
+ for cv in communities
+ .iter_mut()
+ .filter(|cv| cv.community.deleted || cv.community.removed)
+ {
+ cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
+ }
- for cv in communities
- .iter_mut()
- .filter(|cv| cv.community.deleted || cv.community.removed)
- {
- cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
- }
+ for pv in posts
+ .iter_mut()
+ .filter(|p| p.post.deleted || p.post.removed)
+ {
+ pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
+ }
- for pv in posts
- .iter_mut()
- .filter(|p| p.post.deleted || p.post.removed)
- {
- pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
+ for cv in comments
+ .iter_mut()
+ .filter(|cv| cv.comment.deleted || cv.comment.removed)
+ {
+ cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
+ }
}
// Return the jwt
impl Perform for ResolveObject {
type Response = ResolveObjectResponse;
+ #[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<ResolveObjectResponse, LemmyError> {
- let local_user_view = get_local_user_view_from_jwt_opt(&self.auth, context.pool()).await?;
- search_by_apub_id(&self.q, local_user_view, context).await
+ let local_user_view =
+ get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
+ .await?;
+ check_private_instance(&local_user_view, context.pool()).await?;
+
+ let res = search_by_apub_id(&self.q, context)
+ .await
+ .map_err(LemmyError::from)
+ .map_err(|e| e.with_message("couldnt_find_object"))?;
+ convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
+ .await
+ .map_err(LemmyError::from)
+ .map_err(|e| e.with_message("couldnt_find_object"))
}
}
+async fn convert_response(
+ object: SearchableObjects,
+ user_id: Option<PersonId>,
+ pool: &DbPool,
+) -> Result<ResolveObjectResponse, LemmyError> {
+ let removed_or_deleted;
+ let mut res = ResolveObjectResponse {
+ comment: None,
+ post: None,
+ community: None,
+ person: None,
+ };
+ use SearchableObjects::*;
+ match object {
+ Person(p) => {
+ removed_or_deleted = p.deleted;
+ res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
+ }
+ Community(c) => {
+ removed_or_deleted = c.deleted || c.removed;
+ res.community =
+ Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
+ }
+ Post(p) => {
+ removed_or_deleted = p.deleted || p.removed;
+ res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
+ }
+ Comment(c) => {
+ removed_or_deleted = c.deleted || c.removed;
+ res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
+ }
+ };
+ // if the object was deleted from database, dont return it
+ if removed_or_deleted {
+ return Err(NotFound {}.into());
+ }
+ Ok(res)
+}
+
#[async_trait::async_trait(?Send)]
-impl Perform for TransferSite {
+impl Perform for LeaveAdmin {
type Response = GetSiteResponse;
+ #[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<GetSiteResponse, LemmyError> {
- let data: &TransferSite = self;
- let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
+ let data: &LeaveAdmin = self;
+ let local_user_view =
+ get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
is_admin(&local_user_view)?;
- let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
-
- // Make sure user is the creator
- if read_site.creator_id != local_user_view.person.id {
- return Err(ApiError::err("not_an_admin").into());
+ // Make sure there isn't just one admin (so if one leaves, there will still be one left)
+ let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
+ if admins.len() == 1 {
+ return Err(LemmyError::from_message("cannot_leave_admin"));
}
- let new_creator_id = data.person_id;
- let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
- if blocking(context.pool(), transfer_site).await?.is_err() {
- return Err(ApiError::err("couldnt_update_site").into());
- };
+ let person_id = local_user_view.person.id;
+ blocking(context.pool(), move |conn| {
+ Person::leave_admin(conn, person_id)
+ })
+ .await??;
// Mod tables
let form = ModAddForm {
- mod_person_id: local_user_view.person.id,
- other_person_id: data.person_id,
- removed: Some(false),
+ mod_person_id: person_id,
+ other_person_id: person_id,
+ removed: Some(true),
};
blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
- let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
-
- let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
- let creator_index = admins
- .iter()
- .position(|r| r.person.id == site_view.creator.id)
- .context(location_info!())?;
- let creator_person = admins.remove(creator_index);
- admins.insert(0, creator_person);
+ // Reread site and admins
+ let site_view = blocking(context.pool(), SiteView::read).await??;
+ let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
- let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
- let federated_instances = build_federated_instances(context.pool()).await?;
+ let federated_instances = build_federated_instances(
+ context.pool(),
+ &context.settings().federation,
+ &context.settings().hostname,
+ )
+ .await?;
Ok(GetSiteResponse {
site_view: Some(site_view),
admins,
- banned,
online: 0,
version: version::VERSION.to_string(),
my_user: None,
impl Perform for GetSiteConfig {
type Response = GetSiteConfigResponse;
+ #[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &GetSiteConfig = self;
- let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
+ let local_user_view =
+ get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
// Only let admins read this
is_admin(&local_user_view)?;
impl Perform for SaveSiteConfig {
type Response = GetSiteConfigResponse;
+ #[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &SaveSiteConfig = self;
- let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
+ let local_user_view =
+ get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
// Only let admins read this
is_admin(&local_user_view)?;
// Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
let config_hjson = Settings::save_config_file(&data.config_hjson)
- .map_err(|_| ApiError::err("couldnt_update_site"))?;
+ .map_err(LemmyError::from)
+ .map_err(|e| e.with_message("couldnt_update_site"))?;
Ok(GetSiteConfigResponse { config_hjson })
}
}
+
+/// Lists registration applications, filterable by undenied only.
+#[async_trait::async_trait(?Send)]
+impl Perform for ListRegistrationApplications {
+ type Response = ListRegistrationApplicationsResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<Self::Response, LemmyError> {
+ let data = self;
+ let local_user_view =
+ get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+
+ // Make sure user is an admin
+ is_admin(&local_user_view)?;
+
+ let unread_only = data.unread_only;
+ let verified_email_only = blocking(context.pool(), Site::read_simple)
+ .await??
+ .require_email_verification;
+
+ let page = data.page;
+ let limit = data.limit;
+ let registration_applications = blocking(context.pool(), move |conn| {
+ RegistrationApplicationQueryBuilder::create(conn)
+ .unread_only(unread_only)
+ .verified_email_only(verified_email_only)
+ .page(page)
+ .limit(limit)
+ .list()
+ })
+ .await??;
+
+ let res = Self::Response {
+ registration_applications,
+ };
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for ApproveRegistrationApplication {
+ type Response = RegistrationApplicationResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<Self::Response, LemmyError> {
+ let data = self;
+ let local_user_view =
+ get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+
+ let app_id = data.id;
+
+ // Only let admins do this
+ is_admin(&local_user_view)?;
+
+ // Update the registration with reason, admin_id
+ let deny_reason = diesel_option_overwrite(&data.deny_reason);
+ let app_form = RegistrationApplicationForm {
+ admin_id: Some(local_user_view.person.id),
+ deny_reason,
+ ..RegistrationApplicationForm::default()
+ };
+
+ let registration_application = blocking(context.pool(), move |conn| {
+ RegistrationApplication::update(conn, app_id, &app_form)
+ })
+ .await??;
+
+ // Update the local_user row
+ let local_user_form = LocalUserForm {
+ accepted_application: Some(data.approve),
+ ..LocalUserForm::default()
+ };
+
+ let approved_user_id = registration_application.local_user_id;
+ blocking(context.pool(), move |conn| {
+ LocalUser::update(conn, approved_user_id, &local_user_form)
+ })
+ .await??;
+
+ if data.approve {
+ let approved_local_user_view = blocking(context.pool(), move |conn| {
+ LocalUserView::read(conn, approved_user_id)
+ })
+ .await??;
+
+ if approved_local_user_view.local_user.email.is_some() {
+ send_application_approved_email(&approved_local_user_view, &context.settings())?;
+ }
+ }
+
+ // Read the view
+ let registration_application = blocking(context.pool(), move |conn| {
+ RegistrationApplicationView::read(conn, app_id)
+ })
+ .await??;
+
+ Ok(Self::Response {
+ registration_application,
+ })
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for GetUnreadRegistrationApplicationCount {
+ type Response = GetUnreadRegistrationApplicationCountResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<Self::Response, LemmyError> {
+ let data = self;
+ let local_user_view =
+ get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+
+ // Only let admins do this
+ is_admin(&local_user_view)?;
+
+ let verified_email_only = blocking(context.pool(), Site::read_simple)
+ .await??
+ .require_email_verification;
+
+ let registration_applications = blocking(context.pool(), move |conn| {
+ RegistrationApplicationView::get_unread_count(conn, verified_email_only)
+ })
+ .await??;
+
+ Ok(Self::Response {
+ registration_applications,
+ })
+ }
+}