lemmy/server/src/api/community.rs
nutomic d4dccd17ae implement ActivitySender actor (#89)
Merge pull request 'Adding unique ap_ids. Fixes #1100' (#90) from unique_ap_ids into activity-sender

Reviewed-on: https://yerbamate.dev/LemmyNet/lemmy/pulls/90

Adding back in on_conflict.

Trying to add back in the on_conflict_do_nothing.

Trying to reduce delay time.

Removing createFakes.

Removing some unit tests.

Adding comment jest timeout.

Fixing tests again.

Fixing tests again.

Merge branch 'activity-sender' into unique_ap_ids_2

Replace actix client with reqwest to speed up federation tests

Trying to fix tests again.

Fixing unit tests.

Fixing some broken unit tests, not done yet.

Adding uniques.

Adding unique ap_ids. Fixes #1100

use proper sql functionality for upsert

added logging

in fetcher, replace post/comment::create with upsert

no need to do an actual update in post/comment::upsert

Merge branch 'main' into activity-sender

implement upsert for user/community

reuse http client

got it working

attempt to use background-jobs crate

rewrite with proper error handling and less boilerplate

remove do_send, dont return errors from activity_sender

WIP: implement ActivitySender actor

Co-authored-by: dessalines <dessalines@noreply.yerbamate.dev>
Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Felix Ableitner <me@nutomic.com>
Reviewed-on: https://yerbamate.dev/LemmyNet/lemmy/pulls/89
2020-08-31 13:48:02 +00:00

969 lines
27 KiB
Rust

use super::*;
use crate::{
api::{is_admin, is_mod_or_admin, APIError, Perform},
apub::ActorType,
blocking,
websocket::{
server::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage},
UserOperation,
},
ConnectionId,
};
use anyhow::Context;
use lemmy_db::{
comment::Comment,
comment_view::CommentQueryBuilder,
diesel_option_overwrite,
naive_now,
post::Post,
Bannable,
Crud,
Followable,
Joinable,
SortType,
};
use lemmy_utils::{
generate_actor_keypair,
is_valid_community_name,
location_info,
make_apub_endpoint,
naive_from_unix,
EndpointType,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Serialize, Deserialize)]
pub struct GetCommunity {
id: Option<i32>,
pub name: Option<String>,
auth: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct GetCommunityResponse {
pub community: CommunityView,
pub moderators: Vec<CommunityModeratorView>,
pub online: usize,
}
#[derive(Serialize, Deserialize)]
pub struct CreateCommunity {
name: String,
title: String,
description: Option<String>,
icon: Option<String>,
banner: Option<String>,
category_id: i32,
nsfw: bool,
auth: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct CommunityResponse {
pub community: CommunityView,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ListCommunities {
pub sort: String,
pub page: Option<i64>,
pub limit: Option<i64>,
pub auth: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ListCommunitiesResponse {
pub communities: Vec<CommunityView>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct BanFromCommunity {
pub community_id: i32,
user_id: i32,
ban: bool,
remove_data: Option<bool>,
reason: Option<String>,
expires: Option<i64>,
auth: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct BanFromCommunityResponse {
user: UserView,
banned: bool,
}
#[derive(Serialize, Deserialize)]
pub struct AddModToCommunity {
pub community_id: i32,
user_id: i32,
added: bool,
auth: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct AddModToCommunityResponse {
moderators: Vec<CommunityModeratorView>,
}
#[derive(Serialize, Deserialize)]
pub struct EditCommunity {
pub edit_id: i32,
title: String,
description: Option<String>,
icon: Option<String>,
banner: Option<String>,
category_id: i32,
nsfw: bool,
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct DeleteCommunity {
pub edit_id: i32,
deleted: bool,
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct RemoveCommunity {
pub edit_id: i32,
removed: bool,
reason: Option<String>,
expires: Option<i64>,
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct FollowCommunity {
community_id: i32,
follow: bool,
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct GetFollowedCommunities {
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct GetFollowedCommunitiesResponse {
communities: Vec<CommunityFollowerView>,
}
#[derive(Serialize, Deserialize)]
pub struct TransferCommunity {
community_id: i32,
user_id: i32,
auth: String,
}
#[async_trait::async_trait(?Send)]
impl Perform for GetCommunity {
type Response = GetCommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> {
let data: &GetCommunity = &self;
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let user_id = user.map(|u| u.id);
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
let community = match data.id {
Some(id) => blocking(context.pool(), move |conn| Community::read(conn, id)).await??,
None => match blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &name)
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
},
};
let community_id = community.id;
let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, user_id)
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
let community_id = community.id;
let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await?
{
Ok(moderators) => moderators,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
if let Some(id) = websocket_id {
context
.chat_server()
.do_send(JoinCommunityRoom { community_id, id });
}
let online = context
.chat_server()
.send(GetCommunityUsersOnline { community_id })
.await
.unwrap_or(1);
let res = GetCommunityResponse {
community: community_view,
moderators,
online,
};
// Return the jwt
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
impl Perform for CreateCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
let data: &CreateCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?;
check_slurs(&data.title)?;
check_slurs_opt(&data.description)?;
if !is_valid_community_name(&data.name) {
return Err(APIError::err("invalid_community_name").into());
}
// Double check for duplicate community actor_ids
let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
let actor_id_cloned = actor_id.to_owned();
let community_dupe = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, &actor_id_cloned)
})
.await?;
if community_dupe.is_ok() {
return Err(APIError::err("community_already_exists").into());
}
// When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?;
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
description: data.description.to_owned(),
icon: Some(data.icon.to_owned()),
banner: Some(data.banner.to_owned()),
category_id: data.category_id,
creator_id: user.id,
removed: None,
deleted: None,
nsfw: data.nsfw,
updated: None,
actor_id: Some(actor_id),
local: true,
private_key: Some(keypair.private_key),
public_key: Some(keypair.public_key),
last_refreshed_at: None,
published: None,
};
let inserted_community = match blocking(context.pool(), move |conn| {
Community::create(conn, &community_form)
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("community_already_exists").into()),
};
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
user_id: user.id,
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
user_id: user.id,
};
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
if blocking(context.pool(), follow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, inserted_community.id, Some(user_id))
})
.await??;
Ok(CommunityResponse {
community: community_view,
})
}
}
#[async_trait::async_trait(?Send)]
impl Perform for EditCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
let data: &EditCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.title)?;
check_slurs_opt(&data.description)?;
// Verify its a mod (only mods can edit it)
let edit_id = data.edit_id;
let mods: Vec<i32> = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, edit_id)
.map(|v| v.into_iter().map(|m| m.user_id).collect())
})
.await??;
if !mods.contains(&user.id) {
return Err(APIError::err("not_a_moderator").into());
}
let edit_id = data.edit_id;
let read_community =
blocking(context.pool(), move |conn| Community::read(conn, edit_id)).await??;
let icon = diesel_option_overwrite(&data.icon);
let banner = diesel_option_overwrite(&data.banner);
let community_form = CommunityForm {
name: read_community.name,
title: data.title.to_owned(),
description: data.description.to_owned(),
icon,
banner,
category_id: data.category_id.to_owned(),
creator_id: read_community.creator_id,
removed: Some(read_community.removed),
deleted: Some(read_community.deleted),
nsfw: data.nsfw,
updated: Some(naive_now()),
actor_id: Some(read_community.actor_id),
local: read_community.local,
private_key: read_community.private_key,
public_key: read_community.public_key,
last_refreshed_at: None,
published: None,
};
let edit_id = data.edit_id;
match blocking(context.pool(), move |conn| {
Community::update(conn, edit_id, &community_form)
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
};
// TODO there needs to be some kind of an apub update
// process for communities and users
let edit_id = data.edit_id;
let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
.await??;
let res = CommunityResponse {
community: community_view,
};
send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
impl Perform for DeleteCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
let data: &DeleteCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify its the creator (only a creator can delete the community)
let edit_id = data.edit_id;
let read_community =
blocking(context.pool(), move |conn| Community::read(conn, edit_id)).await??;
if read_community.creator_id != user.id {
return Err(APIError::err("no_community_edit_allowed").into());
}
// Do the delete
let edit_id = data.edit_id;
let deleted = data.deleted;
let updated_community = match blocking(context.pool(), move |conn| {
Community::update_deleted(conn, edit_id, deleted)
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
};
// Send apub messages
if deleted {
updated_community.send_delete(&user, context).await?;
} else {
updated_community.send_undo_delete(&user, context).await?;
}
let edit_id = data.edit_id;
let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
.await??;
let res = CommunityResponse {
community: community_view,
};
send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
impl Perform for RemoveCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
let data: &RemoveCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify its an admin (only an admin can remove a community)
is_admin(context.pool(), user.id).await?;
// Do the remove
let edit_id = data.edit_id;
let removed = data.removed;
let updated_community = match blocking(context.pool(), move |conn| {
Community::update_removed(conn, edit_id, removed)
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
};
// Mod tables
let expires = match data.expires {
Some(time) => Some(naive_from_unix(time)),
None => None,
};
let form = ModRemoveCommunityForm {
mod_user_id: user.id,
community_id: data.edit_id,
removed: Some(removed),
reason: data.reason.to_owned(),
expires,
};
blocking(context.pool(), move |conn| {
ModRemoveCommunity::create(conn, &form)
})
.await??;
// Apub messages
if removed {
updated_community.send_remove(&user, context).await?;
} else {
updated_community.send_undo_remove(&user, context).await?;
}
let edit_id = data.edit_id;
let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
.await??;
let res = CommunityResponse {
community: community_view,
};
send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
impl Perform for ListCommunities {
type Response = ListCommunitiesResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<ListCommunitiesResponse, LemmyError> {
let data: &ListCommunities = &self;
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let user_id = match &user {
Some(user) => Some(user.id),
None => None,
};
let show_nsfw = match &user {
Some(user) => user.show_nsfw,
None => false,
};
let sort = SortType::from_str(&data.sort)?;
let page = data.page;
let limit = data.limit;
let communities = blocking(context.pool(), move |conn| {
CommunityQueryBuilder::create(conn)
.sort(&sort)
.for_user(user_id)
.show_nsfw(show_nsfw)
.page(page)
.limit(limit)
.list()
})
.await??;
// Return the jwt
Ok(ListCommunitiesResponse { communities })
}
}
#[async_trait::async_trait(?Send)]
impl Perform for FollowCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
let data: &FollowCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
user_id: user.id,
};
if community.local {
if data.follow {
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
if blocking(context.pool(), follow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
} else {
let unfollow =
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(context.pool(), unfollow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
}
} else if data.follow {
// Dont actually add to the community followers here, because you need
// to wait for the accept
user.send_follow(&community.actor_id()?, context).await?;
} else {
user.send_unfollow(&community.actor_id()?, context).await?;
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(context.pool(), unfollow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
}
// TODO: this needs to return a "pending" state, until Accept is received from the remote server
let community_id = data.community_id;
let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
.await??;
Ok(CommunityResponse {
community: community_view,
})
}
}
#[async_trait::async_trait(?Send)]
impl Perform for GetFollowedCommunities {
type Response = GetFollowedCommunitiesResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
let data: &GetFollowedCommunities = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let user_id = user.id;
let communities = match blocking(context.pool(), move |conn| {
CommunityFollowerView::for_user(conn, user_id)
})
.await?
{
Ok(communities) => communities,
_ => return Err(APIError::err("system_err_login").into()),
};
// Return the jwt
Ok(GetFollowedCommunitiesResponse { communities })
}
}
#[async_trait::async_trait(?Send)]
impl Perform for BanFromCommunity {
type Response = BanFromCommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
let banned_user_id = data.user_id;
// Verify that only mods or admins can ban
is_mod_or_admin(context.pool(), user.id, community_id).await?;
let community_user_ban_form = CommunityUserBanForm {
community_id: data.community_id,
user_id: data.user_id,
};
if data.ban {
let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
if blocking(context.pool(), ban).await?.is_err() {
return Err(APIError::err("community_user_already_banned").into());
}
} else {
let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
if blocking(context.pool(), unban).await?.is_err() {
return Err(APIError::err("community_user_already_banned").into());
}
}
// Remove/Restore their data if that's desired
if let Some(remove_data) = data.remove_data {
// Posts
blocking(context.pool(), move |conn: &'_ _| {
Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), remove_data)
})
.await??;
// Comments
// Diesel doesn't allow updates with joins, so this has to be a loop
let comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn)
.for_creator_id(banned_user_id)
.for_community_id(community_id)
.limit(std::i64::MAX)
.list()
})
.await??;
for comment in &comments {
let comment_id = comment.id;
blocking(context.pool(), move |conn: &'_ _| {
Comment::update_removed(conn, comment_id, remove_data)
})
.await??;
}
}
// Mod tables
// TODO eventually do correct expires
let expires = match data.expires {
Some(time) => Some(naive_from_unix(time)),
None => None,
};
let form = ModBanFromCommunityForm {
mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
reason: data.reason.to_owned(),
banned: Some(data.ban),
expires,
};
blocking(context.pool(), move |conn| {
ModBanFromCommunity::create(conn, &form)
})
.await??;
let user_id = data.user_id;
let user_view = blocking(context.pool(), move |conn| {
UserView::get_user_secure(conn, user_id)
})
.await??;
let res = BanFromCommunityResponse {
user: user_view,
banned: data.ban,
};
context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::BanFromCommunity,
response: res.clone(),
community_id,
websocket_id,
});
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
impl Perform for AddModToCommunity {
type Response = AddModToCommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
user_id: data.user_id,
};
let community_id = data.community_id;
// Verify that only mods or admins can add mod
is_mod_or_admin(context.pool(), user.id, community_id).await?;
if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
} else {
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
if blocking(context.pool(), leave).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
}
// Mod tables
let form = ModAddCommunityForm {
mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
removed: Some(!data.added),
};
blocking(context.pool(), move |conn| {
ModAddCommunity::create(conn, &form)
})
.await??;
let community_id = data.community_id;
let moderators = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let res = AddModToCommunityResponse { moderators };
context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::AddModToCommunity,
response: res.clone(),
community_id,
websocket_id,
});
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
impl Perform for TransferCommunity {
type Response = GetCommunityResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> {
let data: &TransferCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
let read_community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let site_creator_id = blocking(context.pool(), move |conn| {
Site::read(conn, 1).map(|s| s.creator_id)
})
.await??;
let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
let creator_index = admins
.iter()
.position(|r| r.id == site_creator_id)
.context(location_info!())?;
let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user);
// Make sure user is the creator, or an admin
if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
return Err(APIError::err("not_an_admin").into());
}
let community_id = data.community_id;
let new_creator = data.user_id;
let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
if blocking(context.pool(), update).await?.is_err() {
return Err(APIError::err("couldnt_update_community").into());
};
// You also have to re-do the community_moderator table, reordering it.
let community_id = data.community_id;
let mut community_mods = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let creator_index = community_mods
.iter()
.position(|r| r.user_id == data.user_id)
.context(location_info!())?;
let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_user);
let community_id = data.community_id;
blocking(context.pool(), move |conn| {
CommunityModerator::delete_for_community(conn, community_id)
})
.await??;
// TODO: this should probably be a bulk operation
for cmod in &community_mods {
let community_moderator_form = CommunityModeratorForm {
community_id: cmod.community_id,
user_id: cmod.user_id,
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
}
// Mod tables
let form = ModAddCommunityForm {
mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
removed: Some(false),
};
blocking(context.pool(), move |conn| {
ModAddCommunity::create(conn, &form)
})
.await??;
let community_id = data.community_id;
let user_id = user.id;
let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
.await?
{
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
let community_id = data.community_id;
let moderators = match blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await?
{
Ok(moderators) => moderators,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
// Return the jwt
Ok(GetCommunityResponse {
community: community_view,
moderators,
online: 0,
})
}
}
pub fn send_community_websocket(
res: &CommunityResponse,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
op: UserOperation,
) {
// Strip out the user id and subscribed when sending to others
let mut res_sent = res.clone();
res_sent.community.user_id = None;
res_sent.community.subscribed = None;
context.chat_server().do_send(SendCommunityRoomMessage {
op,
response: res_sent,
community_id: res.community.id,
websocket_id,
});
}