};
use lemmy_db::{
comment_view::*,
+ community::*,
community_view::*,
moderator::*,
naive_now,
#[derive(Serialize, Deserialize)]
pub struct EditPost {
pub edit_id: i32,
- creator_id: i32,
- community_id: i32,
name: String,
url: Option<String>,
body: Option<String>,
- removed: Option<bool>,
- deleted: Option<bool>,
nsfw: bool,
- locked: Option<bool>,
- stickied: Option<bool>,
+ 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<String>,
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,
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<i32> = vec![read_post.creator_id];
- let mut moderators: Vec<i32> = 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? {
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;
}
};
- 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??;
- }
+ // Send apub update
+ updated_post.send_update(&user, &self.client, pool).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??;
- }
+ let edit_id = data.edit_id;
+ let post_view = blocking(pool, move |conn| {
+ PostView::read(conn, edit_id, Some(user_id))
+ })
+ .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??;
- }
+ let res = PostResponse { post: post_view };
+
+ if let Some(ws) = websocket_info {
+ ws.chatserver.do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res.clone(),
+ my_id: ws.id,
+ });
}
- 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?;
- }
- }
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for Oper<DeletePost> {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ pool: &DbPool,
+ websocket_info: Option<WebsocketInfo>,
+ ) -> Result<PostResponse, LemmyError> {
+ 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_update(&user, &self.client, pool).await?;
+ 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))
if let Some(ws) = websocket_info {
ws.chatserver.do_send(SendPost {
- op: UserOperation::EditPost,
+ op: UserOperation::DeletePost,
+ post: res.clone(),
+ my_id: ws.id,
+ });
+ }
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for Oper<RemovePost> {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ pool: &DbPool,
+ websocket_info: Option<WebsocketInfo>,
+ ) -> Result<PostResponse, LemmyError> {
+ 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<LockPost> {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ pool: &DbPool,
+ websocket_info: Option<WebsocketInfo>,
+ ) -> Result<PostResponse, LemmyError> {
+ 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<StickyPost> {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ pool: &DbPool,
+ websocket_info: Option<WebsocketInfo>,
+ ) -> Result<PostResponse, LemmyError> {
+ 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,
});
LoginForm,
LoginResponse,
PostForm,
+ DeletePostForm,
+ RemovePostForm,
+ // TODO need to test LockPost and StickyPost federated
PostResponse,
SearchResponse,
FollowCommunityForm,
name,
auth: lemmyAlphaAuth,
community_id: 2,
- creator_id: 2,
nsfw: false,
};
name,
auth: lemmyAlphaAuth,
community_id: 3,
- creator_id: 2,
nsfw: false,
};
edit_id: 2,
auth: lemmyAlphaAuth,
community_id: 3,
- creator_id: 2,
nsfw: false,
};
name: postName,
auth: lemmyBetaAuth,
community_id: createCommunityRes.community.id,
- creator_id: 2,
nsfw: false,
};
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
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',
},
name: postName,
auth: lemmyBetaAuth,
community_id: createCommunityRes.community.id,
- creator_id: 2,
nsfw: false,
};
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
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',
},
name: postName,
auth: lemmyAlphaAuth,
community_id: 2,
- creator_id: 2,
nsfw: false,
};
name: betaPostName,
auth: lemmyBetaAuth,
community_id: 2,
- creator_id: 2,
nsfw: false,
};