diff --git a/docs/src/contributing_websocket_http_api.md b/docs/src/contributing_websocket_http_api.md index 390fa9887..00e26b0d3 100644 --- a/docs/src/contributing_websocket_http_api.md +++ b/docs/src/contributing_websocket_http_api.md @@ -1271,8 +1271,9 @@ Only admins can remove a community. name: String, url: Option, body: Option, + nsfw: bool, community_id: i32, - auth: String + auth: String, } } ``` @@ -1378,25 +1379,17 @@ Post listing types are `All, Subscribed, Community` `POST /post/like` #### Edit Post - -Mods and admins can remove and lock a post, creators can delete it. - ##### Request ```rust { op: "EditPost", data: { edit_id: i32, - creator_id: i32, - community_id: i32, name: String, url: Option, body: Option, - removed: Option, - deleted: Option, - locked: Option, - reason: Option, - auth: String + nsfw: bool, + auth: String, } } ``` @@ -1414,6 +1407,120 @@ Mods and admins can remove and lock a post, creators can delete it. `PUT /post` +#### Delete Post +##### Request +```rust +{ + op: "DeletePost", + data: { + edit_id: i32, + deleted: bool, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "DeletePost", + data: { + post: PostView + } +} +``` + +##### HTTP + +`POST /post/delete` + +#### Remove Post + +Only admins and mods can remove a post. + +##### Request +```rust +{ + op: "RemovePost", + data: { + edit_id: i32, + removed: bool, + reason: Option, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "RemovePost", + data: { + post: PostView + } +} +``` + +##### HTTP + +`POST /post/remove` + +#### Lock Post + +Only admins and mods can lock a post. + +##### Request +```rust +{ + op: "LockPost", + data: { + edit_id: i32, + locked: bool, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "LockPost", + data: { + post: PostView + } +} +``` + +##### HTTP + +`POST /post/lock` + +#### Sticky Post + +Only admins and mods can sticky a post. + +##### Request +```rust +{ + op: "StickyPost", + data: { + edit_id: i32, + stickied: bool, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "StickyPost", + data: { + post: PostView + } +} +``` + +##### HTTP + +`POST /post/sticky` + #### Save Post ##### Request ```rust diff --git a/server/lemmy_db/src/post.rs b/server/lemmy_db/src/post.rs index 66e24773a..35b0feada 100644 --- a/server/lemmy_db/src/post.rs +++ b/server/lemmy_db/src/post.rs @@ -108,6 +108,46 @@ impl Post { )) .get_result::(conn) } + + pub fn update_deleted( + conn: &PgConnection, + post_id: i32, + new_deleted: bool, + ) -> Result { + use crate::schema::post::dsl::*; + diesel::update(post.find(post_id)) + .set(deleted.eq(new_deleted)) + .get_result::(conn) + } + + pub fn update_removed( + conn: &PgConnection, + post_id: i32, + new_removed: bool, + ) -> Result { + use crate::schema::post::dsl::*; + diesel::update(post.find(post_id)) + .set(removed.eq(new_removed)) + .get_result::(conn) + } + + pub fn update_locked(conn: &PgConnection, post_id: i32, new_locked: bool) -> Result { + use crate::schema::post::dsl::*; + diesel::update(post.find(post_id)) + .set(locked.eq(new_locked)) + .get_result::(conn) + } + + pub fn update_stickied( + conn: &PgConnection, + post_id: i32, + new_stickied: bool, + ) -> Result { + use crate::schema::post::dsl::*; + diesel::update(post.find(post_id)) + .set(stickied.eq(new_stickied)) + .get_result::(conn) + } } impl Crud for Post { diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 1a06032b2..79b7b2c79 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -162,6 +162,11 @@ impl Perform for Oper { return Err(APIError::err("site_ban").into()); } + // Check if post is locked, no new comments + if post.locked { + return Err(APIError::err("locked").into()); + } + // Create the comment let comment_form2 = comment_form.clone(); let inserted_comment = diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 61f3513b6..390d291cf 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -13,6 +13,7 @@ use crate::{ }; use lemmy_db::{ comment_view::*, + community::*, community_view::*, moderator::*, naive_now, @@ -96,20 +97,42 @@ pub struct CreatePostLike { #[derive(Serialize, Deserialize)] pub struct EditPost { pub edit_id: i32, - creator_id: i32, - community_id: i32, name: String, url: Option, body: Option, - removed: Option, - deleted: Option, nsfw: bool, - locked: Option, - stickied: Option, + auth: String, +} + +#[derive(Serialize, Deserialize)] +pub struct DeletePost { + pub edit_id: i32, + deleted: bool, + auth: String, +} + +#[derive(Serialize, Deserialize)] +pub struct RemovePost { + pub edit_id: i32, + removed: bool, reason: Option, auth: String, } +#[derive(Serialize, Deserialize)] +pub struct LockPost { + pub edit_id: i32, + locked: bool, + auth: String, +} + +#[derive(Serialize, Deserialize)] +pub struct StickyPost { + pub edit_id: i32, + stickied: bool, + auth: String, +} + #[derive(Serialize, Deserialize)] pub struct SavePost { post_id: i32, @@ -549,35 +572,10 @@ impl Perform for Oper { let user_id = claims.id; let edit_id = data.edit_id; - let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; - - // Verify its the creator or a mod or admin - let community_id = read_post.community_id; - let mut editors: Vec = vec![read_post.creator_id]; - let mut moderators: Vec = vec![]; - - moderators.append( - &mut blocking(pool, move |conn| { - CommunityModeratorView::for_community(conn, community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - moderators.append( - &mut blocking(pool, move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - - editors.extend(&moderators); - - if !editors.contains(&user_id) { - return Err(APIError::err("no_post_edit_allowed").into()); - } + let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; // Check for a community ban - let community_id = read_post.community_id; + let community_id = orig_post.community_id; let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); if blocking(pool, is_banned).await? { @@ -590,55 +588,34 @@ impl Perform for Oper { return Err(APIError::err("site_ban").into()); } + // Verify that only the creator can edit + if user_id != orig_post.creator_id { + return Err(APIError::err("no_post_edit_allowed").into()); + } + // Fetch Iframely and Pictrs cached image let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await; - let post_form = { - // only modify some properties if they are a moderator - if moderators.contains(&user_id) { - PostForm { - name: data.name.trim().to_owned(), - url: data.url.to_owned(), - body: data.body.to_owned(), - creator_id: read_post.creator_id.to_owned(), - community_id: read_post.community_id, - removed: data.removed.to_owned(), - deleted: data.deleted.to_owned(), - nsfw: data.nsfw, - locked: data.locked.to_owned(), - stickied: data.stickied.to_owned(), - updated: Some(naive_now()), - embed_title: iframely_title, - embed_description: iframely_description, - embed_html: iframely_html, - thumbnail_url: pictrs_thumbnail, - ap_id: read_post.ap_id, - local: read_post.local, - published: None, - } - } else { - PostForm { - name: read_post.name.trim().to_owned(), - url: data.url.to_owned(), - body: data.body.to_owned(), - creator_id: read_post.creator_id.to_owned(), - community_id: read_post.community_id, - removed: Some(read_post.removed), - deleted: data.deleted.to_owned(), - nsfw: data.nsfw, - locked: Some(read_post.locked), - stickied: Some(read_post.stickied), - updated: Some(naive_now()), - embed_title: iframely_title, - embed_description: iframely_description, - embed_html: iframely_html, - thumbnail_url: pictrs_thumbnail, - ap_id: read_post.ap_id, - local: read_post.local, - published: None, - } - } + let post_form = PostForm { + name: data.name.trim().to_owned(), + url: data.url.to_owned(), + body: data.body.to_owned(), + nsfw: data.nsfw, + creator_id: orig_post.creator_id.to_owned(), + community_id: orig_post.community_id, + removed: Some(orig_post.removed), + deleted: Some(orig_post.deleted), + locked: Some(orig_post.locked), + stickied: Some(orig_post.stickied), + updated: Some(naive_now()), + embed_title: iframely_title, + embed_description: iframely_description, + embed_html: iframely_html, + thumbnail_url: pictrs_thumbnail, + ap_id: orig_post.ap_id, + local: orig_post.local, + published: None, }; let edit_id = data.edit_id; @@ -656,58 +633,8 @@ impl Perform for Oper { } }; - if moderators.contains(&user_id) { - // Mod tables - if let Some(removed) = data.removed.to_owned() { - let form = ModRemovePostForm { - mod_user_id: user_id, - post_id: data.edit_id, - removed: Some(removed), - reason: data.reason.to_owned(), - }; - blocking(pool, move |conn| ModRemovePost::create(conn, &form)).await??; - } - - if let Some(locked) = data.locked.to_owned() { - let form = ModLockPostForm { - mod_user_id: user_id, - post_id: data.edit_id, - locked: Some(locked), - }; - blocking(pool, move |conn| ModLockPost::create(conn, &form)).await??; - } - - if let Some(stickied) = data.stickied.to_owned() { - let form = ModStickyPostForm { - mod_user_id: user_id, - post_id: data.edit_id, - stickied: Some(stickied), - }; - blocking(pool, move |conn| ModStickyPost::create(conn, &form)).await??; - } - } - - if let Some(deleted) = data.deleted.to_owned() { - if deleted { - updated_post.send_delete(&user, &self.client, pool).await?; - } else { - updated_post - .send_undo_delete(&user, &self.client, pool) - .await?; - } - } else if let Some(removed) = data.removed.to_owned() { - if moderators.contains(&user_id) { - if removed { - updated_post.send_remove(&user, &self.client, pool).await?; - } else { - updated_post - .send_undo_remove(&user, &self.client, pool) - .await?; - } - } - } else { - updated_post.send_update(&user, &self.client, pool).await?; - } + // Send apub update + updated_post.send_update(&user, &self.client, pool).await?; let edit_id = data.edit_id; let post_view = blocking(pool, move |conn| { @@ -729,6 +656,342 @@ impl Perform for Oper { } } +#[async_trait::async_trait(?Send)] +impl Perform for Oper { + type Response = PostResponse; + + async fn perform( + &self, + pool: &DbPool, + websocket_info: Option, + ) -> Result { + let data: &DeletePost = &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; + + let edit_id = data.edit_id; + let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; + + // 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()); + } + + // Check for a community ban + let community_id = orig_post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Verify that only the creator can delete + if user_id != orig_post.creator_id { + return Err(APIError::err("no_post_edit_allowed").into()); + } + + // Update the post + let edit_id = data.edit_id; + let deleted = data.deleted; + let updated_post = blocking(pool, move |conn| { + Post::update_deleted(conn, edit_id, deleted) + }) + .await??; + + // apub updates + if deleted { + updated_post.send_delete(&user, &self.client, pool).await?; + } else { + updated_post + .send_undo_delete(&user, &self.client, pool) + .await?; + } + + // Refetch the post + let edit_id = data.edit_id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, edit_id, Some(user_id)) + }) + .await??; + + let res = PostResponse { post: post_view }; + + if let Some(ws) = websocket_info { + ws.chatserver.do_send(SendPost { + op: UserOperation::DeletePost, + post: res.clone(), + my_id: ws.id, + }); + } + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for Oper { + type Response = PostResponse; + + async fn perform( + &self, + pool: &DbPool, + websocket_info: Option, + ) -> Result { + let data: &RemovePost = &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; + + let edit_id = data.edit_id; + let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; + + // 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()); + } + + // Check for a community ban + let community_id = orig_post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Verify that only the mods can remove + let mods_and_admins = blocking(pool, move |conn| { + Community::community_mods_and_admins(conn, community_id) + }) + .await??; + if !mods_and_admins.contains(&user_id) { + return Err(APIError::err("not_an_admin").into()); + } + + // Update the post + let edit_id = data.edit_id; + let removed = data.removed; + let updated_post = blocking(pool, move |conn| { + Post::update_removed(conn, edit_id, removed) + }) + .await??; + + // Mod tables + let form = ModRemovePostForm { + mod_user_id: user_id, + post_id: data.edit_id, + removed: Some(removed), + reason: data.reason.to_owned(), + }; + blocking(pool, move |conn| ModRemovePost::create(conn, &form)).await??; + + // apub updates + if removed { + updated_post.send_remove(&user, &self.client, pool).await?; + } else { + updated_post + .send_undo_remove(&user, &self.client, pool) + .await?; + } + + // Refetch the post + let edit_id = data.edit_id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, edit_id, Some(user_id)) + }) + .await??; + + let res = PostResponse { post: post_view }; + + if let Some(ws) = websocket_info { + ws.chatserver.do_send(SendPost { + op: UserOperation::RemovePost, + post: res.clone(), + my_id: ws.id, + }); + } + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for Oper { + type Response = PostResponse; + + async fn perform( + &self, + pool: &DbPool, + websocket_info: Option, + ) -> Result { + let data: &LockPost = &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; + + let edit_id = data.edit_id; + let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; + + // 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()); + } + + // Check for a community ban + let community_id = orig_post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Verify that only the mods can lock + let mods_and_admins = blocking(pool, move |conn| { + Community::community_mods_and_admins(conn, community_id) + }) + .await??; + if !mods_and_admins.contains(&user_id) { + return Err(APIError::err("not_an_admin").into()); + } + + // Update the post + let edit_id = data.edit_id; + let locked = data.locked; + let updated_post = + blocking(pool, move |conn| Post::update_locked(conn, edit_id, locked)).await??; + + // Mod tables + let form = ModLockPostForm { + mod_user_id: user_id, + post_id: data.edit_id, + locked: Some(locked), + }; + blocking(pool, move |conn| ModLockPost::create(conn, &form)).await??; + + // apub updates + updated_post.send_update(&user, &self.client, pool).await?; + + // Refetch the post + let edit_id = data.edit_id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, edit_id, Some(user_id)) + }) + .await??; + + let res = PostResponse { post: post_view }; + + if let Some(ws) = websocket_info { + ws.chatserver.do_send(SendPost { + op: UserOperation::LockPost, + post: res.clone(), + my_id: ws.id, + }); + } + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for Oper { + type Response = PostResponse; + + async fn perform( + &self, + pool: &DbPool, + websocket_info: Option, + ) -> Result { + let data: &StickyPost = &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; + + let edit_id = data.edit_id; + let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; + + // 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()); + } + + // Check for a community ban + let community_id = orig_post.community_id; + let is_banned = + move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + return Err(APIError::err("community_ban").into()); + } + + // Verify that only the mods can sticky + let mods_and_admins = blocking(pool, move |conn| { + Community::community_mods_and_admins(conn, community_id) + }) + .await??; + if !mods_and_admins.contains(&user_id) { + return Err(APIError::err("not_an_admin").into()); + } + + // Update the post + let edit_id = data.edit_id; + let stickied = data.stickied; + let updated_post = blocking(pool, move |conn| { + Post::update_stickied(conn, edit_id, stickied) + }) + .await??; + + // Mod tables + let form = ModStickyPostForm { + mod_user_id: user_id, + post_id: data.edit_id, + stickied: Some(stickied), + }; + blocking(pool, move |conn| ModStickyPost::create(conn, &form)).await??; + + // Apub updates + // TODO stickied should pry work like locked for ease of use + updated_post.send_update(&user, &self.client, pool).await?; + + // Refetch the post + let edit_id = data.edit_id; + let post_view = blocking(pool, move |conn| { + PostView::read(conn, edit_id, Some(user_id)) + }) + .await??; + + let res = PostResponse { post: post_view }; + + if let Some(ws) = websocket_info { + ws.chatserver.do_send(SendPost { + op: UserOperation::StickyPost, + post: res.clone(), + my_id: ws.id, + }); + } + + Ok(res) + } +} + #[async_trait::async_trait(?Send)] impl Perform for Oper { type Response = PostResponse; diff --git a/server/src/routes/api.rs b/server/src/routes/api.rs index 9fc84f4c3..31888156e 100644 --- a/server/src/routes/api.rs +++ b/server/src/routes/api.rs @@ -73,6 +73,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .wrap(rate_limit.message()) .route("", web::get().to(route_get::)) .route("", web::put().to(route_post::)) + .route("/delete", web::post().to(route_post::)) + .route("/remove", web::post().to(route_post::)) + .route("/lock", web::post().to(route_post::)) + .route("/sticky", web::post().to(route_post::)) .route("/list", web::get().to(route_get::)) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)), diff --git a/server/src/websocket/mod.rs b/server/src/websocket/mod.rs index ed8ee272a..5f3157b1b 100644 --- a/server/src/websocket/mod.rs +++ b/server/src/websocket/mod.rs @@ -36,6 +36,10 @@ pub enum UserOperation { GetPosts, CreatePostLike, EditPost, + DeletePost, + RemovePost, + LockPost, + StickyPost, SavePost, EditCommunity, DeleteCommunity, diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 6f0516ffd..cc6de1150 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -500,6 +500,10 @@ impl ChatServer { UserOperation::GetPost => do_user_operation::(args).await, UserOperation::GetPosts => do_user_operation::(args).await, UserOperation::EditPost => do_user_operation::(args).await, + UserOperation::DeletePost => do_user_operation::(args).await, + UserOperation::RemovePost => do_user_operation::(args).await, + UserOperation::LockPost => do_user_operation::(args).await, + UserOperation::StickyPost => do_user_operation::(args).await, UserOperation::CreatePostLike => do_user_operation::(args).await, UserOperation::SavePost => do_user_operation::(args).await, diff --git a/ui/src/api_tests/api.spec.ts b/ui/src/api_tests/api.spec.ts index f3cc86736..9ab9fc2ae 100644 --- a/ui/src/api_tests/api.spec.ts +++ b/ui/src/api_tests/api.spec.ts @@ -4,6 +4,9 @@ import { LoginForm, LoginResponse, PostForm, + DeletePostForm, + RemovePostForm, + // TODO need to test LockPost and StickyPost federated PostResponse, SearchResponse, FollowCommunityForm, @@ -100,7 +103,6 @@ describe('main', () => { name, auth: lemmyAlphaAuth, community_id: 2, - creator_id: 2, nsfw: false, }; @@ -269,7 +271,6 @@ describe('main', () => { name, auth: lemmyAlphaAuth, community_id: 3, - creator_id: 2, nsfw: false, }; @@ -326,7 +327,6 @@ describe('main', () => { edit_id: 2, auth: lemmyAlphaAuth, community_id: 3, - creator_id: 2, nsfw: false, }; @@ -587,7 +587,6 @@ describe('main', () => { name: postName, auth: lemmyBetaAuth, community_id: createCommunityRes.community.id, - creator_id: 2, nsfw: false, }; @@ -673,23 +672,22 @@ describe('main', () => { expect(getPostUndeleteRes.comments[0].deleted).toBe(false); // lemmy_beta deletes the post - let deletePostForm: PostForm = { - name: postName, + let deletePostForm: DeletePostForm = { edit_id: createPostRes.post.id, - auth: lemmyBetaAuth, - community_id: createPostRes.post.community_id, - creator_id: createPostRes.post.creator_id, - nsfw: false, deleted: true, + auth: lemmyBetaAuth, }; - let deletePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(deletePostForm), - }).then(d => d.json()); + let deletePostRes: PostResponse = await fetch( + `${lemmyBetaApiUrl}/post/delete`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: wrapper(deletePostForm), + } + ).then(d => d.json()); expect(deletePostRes.post.deleted).toBe(true); // Make sure lemmy_alpha sees the post is deleted @@ -699,20 +697,16 @@ describe('main', () => { expect(getPostResAgain.post.deleted).toBe(true); // lemmy_beta undeletes the post - let undeletePostForm: PostForm = { - name: postName, + let undeletePostForm: DeletePostForm = { edit_id: createPostRes.post.id, - auth: lemmyBetaAuth, - community_id: createPostRes.post.community_id, - creator_id: createPostRes.post.creator_id, - nsfw: false, deleted: false, + auth: lemmyBetaAuth, }; let undeletePostRes: PostResponse = await fetch( - `${lemmyBetaApiUrl}/post`, + `${lemmyBetaApiUrl}/post/delete`, { - method: 'PUT', + method: 'POST', headers: { 'Content-Type': 'application/json', }, @@ -849,7 +843,6 @@ describe('main', () => { name: postName, auth: lemmyBetaAuth, community_id: createCommunityRes.community.id, - creator_id: 2, nsfw: false, }; @@ -935,23 +928,22 @@ describe('main', () => { expect(getPostUnremoveRes.comments[0].removed).toBe(false); // lemmy_beta deletes the post - let removePostForm: PostForm = { - name: postName, + let removePostForm: RemovePostForm = { edit_id: createPostRes.post.id, - auth: lemmyBetaAuth, - community_id: createPostRes.post.community_id, - creator_id: createPostRes.post.creator_id, - nsfw: false, removed: true, + auth: lemmyBetaAuth, }; - let removePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: wrapper(removePostForm), - }).then(d => d.json()); + let removePostRes: PostResponse = await fetch( + `${lemmyBetaApiUrl}/post/remove`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: wrapper(removePostForm), + } + ).then(d => d.json()); expect(removePostRes.post.removed).toBe(true); // Make sure lemmy_alpha sees the post is deleted @@ -961,20 +953,16 @@ describe('main', () => { expect(getPostResAgain.post.removed).toBe(true); // lemmy_beta unremoves the post - let unremovePostForm: PostForm = { - name: postName, + let unremovePostForm: RemovePostForm = { edit_id: createPostRes.post.id, - auth: lemmyBetaAuth, - community_id: createPostRes.post.community_id, - creator_id: createPostRes.post.creator_id, - nsfw: false, removed: false, + auth: lemmyBetaAuth, }; let unremovePostRes: PostResponse = await fetch( - `${lemmyBetaApiUrl}/post`, + `${lemmyBetaApiUrl}/post/remove`, { - method: 'PUT', + method: 'POST', headers: { 'Content-Type': 'application/json', }, @@ -1226,7 +1214,6 @@ describe('main', () => { name: postName, auth: lemmyAlphaAuth, community_id: 2, - creator_id: 2, nsfw: false, }; @@ -1337,7 +1324,6 @@ describe('main', () => { name: betaPostName, auth: lemmyBetaAuth, community_id: 2, - creator_id: 2, nsfw: false, }; diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 66eaf96e6..f70fa4f7a 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -380,7 +380,13 @@ export class Community extends Component { this.state.loading = false; this.setState(this.state); setupTippy(); - } else if (res.op == UserOperation.EditPost) { + } else if ( + res.op == UserOperation.EditPost || + res.op == UserOperation.DeletePost || + res.op == UserOperation.RemovePost || + res.op == UserOperation.LockPost || + res.op == UserOperation.StickyPost + ) { let data = res.data as PostResponse; editPostFindRes(data, this.state.posts); this.setState(this.state); diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 6656ef842..854cff6e9 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -71,9 +71,6 @@ export class PostForm extends Component { nsfw: false, auth: null, community_id: null, - creator_id: UserService.Instance.user - ? UserService.Instance.user.id - : null, }, communities: [], loading: false, @@ -99,7 +96,6 @@ export class PostForm extends Component { name: this.props.post.name, community_id: this.props.post.community_id, edit_id: this.props.post.id, - creator_id: this.props.post.creator_id, url: this.props.post.url, nsfw: this.props.post.nsfw, auth: null, diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index a47aba992..e117a282e 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -4,7 +4,10 @@ import { WebSocketService, UserService } from '../services'; import { Post, CreatePostLikeForm, - PostForm as PostFormI, + DeletePostForm, + RemovePostForm, + LockPostForm, + StickyPostForm, SavePostForm, CommunityUser, UserView, @@ -33,7 +36,6 @@ import { setupTippy, hostname, previewLines, - toast, } from '../utils'; import { i18n } from '../i18next'; @@ -1114,18 +1116,12 @@ export class PostListing extends Component { } handleDeleteClick(i: PostListing) { - let deleteForm: PostFormI = { - body: i.props.post.body, - community_id: i.props.post.community_id, - name: i.props.post.name, - url: i.props.post.url, + let deleteForm: DeletePostForm = { edit_id: i.props.post.id, - creator_id: i.props.post.creator_id, deleted: !i.props.post.deleted, - nsfw: i.props.post.nsfw, auth: null, }; - WebSocketService.Instance.editPost(deleteForm); + WebSocketService.Instance.deletePost(deleteForm); } handleSavePostClick(i: PostListing) { @@ -1163,46 +1159,34 @@ export class PostListing extends Component { handleModRemoveSubmit(i: PostListing) { event.preventDefault(); - let form: PostFormI = { - name: i.props.post.name, - community_id: i.props.post.community_id, + let form: RemovePostForm = { edit_id: i.props.post.id, - creator_id: i.props.post.creator_id, removed: !i.props.post.removed, reason: i.state.removeReason, - nsfw: i.props.post.nsfw, auth: null, }; - WebSocketService.Instance.editPost(form); + WebSocketService.Instance.removePost(form); i.state.showRemoveDialog = false; i.setState(i.state); } handleModLock(i: PostListing) { - let form: PostFormI = { - name: i.props.post.name, - community_id: i.props.post.community_id, + let form: LockPostForm = { edit_id: i.props.post.id, - creator_id: i.props.post.creator_id, - nsfw: i.props.post.nsfw, locked: !i.props.post.locked, auth: null, }; - WebSocketService.Instance.editPost(form); + WebSocketService.Instance.lockPost(form); } handleModSticky(i: PostListing) { - let form: PostFormI = { - name: i.props.post.name, - community_id: i.props.post.community_id, + let form: StickyPostForm = { edit_id: i.props.post.id, - creator_id: i.props.post.creator_id, - nsfw: i.props.post.nsfw, stickied: !i.props.post.stickied, auth: null, }; - WebSocketService.Instance.editPost(form); + WebSocketService.Instance.stickyPost(form); } handleModBanFromCommunityShow(i: PostListing) { diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 91ffeb19f..c811062f5 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -452,7 +452,13 @@ export class Post extends Component { let data = res.data as PostResponse; createPostLikeRes(data, this.state.post); this.setState(this.state); - } else if (res.op == UserOperation.EditPost) { + } else if ( + res.op == UserOperation.EditPost || + res.op == UserOperation.DeletePost || + res.op == UserOperation.RemovePost || + res.op == UserOperation.LockPost || + res.op == UserOperation.StickyPost + ) { let data = res.data as PostResponse; this.state.post = data.post; this.setState(this.state); diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 6006f2ee4..8dced1a61 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -17,6 +17,10 @@ export enum UserOperation { GetPosts, CreatePostLike, EditPost, + DeletePost, + RemovePost, + LockPost, + StickyPost, SavePost, EditCommunity, DeleteCommunity, @@ -636,19 +640,37 @@ export interface PostForm { name: string; url?: string; body?: string; - community_id: number; - updated?: number; + community_id?: number; edit_id?: number; - creator_id: number; - removed?: boolean; - deleted?: boolean; nsfw: boolean; - locked?: boolean; - stickied?: boolean; + auth: string; +} + +export interface DeletePostForm { + edit_id: number; + deleted: boolean; + auth: string; +} + +export interface RemovePostForm { + edit_id: number; + removed: boolean; reason?: string; auth: string; } +export interface LockPostForm { + edit_id: number; + locked: boolean; + auth: string; +} + +export interface StickyPostForm { + edit_id: number; + stickied: boolean; + auth: string; +} + export interface PostFormParams { name: string; url?: string; @@ -914,6 +936,10 @@ export type MessageType = | ListCommunitiesForm | GetFollowedCommunitiesForm | PostForm + | DeletePostForm + | RemovePostForm + | LockPostForm + | StickyPostForm | GetPostForm | GetPostsForm | GetCommunityForm diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index 2c85425d8..aabfc4dd5 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -7,6 +7,10 @@ import { DeleteCommunityForm, RemoveCommunityForm, PostForm, + DeletePostForm, + RemovePostForm, + LockPostForm, + StickyPostForm, SavePostForm, CommentForm, DeleteCommentForm, @@ -153,9 +157,9 @@ export class WebSocketService { this.ws.send(this.wsSendWrapper(UserOperation.ListCategories, {})); } - public createPost(postForm: PostForm) { - this.setAuth(postForm); - this.ws.send(this.wsSendWrapper(UserOperation.CreatePost, postForm)); + public createPost(form: PostForm) { + this.setAuth(form); + this.ws.send(this.wsSendWrapper(UserOperation.CreatePost, form)); } public getPost(form: GetPostForm) { @@ -218,9 +222,29 @@ export class WebSocketService { this.ws.send(this.wsSendWrapper(UserOperation.CreatePostLike, form)); } - public editPost(postForm: PostForm) { - this.setAuth(postForm); - this.ws.send(this.wsSendWrapper(UserOperation.EditPost, postForm)); + public editPost(form: PostForm) { + this.setAuth(form); + this.ws.send(this.wsSendWrapper(UserOperation.EditPost, form)); + } + + public deletePost(form: DeletePostForm) { + this.setAuth(form); + this.ws.send(this.wsSendWrapper(UserOperation.DeletePost, form)); + } + + public removePost(form: RemovePostForm) { + this.setAuth(form); + this.ws.send(this.wsSendWrapper(UserOperation.RemovePost, form)); + } + + public lockPost(form: LockPostForm) { + this.setAuth(form); + this.ws.send(this.wsSendWrapper(UserOperation.LockPost, form)); + } + + public stickyPost(form: StickyPostForm) { + this.setAuth(form); + this.ws.send(this.wsSendWrapper(UserOperation.StickyPost, form)); } public savePost(form: SavePostForm) {