mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-15 00:43:59 +00:00
Added community delete and remove.
This commit is contained in:
parent
a67f46bec5
commit
9bc6698f58
14 changed files with 395 additions and 148 deletions
62
docs/src/contributing_websocket_http_api.md
vendored
62
docs/src/contributing_websocket_http_api.md
vendored
|
@ -1102,7 +1102,7 @@ Search types are `All, Comments, Posts, Communities, Users, Url`
|
||||||
`POST /community/mod`
|
`POST /community/mod`
|
||||||
|
|
||||||
#### Edit Community
|
#### Edit Community
|
||||||
Mods and admins can remove and lock a community, creators can delete it.
|
Only mods can edit a community.
|
||||||
|
|
||||||
##### Request
|
##### Request
|
||||||
```rust
|
```rust
|
||||||
|
@ -1113,10 +1113,6 @@ Mods and admins can remove and lock a community, creators can delete it.
|
||||||
title: String,
|
title: String,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
category_id: i32,
|
category_id: i32,
|
||||||
removed: Option<bool>,
|
|
||||||
deleted: Option<bool>,
|
|
||||||
reason: Option<String>,
|
|
||||||
expires: Option<i64>,
|
|
||||||
auth: String
|
auth: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1134,6 +1130,62 @@ Mods and admins can remove and lock a community, creators can delete it.
|
||||||
|
|
||||||
`PUT /community`
|
`PUT /community`
|
||||||
|
|
||||||
|
#### Delete Community
|
||||||
|
Only a creator can delete a community
|
||||||
|
|
||||||
|
##### Request
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
op: "DeleteCommunity",
|
||||||
|
data: {
|
||||||
|
edit_id: i32,
|
||||||
|
deleted: bool,
|
||||||
|
auth: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
op: "DeleteCommunity",
|
||||||
|
data: {
|
||||||
|
community: CommunityView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### HTTP
|
||||||
|
|
||||||
|
`POST /community/delete`
|
||||||
|
|
||||||
|
#### Remove Community
|
||||||
|
Only admins can remove a community.
|
||||||
|
|
||||||
|
##### Request
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
op: "RemoveCommunity",
|
||||||
|
data: {
|
||||||
|
edit_id: i32,
|
||||||
|
removed: bool,
|
||||||
|
reason: Option<String>,
|
||||||
|
expires: Option<i64>,
|
||||||
|
auth: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
op: "RemoveCommunity",
|
||||||
|
data: {
|
||||||
|
community: CommunityView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### HTTP
|
||||||
|
|
||||||
|
`POST /community/remove`
|
||||||
|
|
||||||
#### Follow Community
|
#### Follow Community
|
||||||
##### Request
|
##### Request
|
||||||
```rust
|
```rust
|
||||||
|
|
|
@ -99,6 +99,39 @@ impl Community {
|
||||||
use crate::schema::community::dsl::*;
|
use crate::schema::community::dsl::*;
|
||||||
community.filter(local.eq(true)).load::<Community>(conn)
|
community.filter(local.eq(true)).load::<Community>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_deleted(
|
||||||
|
conn: &PgConnection,
|
||||||
|
community_id: i32,
|
||||||
|
new_deleted: bool,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::community::dsl::*;
|
||||||
|
diesel::update(community.find(community_id))
|
||||||
|
.set(deleted.eq(new_deleted))
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_removed(
|
||||||
|
conn: &PgConnection,
|
||||||
|
community_id: i32,
|
||||||
|
new_removed: bool,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::community::dsl::*;
|
||||||
|
diesel::update(community.find(community_id))
|
||||||
|
.set(removed.eq(new_removed))
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_creator(
|
||||||
|
conn: &PgConnection,
|
||||||
|
community_id: i32,
|
||||||
|
new_creator_id: i32,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::community::dsl::*;
|
||||||
|
diesel::update(community.find(community_id))
|
||||||
|
.set(creator_id.eq(new_creator_id))
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
|
|
|
@ -295,18 +295,18 @@ pub struct CommunityModeratorView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommunityModeratorView {
|
impl CommunityModeratorView {
|
||||||
pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
|
pub fn for_community(conn: &PgConnection, for_community_id: i32) -> Result<Vec<Self>, Error> {
|
||||||
use super::community_view::community_moderator_view::dsl::*;
|
use super::community_view::community_moderator_view::dsl::*;
|
||||||
community_moderator_view
|
community_moderator_view
|
||||||
.filter(community_id.eq(from_community_id))
|
.filter(community_id.eq(for_community_id))
|
||||||
.order_by(published)
|
.order_by(published)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
|
pub fn for_user(conn: &PgConnection, for_user_id: i32) -> Result<Vec<Self>, Error> {
|
||||||
use super::community_view::community_moderator_view::dsl::*;
|
use super::community_view::community_moderator_view::dsl::*;
|
||||||
community_moderator_view
|
community_moderator_view
|
||||||
.filter(user_id.eq(from_user_id))
|
.filter(user_id.eq(for_user_id))
|
||||||
.order_by(published)
|
.order_by(published)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
DbPool,
|
DbPool,
|
||||||
};
|
};
|
||||||
|
use diesel::PgConnection;
|
||||||
use lemmy_db::{naive_now, Bannable, Crud, Followable, Joinable, SortType};
|
use lemmy_db::{naive_now, Bannable, Crud, Followable, Joinable, SortType};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
generate_actor_keypair,
|
generate_actor_keypair,
|
||||||
|
@ -34,7 +35,7 @@ pub struct GetCommunity {
|
||||||
pub struct GetCommunityResponse {
|
pub struct GetCommunityResponse {
|
||||||
pub community: CommunityView,
|
pub community: CommunityView,
|
||||||
pub moderators: Vec<CommunityModeratorView>,
|
pub moderators: Vec<CommunityModeratorView>,
|
||||||
pub admins: Vec<UserView>,
|
pub admins: Vec<UserView>, // TODO this should be from GetSite, shouldn't need this
|
||||||
pub online: usize,
|
pub online: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +102,21 @@ pub struct EditCommunity {
|
||||||
title: String,
|
title: String,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
category_id: i32,
|
category_id: i32,
|
||||||
removed: Option<bool>,
|
|
||||||
deleted: Option<bool>,
|
|
||||||
nsfw: bool,
|
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>,
|
reason: Option<String>,
|
||||||
expires: Option<i64>,
|
expires: Option<i64>,
|
||||||
auth: String,
|
auth: String,
|
||||||
|
@ -366,24 +379,15 @@ impl Perform for Oper<EditCommunity> {
|
||||||
return Err(APIError::err("site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify its a mod
|
// Verify its a mod (only mods can edit it)
|
||||||
let edit_id = data.edit_id;
|
let edit_id = data.edit_id;
|
||||||
let mut editors: Vec<i32> = Vec::new();
|
let mods: Vec<i32> = blocking(pool, move |conn| {
|
||||||
editors.append(
|
|
||||||
&mut blocking(pool, move |conn| {
|
|
||||||
CommunityModeratorView::for_community(conn, edit_id)
|
CommunityModeratorView::for_community(conn, edit_id)
|
||||||
.map(|v| v.into_iter().map(|m| m.user_id).collect())
|
.map(|v| v.into_iter().map(|m| m.user_id).collect())
|
||||||
})
|
})
|
||||||
.await??,
|
.await??;
|
||||||
);
|
if !mods.contains(&user_id) {
|
||||||
editors.append(
|
return Err(APIError::err("not_a_moderator").into());
|
||||||
&mut blocking(pool, move |conn| {
|
|
||||||
UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
|
|
||||||
})
|
|
||||||
.await??,
|
|
||||||
);
|
|
||||||
if !editors.contains(&user_id) {
|
|
||||||
return Err(APIError::err("no_community_edit_allowed").into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let edit_id = data.edit_id;
|
let edit_id = data.edit_id;
|
||||||
|
@ -395,8 +399,8 @@ impl Perform for Oper<EditCommunity> {
|
||||||
description: data.description.to_owned(),
|
description: data.description.to_owned(),
|
||||||
category_id: data.category_id.to_owned(),
|
category_id: data.category_id.to_owned(),
|
||||||
creator_id: read_community.creator_id,
|
creator_id: read_community.creator_id,
|
||||||
removed: data.removed.to_owned(),
|
removed: Some(read_community.removed),
|
||||||
deleted: data.deleted.to_owned(),
|
deleted: Some(read_community.deleted),
|
||||||
nsfw: data.nsfw,
|
nsfw: data.nsfw,
|
||||||
updated: Some(naive_now()),
|
updated: Some(naive_now()),
|
||||||
actor_id: read_community.actor_id,
|
actor_id: read_community.actor_id,
|
||||||
|
@ -408,7 +412,7 @@ impl Perform for Oper<EditCommunity> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let edit_id = data.edit_id;
|
let edit_id = data.edit_id;
|
||||||
let updated_community = match blocking(pool, move |conn| {
|
match blocking(pool, move |conn| {
|
||||||
Community::update(conn, edit_id, &community_form)
|
Community::update(conn, edit_id, &community_form)
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
|
@ -417,43 +421,8 @@ impl Perform for Oper<EditCommunity> {
|
||||||
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mod tables
|
// TODO there needs to be some kind of an apub update
|
||||||
if let Some(removed) = data.removed.to_owned() {
|
// process for communities and users
|
||||||
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(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(deleted) = data.deleted.to_owned() {
|
|
||||||
if deleted {
|
|
||||||
updated_community
|
|
||||||
.send_delete(&user, &self.client, pool)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_community
|
|
||||||
.send_undo_delete(&user, &self.client, pool)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
} else if let Some(removed) = data.removed.to_owned() {
|
|
||||||
if removed {
|
|
||||||
updated_community
|
|
||||||
.send_remove(&user, &self.client, pool)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_community
|
|
||||||
.send_undo_remove(&user, &self.client, pool)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let edit_id = data.edit_id;
|
let edit_id = data.edit_id;
|
||||||
let community_view = blocking(pool, move |conn| {
|
let community_view = blocking(pool, move |conn| {
|
||||||
|
@ -483,6 +452,186 @@ impl Perform for Oper<EditCommunity> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for Oper<DeleteCommunity> {
|
||||||
|
type Response = CommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
pool: &DbPool,
|
||||||
|
websocket_info: Option<WebsocketInfo>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError> {
|
||||||
|
let data: &DeleteCommunity = &self.data;
|
||||||
|
|
||||||
|
let claims = match Claims::decode(&data.auth) {
|
||||||
|
Ok(claims) => claims.claims,
|
||||||
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_id = claims.id;
|
||||||
|
|
||||||
|
// Check for a site ban
|
||||||
|
let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
|
||||||
|
if user.banned {
|
||||||
|
return Err(APIError::err("site_ban").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify its the creator (only a creator can delete the community)
|
||||||
|
let edit_id = data.edit_id;
|
||||||
|
let read_community = blocking(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(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, &self.client, pool)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_community
|
||||||
|
.send_undo_delete(&user, &self.client, pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let edit_id = data.edit_id;
|
||||||
|
let community_view = blocking(pool, move |conn| {
|
||||||
|
CommunityView::read(conn, edit_id, Some(user_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = CommunityResponse {
|
||||||
|
community: community_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ws) = websocket_info {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
ws.chatserver.do_send(SendCommunityRoomMessage {
|
||||||
|
op: UserOperation::DeleteCommunity,
|
||||||
|
response: res_sent,
|
||||||
|
community_id: data.edit_id,
|
||||||
|
my_id: ws.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for Oper<RemoveCommunity> {
|
||||||
|
type Response = CommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
pool: &DbPool,
|
||||||
|
websocket_info: Option<WebsocketInfo>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError> {
|
||||||
|
let data: &RemoveCommunity = &self.data;
|
||||||
|
|
||||||
|
let claims = match Claims::decode(&data.auth) {
|
||||||
|
Ok(claims) => claims.claims,
|
||||||
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_id = claims.id;
|
||||||
|
|
||||||
|
// Check for a site ban
|
||||||
|
let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
|
||||||
|
if user.banned {
|
||||||
|
return Err(APIError::err("site_ban").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify its an admin (only an admin can remove a community)
|
||||||
|
let admins: Vec<i32> = blocking(pool, move |conn| {
|
||||||
|
UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if !admins.contains(&user_id) {
|
||||||
|
return Err(APIError::err("not_an_admin").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the remove
|
||||||
|
let edit_id = data.edit_id;
|
||||||
|
let removed = data.removed;
|
||||||
|
let updated_community = match blocking(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(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??;
|
||||||
|
|
||||||
|
// Apub messages
|
||||||
|
if removed {
|
||||||
|
updated_community
|
||||||
|
.send_remove(&user, &self.client, pool)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_community
|
||||||
|
.send_undo_remove(&user, &self.client, pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let edit_id = data.edit_id;
|
||||||
|
let community_view = blocking(pool, move |conn| {
|
||||||
|
CommunityView::read(conn, edit_id, Some(user_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = CommunityResponse {
|
||||||
|
community: community_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ws) = websocket_info {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
ws.chatserver.do_send(SendCommunityRoomMessage {
|
||||||
|
op: UserOperation::RemoveCommunity,
|
||||||
|
response: res_sent,
|
||||||
|
community_id: data.edit_id,
|
||||||
|
my_id: ws.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for Oper<ListCommunities> {
|
impl Perform for Oper<ListCommunities> {
|
||||||
type Response = ListCommunitiesResponse;
|
type Response = ListCommunitiesResponse;
|
||||||
|
@ -852,26 +1001,9 @@ impl Perform for Oper<TransferCommunity> {
|
||||||
return Err(APIError::err("not_an_admin").into());
|
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, // This makes the new user the community creator
|
|
||||||
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 community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let update = move |conn: &'_ _| Community::update(conn, community_id, &community_form);
|
let new_creator = data.user_id;
|
||||||
|
let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
|
||||||
if blocking(pool, update).await?.is_err() {
|
if blocking(pool, update).await?.is_err() {
|
||||||
return Err(APIError::err("couldnt_update_community").into());
|
return Err(APIError::err("couldnt_update_community").into());
|
||||||
};
|
};
|
||||||
|
@ -946,3 +1078,16 @@ impl Perform for Oper<TransferCommunity> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn community_mods_and_admins(
|
||||||
|
conn: &PgConnection,
|
||||||
|
community_id: i32,
|
||||||
|
) -> Result<Vec<i32>, LemmyError> {
|
||||||
|
let mut editors: Vec<i32> = Vec::new();
|
||||||
|
editors.append(
|
||||||
|
&mut CommunityModeratorView::for_community(conn, community_id)
|
||||||
|
.map(|v| v.into_iter().map(|m| m.user_id).collect())?,
|
||||||
|
);
|
||||||
|
editors.append(&mut UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())?);
|
||||||
|
Ok(editors)
|
||||||
|
}
|
||||||
|
|
|
@ -53,7 +53,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("", web::put().to(route_post::<EditCommunity>))
|
.route("", web::put().to(route_post::<EditCommunity>))
|
||||||
.route("/list", web::get().to(route_get::<ListCommunities>))
|
.route("/list", web::get().to(route_get::<ListCommunities>))
|
||||||
.route("/follow", web::post().to(route_post::<FollowCommunity>))
|
.route("/follow", web::post().to(route_post::<FollowCommunity>))
|
||||||
|
.route("/delete", web::post().to(route_post::<DeleteCommunity>))
|
||||||
// Mod Actions
|
// Mod Actions
|
||||||
|
.route("/remove", web::post().to(route_post::<RemoveCommunity>))
|
||||||
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
|
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
|
||||||
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
|
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
|
||||||
.route("/mod", web::post().to(route_post::<AddModToCommunity>)),
|
.route("/mod", web::post().to(route_post::<AddModToCommunity>)),
|
||||||
|
|
|
@ -35,6 +35,8 @@ pub enum UserOperation {
|
||||||
EditPost,
|
EditPost,
|
||||||
SavePost,
|
SavePost,
|
||||||
EditCommunity,
|
EditCommunity,
|
||||||
|
DeleteCommunity,
|
||||||
|
RemoveCommunity,
|
||||||
FollowCommunity,
|
FollowCommunity,
|
||||||
GetFollowedCommunities,
|
GetFollowedCommunities,
|
||||||
GetUserDetails,
|
GetUserDetails,
|
||||||
|
|
|
@ -212,6 +212,9 @@ impl ChatServer {
|
||||||
|
|
||||||
// Also leave all communities
|
// Also leave all communities
|
||||||
// This avoids double messages
|
// This avoids double messages
|
||||||
|
// TODO found a bug, whereby community messages like
|
||||||
|
// delete and remove aren't sent, because
|
||||||
|
// you left the community room
|
||||||
for sessions in self.community_rooms.values_mut() {
|
for sessions in self.community_rooms.values_mut() {
|
||||||
sessions.remove(&id);
|
sessions.remove(&id);
|
||||||
}
|
}
|
||||||
|
@ -483,6 +486,8 @@ impl ChatServer {
|
||||||
UserOperation::ListCommunities => do_user_operation::<ListCommunities>(args).await,
|
UserOperation::ListCommunities => do_user_operation::<ListCommunities>(args).await,
|
||||||
UserOperation::CreateCommunity => do_user_operation::<CreateCommunity>(args).await,
|
UserOperation::CreateCommunity => do_user_operation::<CreateCommunity>(args).await,
|
||||||
UserOperation::EditCommunity => do_user_operation::<EditCommunity>(args).await,
|
UserOperation::EditCommunity => do_user_operation::<EditCommunity>(args).await,
|
||||||
|
UserOperation::DeleteCommunity => do_user_operation::<DeleteCommunity>(args).await,
|
||||||
|
UserOperation::RemoveCommunity => do_user_operation::<RemoveCommunity>(args).await,
|
||||||
UserOperation::FollowCommunity => do_user_operation::<FollowCommunity>(args).await,
|
UserOperation::FollowCommunity => do_user_operation::<FollowCommunity>(args).await,
|
||||||
UserOperation::GetFollowedCommunities => {
|
UserOperation::GetFollowedCommunities => {
|
||||||
do_user_operation::<GetFollowedCommunities>(args).await
|
do_user_operation::<GetFollowedCommunities>(args).await
|
||||||
|
|
46
ui/src/api_tests/api.spec.ts
vendored
46
ui/src/api_tests/api.spec.ts
vendored
|
@ -13,6 +13,8 @@ import {
|
||||||
CommentForm,
|
CommentForm,
|
||||||
CommentResponse,
|
CommentResponse,
|
||||||
CommunityForm,
|
CommunityForm,
|
||||||
|
DeleteCommunityForm,
|
||||||
|
RemoveCommunityForm,
|
||||||
GetCommunityResponse,
|
GetCommunityResponse,
|
||||||
CommentLikeForm,
|
CommentLikeForm,
|
||||||
CreatePostLikeForm,
|
CreatePostLikeForm,
|
||||||
|
@ -731,20 +733,16 @@ describe('main', () => {
|
||||||
expect(getPostResAgainTwo.post.deleted).toBe(false);
|
expect(getPostResAgainTwo.post.deleted).toBe(false);
|
||||||
|
|
||||||
// lemmy_beta deletes the community
|
// lemmy_beta deletes the community
|
||||||
let deleteCommunityForm: CommunityForm = {
|
let deleteCommunityForm: DeleteCommunityForm = {
|
||||||
name: communityName,
|
|
||||||
title: communityName,
|
|
||||||
category_id: 1,
|
|
||||||
edit_id: createCommunityRes.community.id,
|
edit_id: createCommunityRes.community.id,
|
||||||
nsfw: false,
|
|
||||||
deleted: true,
|
deleted: true,
|
||||||
auth: lemmyBetaAuth,
|
auth: lemmyBetaAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
let deleteResponse: CommunityResponse = await fetch(
|
let deleteResponse: CommunityResponse = await fetch(
|
||||||
`${lemmyBetaApiUrl}/community`,
|
`${lemmyBetaApiUrl}/community/delete`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
@ -764,20 +762,16 @@ describe('main', () => {
|
||||||
expect(getCommunityRes.community.deleted).toBe(true);
|
expect(getCommunityRes.community.deleted).toBe(true);
|
||||||
|
|
||||||
// lemmy_beta undeletes the community
|
// lemmy_beta undeletes the community
|
||||||
let undeleteCommunityForm: CommunityForm = {
|
let undeleteCommunityForm: DeleteCommunityForm = {
|
||||||
name: communityName,
|
|
||||||
title: communityName,
|
|
||||||
category_id: 1,
|
|
||||||
edit_id: createCommunityRes.community.id,
|
edit_id: createCommunityRes.community.id,
|
||||||
nsfw: false,
|
|
||||||
deleted: false,
|
deleted: false,
|
||||||
auth: lemmyBetaAuth,
|
auth: lemmyBetaAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
let undeleteCommunityRes: CommunityResponse = await fetch(
|
let undeleteCommunityRes: CommunityResponse = await fetch(
|
||||||
`${lemmyBetaApiUrl}/community`,
|
`${lemmyBetaApiUrl}/community/delete`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
@ -1006,21 +1000,17 @@ describe('main', () => {
|
||||||
}).then(d => d.json());
|
}).then(d => d.json());
|
||||||
expect(getPostResAgainTwo.post.removed).toBe(false);
|
expect(getPostResAgainTwo.post.removed).toBe(false);
|
||||||
|
|
||||||
// lemmy_beta deletes the community
|
// lemmy_beta removes the community
|
||||||
let removeCommunityForm: CommunityForm = {
|
let removeCommunityForm: RemoveCommunityForm = {
|
||||||
name: communityName,
|
|
||||||
title: communityName,
|
|
||||||
category_id: 1,
|
|
||||||
edit_id: createCommunityRes.community.id,
|
edit_id: createCommunityRes.community.id,
|
||||||
nsfw: false,
|
|
||||||
removed: true,
|
removed: true,
|
||||||
auth: lemmyBetaAuth,
|
auth: lemmyBetaAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
let removeCommunityRes: CommunityResponse = await fetch(
|
let removeCommunityRes: CommunityResponse = await fetch(
|
||||||
`${lemmyBetaApiUrl}/community`,
|
`${lemmyBetaApiUrl}/community/remove`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
@ -1028,7 +1018,7 @@ describe('main', () => {
|
||||||
}
|
}
|
||||||
).then(d => d.json());
|
).then(d => d.json());
|
||||||
|
|
||||||
// Make sure the delete went through
|
// Make sure the remove went through
|
||||||
expect(removeCommunityRes.community.removed).toBe(true);
|
expect(removeCommunityRes.community.removed).toBe(true);
|
||||||
|
|
||||||
// Re-get it from alpha, make sure its removed there too
|
// Re-get it from alpha, make sure its removed there too
|
||||||
|
@ -1040,20 +1030,16 @@ describe('main', () => {
|
||||||
expect(getCommunityRes.community.removed).toBe(true);
|
expect(getCommunityRes.community.removed).toBe(true);
|
||||||
|
|
||||||
// lemmy_beta unremoves the community
|
// lemmy_beta unremoves the community
|
||||||
let unremoveCommunityForm: CommunityForm = {
|
let unremoveCommunityForm: RemoveCommunityForm = {
|
||||||
name: communityName,
|
|
||||||
title: communityName,
|
|
||||||
category_id: 1,
|
|
||||||
edit_id: createCommunityRes.community.id,
|
edit_id: createCommunityRes.community.id,
|
||||||
nsfw: false,
|
|
||||||
removed: false,
|
removed: false,
|
||||||
auth: lemmyBetaAuth,
|
auth: lemmyBetaAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
let unremoveCommunityRes: CommunityResponse = await fetch(
|
let unremoveCommunityRes: CommunityResponse = await fetch(
|
||||||
`${lemmyBetaApiUrl}/community`,
|
`${lemmyBetaApiUrl}/community/remove`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
|
6
ui/src/components/community.tsx
vendored
6
ui/src/components/community.tsx
vendored
|
@ -360,7 +360,11 @@ export class Community extends Component<any, State> {
|
||||||
document.title = `/c/${this.state.community.name} - ${this.state.site.name}`;
|
document.title = `/c/${this.state.community.name} - ${this.state.site.name}`;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
} else if (res.op == UserOperation.EditCommunity) {
|
} else if (
|
||||||
|
res.op == UserOperation.EditCommunity ||
|
||||||
|
res.op == UserOperation.DeleteCommunity ||
|
||||||
|
res.op == UserOperation.RemoveCommunity
|
||||||
|
) {
|
||||||
let data = res.data as CommunityResponse;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.community = data.community;
|
this.state.community = data.community;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
|
6
ui/src/components/post.tsx
vendored
6
ui/src/components/post.tsx
vendored
|
@ -462,7 +462,11 @@ export class Post extends Component<any, PostState> {
|
||||||
this.state.post = data.post;
|
this.state.post = data.post;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
setupTippy();
|
setupTippy();
|
||||||
} else if (res.op == UserOperation.EditCommunity) {
|
} else if (
|
||||||
|
res.op == UserOperation.EditCommunity ||
|
||||||
|
res.op == UserOperation.DeleteCommunity ||
|
||||||
|
res.op == UserOperation.RemoveCommunity
|
||||||
|
) {
|
||||||
let data = res.data as CommunityResponse;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.community = data.community;
|
this.state.community = data.community;
|
||||||
this.state.post.community_id = data.community.id;
|
this.state.post.community_id = data.community.id;
|
||||||
|
|
21
ui/src/components/sidebar.tsx
vendored
21
ui/src/components/sidebar.tsx
vendored
|
@ -4,7 +4,8 @@ import {
|
||||||
Community,
|
Community,
|
||||||
CommunityUser,
|
CommunityUser,
|
||||||
FollowCommunityForm,
|
FollowCommunityForm,
|
||||||
CommunityForm as CommunityFormI,
|
DeleteCommunityForm,
|
||||||
|
RemoveCommunityForm,
|
||||||
UserView,
|
UserView,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
|
@ -284,16 +285,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
handleDeleteClick(i: Sidebar) {
|
handleDeleteClick(i: Sidebar) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let deleteForm: CommunityFormI = {
|
let deleteForm: DeleteCommunityForm = {
|
||||||
name: i.props.community.name,
|
|
||||||
title: i.props.community.title,
|
|
||||||
category_id: i.props.community.category_id,
|
|
||||||
edit_id: i.props.community.id,
|
edit_id: i.props.community.id,
|
||||||
deleted: !i.props.community.deleted,
|
deleted: !i.props.community.deleted,
|
||||||
nsfw: i.props.community.nsfw,
|
|
||||||
auth: null,
|
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.editCommunity(deleteForm);
|
WebSocketService.Instance.deleteCommunity(deleteForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUnsubscribe(communityId: number) {
|
handleUnsubscribe(communityId: number) {
|
||||||
|
@ -350,18 +346,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
handleModRemoveSubmit(i: Sidebar) {
|
handleModRemoveSubmit(i: Sidebar) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let deleteForm: CommunityFormI = {
|
let removeForm: RemoveCommunityForm = {
|
||||||
name: i.props.community.name,
|
|
||||||
title: i.props.community.title,
|
|
||||||
category_id: i.props.community.category_id,
|
|
||||||
edit_id: i.props.community.id,
|
edit_id: i.props.community.id,
|
||||||
removed: !i.props.community.removed,
|
removed: !i.props.community.removed,
|
||||||
reason: i.state.removeReason,
|
reason: i.state.removeReason,
|
||||||
expires: getUnixTime(i.state.removeExpires),
|
expires: getUnixTime(i.state.removeExpires),
|
||||||
nsfw: i.props.community.nsfw,
|
|
||||||
auth: null,
|
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.editCommunity(deleteForm);
|
WebSocketService.Instance.removeCommunity(removeForm);
|
||||||
|
|
||||||
i.state.showRemoveDialog = false;
|
i.state.showRemoveDialog = false;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
|
20
ui/src/interfaces.ts
vendored
20
ui/src/interfaces.ts
vendored
|
@ -16,6 +16,8 @@ export enum UserOperation {
|
||||||
EditPost,
|
EditPost,
|
||||||
SavePost,
|
SavePost,
|
||||||
EditCommunity,
|
EditCommunity,
|
||||||
|
DeleteCommunity,
|
||||||
|
RemoveCommunity,
|
||||||
FollowCommunity,
|
FollowCommunity,
|
||||||
GetFollowedCommunities,
|
GetFollowedCommunities,
|
||||||
GetUserDetails,
|
GetUserDetails,
|
||||||
|
@ -573,13 +575,23 @@ export interface UserSettingsForm {
|
||||||
|
|
||||||
export interface CommunityForm {
|
export interface CommunityForm {
|
||||||
name: string;
|
name: string;
|
||||||
|
edit_id?: number;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
category_id: number;
|
category_id: number;
|
||||||
edit_id?: number;
|
|
||||||
removed?: boolean;
|
|
||||||
deleted?: boolean;
|
|
||||||
nsfw: boolean;
|
nsfw: boolean;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteCommunityForm {
|
||||||
|
edit_id: number;
|
||||||
|
deleted: boolean;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveCommunityForm {
|
||||||
|
edit_id: number;
|
||||||
|
removed: boolean;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
expires?: number;
|
expires?: number;
|
||||||
auth?: string;
|
auth?: string;
|
||||||
|
@ -879,6 +891,8 @@ export type MessageType =
|
||||||
| LoginForm
|
| LoginForm
|
||||||
| RegisterForm
|
| RegisterForm
|
||||||
| CommunityForm
|
| CommunityForm
|
||||||
|
| DeleteCommunityForm
|
||||||
|
| RemoveCommunityForm
|
||||||
| FollowCommunityForm
|
| FollowCommunityForm
|
||||||
| ListCommunitiesForm
|
| ListCommunitiesForm
|
||||||
| GetFollowedCommunitiesForm
|
| GetFollowedCommunitiesForm
|
||||||
|
|
28
ui/src/services/WebSocketService.ts
vendored
28
ui/src/services/WebSocketService.ts
vendored
|
@ -4,6 +4,8 @@ import {
|
||||||
RegisterForm,
|
RegisterForm,
|
||||||
UserOperation,
|
UserOperation,
|
||||||
CommunityForm,
|
CommunityForm,
|
||||||
|
DeleteCommunityForm,
|
||||||
|
RemoveCommunityForm,
|
||||||
PostForm,
|
PostForm,
|
||||||
SavePostForm,
|
SavePostForm,
|
||||||
CommentForm,
|
CommentForm,
|
||||||
|
@ -105,18 +107,24 @@ export class WebSocketService {
|
||||||
this.ws.send(this.wsSendWrapper(UserOperation.Register, registerForm));
|
this.ws.send(this.wsSendWrapper(UserOperation.Register, registerForm));
|
||||||
}
|
}
|
||||||
|
|
||||||
public createCommunity(communityForm: CommunityForm) {
|
public createCommunity(form: CommunityForm) {
|
||||||
this.setAuth(communityForm);
|
this.setAuth(form);
|
||||||
this.ws.send(
|
this.ws.send(this.wsSendWrapper(UserOperation.CreateCommunity, form));
|
||||||
this.wsSendWrapper(UserOperation.CreateCommunity, communityForm)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public editCommunity(communityForm: CommunityForm) {
|
public editCommunity(form: CommunityForm) {
|
||||||
this.setAuth(communityForm);
|
this.setAuth(form);
|
||||||
this.ws.send(
|
this.ws.send(this.wsSendWrapper(UserOperation.EditCommunity, form));
|
||||||
this.wsSendWrapper(UserOperation.EditCommunity, communityForm)
|
}
|
||||||
);
|
|
||||||
|
public deleteCommunity(form: DeleteCommunityForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.ws.send(this.wsSendWrapper(UserOperation.DeleteCommunity, form));
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeCommunity(form: RemoveCommunityForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.ws.send(this.wsSendWrapper(UserOperation.RemoveCommunity, form));
|
||||||
}
|
}
|
||||||
|
|
||||||
public followCommunity(followCommunityForm: FollowCommunityForm) {
|
public followCommunity(followCommunityForm: FollowCommunityForm) {
|
||||||
|
|
1
ui/translations/en.json
vendored
1
ui/translations/en.json
vendored
|
@ -254,6 +254,7 @@
|
||||||
"couldnt_save_post": "Couldn't save post.",
|
"couldnt_save_post": "Couldn't save post.",
|
||||||
"no_slurs": "No slurs.",
|
"no_slurs": "No slurs.",
|
||||||
"not_an_admin": "Not an admin.",
|
"not_an_admin": "Not an admin.",
|
||||||
|
"not_a_moderator": "Not a moderator.",
|
||||||
"site_already_exists": "Site already exists.",
|
"site_already_exists": "Site already exists.",
|
||||||
"couldnt_update_site": "Couldn't update site.",
|
"couldnt_update_site": "Couldn't update site.",
|
||||||
"couldnt_find_that_username_or_email":
|
"couldnt_find_that_username_or_email":
|
||||||
|
|
Loading…
Reference in a new issue