lemmy/server/src/api/community.rs

655 lines
19 KiB
Rust
Raw Normal View History

2019-05-05 05:20:38 +00:00
use super::*;
2020-04-14 15:37:23 +00:00
use crate::apub::activities::follow_community;
use crate::apub::{format_community_name, gen_keypair_str, make_apub_endpoint, EndpointType};
use diesel::PgConnection;
2019-05-05 05:20:38 +00:00
use std::str::FromStr;
use url::Url;
2019-05-05 05:20:38 +00:00
#[derive(Serialize, Deserialize)]
pub struct GetCommunity {
id: Option<i32>,
pub name: Option<String>,
auth: Option<String>,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct GetCommunityResponse {
pub community: CommunityView,
pub moderators: Vec<CommunityModeratorView>,
pub admins: Vec<UserView>,
pub online: usize,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct CreateCommunity {
name: String,
title: String,
description: Option<String>,
category_id: i32,
nsfw: bool,
auth: String,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize, Clone)]
pub struct CommunityResponse {
pub community: CommunityView,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize, Debug)]
2019-05-05 05:20:38 +00:00
pub struct ListCommunities {
pub sort: String,
pub page: Option<i64>,
pub limit: Option<i64>,
pub auth: Option<String>,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize, Debug)]
2019-05-05 05:20:38 +00:00
pub struct ListCommunitiesResponse {
pub communities: Vec<CommunityView>,
2019-05-05 05:20:38 +00:00
}
2019-05-05 16:20:30 +00:00
#[derive(Serialize, Deserialize, Clone)]
2019-05-05 05:20:38 +00:00
pub struct BanFromCommunity {
pub community_id: i32,
user_id: i32,
ban: bool,
reason: Option<String>,
expires: Option<i64>,
auth: String,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct BanFromCommunityResponse {
user: UserView,
banned: bool,
}
#[derive(Serialize, Deserialize)]
pub struct AddModToCommunity {
pub community_id: i32,
user_id: i32,
added: bool,
auth: String,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct AddModToCommunityResponse {
moderators: Vec<CommunityModeratorView>,
}
#[derive(Serialize, Deserialize)]
pub struct EditCommunity {
pub edit_id: i32,
name: String,
title: String,
description: Option<String>,
category_id: i32,
removed: Option<bool>,
deleted: Option<bool>,
nsfw: bool,
2019-05-05 05:20:38 +00:00
reason: Option<String>,
expires: Option<i64>,
auth: String,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct FollowCommunity {
community_id: i32,
follow: bool,
auth: String,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct GetFollowedCommunities {
auth: String,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct GetFollowedCommunitiesResponse {
communities: Vec<CommunityFollowerView>,
2019-05-05 05:20:38 +00:00
}
#[derive(Serialize, Deserialize)]
pub struct TransferCommunity {
community_id: i32,
user_id: i32,
auth: String,
}
2019-05-05 05:20:38 +00:00
impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<GetCommunityResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &GetCommunity = &self.data;
2019-05-05 05:20:38 +00:00
let user_id: Option<i32> = match &data.auth {
Some(auth) => match Claims::decode(&auth) {
Ok(claims) => {
let user_id = claims.claims.id;
Some(user_id)
2019-05-05 05:20:38 +00:00
}
Err(_e) => None,
},
None => None,
2019-05-05 05:20:38 +00:00
};
let community = match data.id {
Some(id) => Community::read(&conn, id)?,
None => {
match Community::read_from_name(
&conn,
data.name.to_owned().unwrap_or_else(|| "main".to_string()),
) {
Ok(community) => community,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}
}
2019-05-05 05:20:38 +00:00
};
let mut community_view = match CommunityView::read(&conn, community.id, user_id) {
2019-05-05 05:20:38 +00:00
Ok(community) => community,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
2019-05-05 05:20:38 +00:00
};
let moderators = match CommunityModeratorView::for_community(&conn, community.id) {
2019-05-05 05:20:38 +00:00
Ok(moderators) => moderators,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
2019-05-05 05:20:38 +00:00
};
let site_creator_id = Site::read(&conn, 1)?.creator_id;
let mut admins = UserView::admins(&conn)?;
let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user);
2019-05-05 05:20:38 +00:00
if !community.local {
let domain = Url::parse(&community.actor_id)?;
community_view.name =
format_community_name(&community_view.name.to_string(), domain.host_str().unwrap());
}
2019-05-05 05:20:38 +00:00
// Return the jwt
Ok(GetCommunityResponse {
community: community_view,
moderators,
admins,
online: 0,
})
2019-05-05 05:20:38 +00:00
}
}
impl Perform<CommunityResponse> for Oper<CreateCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &CreateCommunity = &self.data;
2019-05-05 05:20:38 +00:00
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
2019-05-05 05:20:38 +00:00
};
if let Err(slurs) = slur_check(&data.name) {
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
}
if let Err(slurs) = slur_check(&data.title) {
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
}
if let Some(description) = &data.description {
if let Err(slurs) = slur_check(description) {
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
}
}
2019-05-05 05:20:38 +00:00
let user_id = claims.id;
// Check for a site ban
if UserView::read(&conn, user_id)?.banned {
2020-01-16 14:39:08 +00:00
return Err(APIError::err("site_ban").into());
2019-05-05 05:20:38 +00:00
}
// When you create a community, make sure the user becomes a moderator and a follower
let (community_public_key, community_private_key) = gen_keypair_str();
2019-05-05 05:20:38 +00:00
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
description: data.description.to_owned(),
category_id: data.category_id,
creator_id: user_id,
removed: None,
deleted: None,
nsfw: data.nsfw,
2019-05-05 05:20:38 +00:00
updated: None,
actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
local: true,
private_key: Some(community_private_key),
public_key: Some(community_public_key),
last_refreshed_at: None,
published: None,
2019-05-05 05:20:38 +00:00
};
let inserted_community = match Community::create(&conn, &community_form) {
Ok(community) => community,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_already_exists").into()),
2019-05-05 05:20:38 +00:00
};
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
user_id,
2019-05-05 05:20:38 +00:00
};
let _inserted_community_moderator =
match CommunityModerator::join(&conn, &community_moderator_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
};
2019-05-05 05:20:38 +00:00
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
user_id,
2019-05-05 05:20:38 +00:00
};
let _inserted_community_follower =
match CommunityFollower::follow(&conn, &community_follower_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
};
2019-05-05 05:20:38 +00:00
let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
Ok(CommunityResponse {
community: community_view,
})
2019-05-05 05:20:38 +00:00
}
}
impl Perform<CommunityResponse> for Oper<EditCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &EditCommunity = &self.data;
2019-05-05 05:20:38 +00:00
if let Err(slurs) = slur_check(&data.name) {
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
}
if let Err(slurs) = slur_check(&data.title) {
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
}
if let Some(description) = &data.description {
if let Err(slurs) = slur_check(description) {
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
}
2019-05-05 05:20:38 +00:00
}
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
2019-05-05 05:20:38 +00:00
};
let user_id = claims.id;
// Check for a site ban
if UserView::read(&conn, user_id)?.banned {
2020-01-16 14:39:08 +00:00
return Err(APIError::err("site_ban").into());
2019-05-05 05:20:38 +00:00
}
// Verify its a mod
let mut editors: Vec<i32> = Vec::new();
editors.append(
&mut CommunityModeratorView::for_community(&conn, data.edit_id)?
.into_iter()
.map(|m| m.user_id)
.collect(),
);
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
2019-05-05 05:20:38 +00:00
if !editors.contains(&user_id) {
2020-01-16 14:39:08 +00:00
return Err(APIError::err("no_community_edit_allowed").into());
2019-05-05 05:20:38 +00:00
}
let read_community = Community::read(&conn, data.edit_id)?;
2019-05-05 05:20:38 +00:00
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
description: data.description.to_owned(),
category_id: data.category_id.to_owned(),
creator_id: user_id,
removed: data.removed.to_owned(),
deleted: data.deleted.to_owned(),
nsfw: data.nsfw,
updated: Some(naive_now()),
actor_id: 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,
2019-05-05 05:20:38 +00:00
};
let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
Ok(community) => community,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
2019-05-05 05:20:38 +00:00
};
// Mod tables
if let Some(removed) = data.removed.to_owned() {
let expires = match data.expires {
Some(time) => Some(naive_from_unix(time)),
None => None,
2019-05-05 05:20:38 +00:00
};
let form = ModRemoveCommunityForm {
mod_user_id: user_id,
community_id: data.edit_id,
removed: Some(removed),
reason: data.reason.to_owned(),
expires,
2019-05-05 05:20:38 +00:00
};
ModRemoveCommunity::create(&conn, &form)?;
}
let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
Ok(CommunityResponse {
community: community_view,
})
2019-05-05 05:20:38 +00:00
}
}
impl Perform<ListCommunitiesResponse> for Oper<ListCommunities> {
fn perform(&self, conn: &PgConnection) -> Result<ListCommunitiesResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &ListCommunities = &self.data;
2019-05-05 05:20:38 +00:00
let user_claims: Option<Claims> = match &data.auth {
Some(auth) => match Claims::decode(&auth) {
Ok(claims) => Some(claims.claims),
Err(_e) => None,
},
None => None,
2019-05-05 05:20:38 +00:00
};
let user_id = match &user_claims {
Some(claims) => Some(claims.id),
None => None,
};
let show_nsfw = match &user_claims {
Some(claims) => claims.show_nsfw,
None => false,
};
2019-05-05 05:20:38 +00:00
let sort = SortType::from_str(&data.sort)?;
let communities = CommunityQueryBuilder::create(&conn)
.sort(&sort)
.for_user(user_id)
.show_nsfw(show_nsfw)
.page(data.page)
.limit(data.limit)
.list()?;
2019-05-05 05:20:38 +00:00
// Return the jwt
2020-01-16 14:39:08 +00:00
Ok(ListCommunitiesResponse { communities })
2019-05-05 05:20:38 +00:00
}
}
impl Perform<CommunityResponse> for Oper<FollowCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &FollowCommunity = &self.data;
2019-05-05 05:20:38 +00:00
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
2019-05-05 05:20:38 +00:00
};
let user_id = claims.id;
2020-04-14 15:37:23 +00:00
let community = Community::read(conn, data.community_id)?;
if community.local {
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
user_id,
2019-05-05 05:20:38 +00:00
};
2020-04-14 15:37:23 +00:00
if data.follow {
match CommunityFollower::follow(&conn, &community_follower_form) {
Ok(user) => user,
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
};
} else {
match CommunityFollower::ignore(&conn, &community_follower_form) {
Ok(user) => user,
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
};
}
2019-05-05 05:20:38 +00:00
} else {
2020-04-14 15:37:23 +00:00
// TODO: still have to implement unfollow
let user = User_::read(conn, user_id)?;
follow_community(&community, &user, conn)?;
// TODO: this needs to return a "pending" state, until Accept is received from the remote server
2019-05-05 05:20:38 +00:00
}
let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
Ok(CommunityResponse {
community: community_view,
})
2019-05-05 05:20:38 +00:00
}
}
impl Perform<GetFollowedCommunitiesResponse> for Oper<GetFollowedCommunities> {
fn perform(&self, conn: &PgConnection) -> Result<GetFollowedCommunitiesResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &GetFollowedCommunities = &self.data;
2019-05-05 05:20:38 +00:00
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
2019-05-05 05:20:38 +00:00
};
let user_id = claims.id;
let communities: Vec<CommunityFollowerView> =
match CommunityFollowerView::for_user(&conn, user_id) {
Ok(communities) => communities,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("system_err_login").into()),
};
2019-05-05 05:20:38 +00:00
// Return the jwt
2020-01-16 14:39:08 +00:00
Ok(GetFollowedCommunitiesResponse { communities })
2019-05-05 05:20:38 +00:00
}
}
impl Perform<BanFromCommunityResponse> for Oper<BanFromCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<BanFromCommunityResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &BanFromCommunity = &self.data;
2019-05-05 05:20:38 +00:00
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
2019-05-05 05:20:38 +00:00
};
let user_id = claims.id;
let community_user_ban_form = CommunityUserBanForm {
community_id: data.community_id,
user_id: data.user_id,
};
if data.ban {
match CommunityUserBan::ban(&conn, &community_user_ban_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
2019-05-05 05:20:38 +00:00
};
} else {
match CommunityUserBan::unban(&conn, &community_user_ban_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
2019-05-05 05:20:38 +00:00
};
}
// Mod tables
let expires = match data.expires {
Some(time) => Some(naive_from_unix(time)),
None => None,
2019-05-05 05:20:38 +00:00
};
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,
2019-05-05 05:20:38 +00:00
};
ModBanFromCommunity::create(&conn, &form)?;
let user_view = UserView::read(&conn, data.user_id)?;
Ok(BanFromCommunityResponse {
user: user_view,
banned: data.ban,
})
2019-05-05 05:20:38 +00:00
}
}
impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<AddModToCommunityResponse, Error> {
2019-05-05 16:20:30 +00:00
let data: &AddModToCommunity = &self.data;
2019-05-05 05:20:38 +00:00
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
2019-05-05 05:20:38 +00:00
};
let user_id = claims.id;
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
user_id: data.user_id,
2019-05-05 05:20:38 +00:00
};
if data.added {
match CommunityModerator::join(&conn, &community_moderator_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
2019-05-05 05:20:38 +00:00
};
} else {
match CommunityModerator::leave(&conn, &community_moderator_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
2019-05-05 05:20:38 +00:00
};
}
// 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),
};
ModAddCommunity::create(&conn, &form)?;
let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
2020-01-16 14:39:08 +00:00
Ok(AddModToCommunityResponse { moderators })
2019-05-05 05:20:38 +00:00
}
}
impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<GetCommunityResponse, Error> {
let data: &TransferCommunity = &self.data;
let claims = match Claims::decode(&data.auth) {
Ok(claims) => claims.claims,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("not_logged_in").into()),
};
let user_id = claims.id;
let read_community = Community::read(&conn, data.community_id)?;
let site_creator_id = Site::read(&conn, 1)?.creator_id;
let mut admins = UserView::admins(&conn)?;
let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
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) {
2020-01-16 14:39:08 +00:00
return Err(APIError::err("not_an_admin").into());
}
let community_form = CommunityForm {
name: read_community.name,
title: read_community.title,
description: read_community.description,
category_id: read_community.category_id,
creator_id: data.user_id,
removed: None,
deleted: None,
nsfw: read_community.nsfw,
updated: Some(naive_now()),
actor_id: 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 _updated_community = match Community::update(&conn, data.community_id, &community_form) {
Ok(community) => community,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
};
// You also have to re-do the community_moderator table, reordering it.
let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?;
let creator_index = community_mods
.iter()
.position(|r| r.user_id == data.user_id)
.unwrap();
let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_user);
CommunityModerator::delete_for_community(&conn, data.community_id)?;
for cmod in &community_mods {
let community_moderator_form = CommunityModeratorForm {
community_id: cmod.community_id,
user_id: cmod.user_id,
};
let _inserted_community_moderator =
match CommunityModerator::join(&conn, &community_moderator_form) {
Ok(user) => user,
2020-01-16 14:39:08 +00:00
Err(_e) => 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),
};
ModAddCommunity::create(&conn, &form)?;
let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
Ok(community) => community,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
Ok(moderators) => moderators,
2020-01-16 14:39:08 +00:00
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
// Return the jwt
Ok(GetCommunityResponse {
community: community_view,
moderators,
admins,
online: 0,
})
}
}