From 61fc436b581d12ad1057ee1f49c52bac6e175400 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sat, 4 May 2019 22:20:38 -0700 Subject: [PATCH] Mostly done with reorg. --- server/src/api/comment.rs | 316 ++++ server/src/api/community.rs | 559 ++++++ server/src/api/mod.rs | 60 + server/src/api/post.rs | 469 +++++ server/src/api/site.rs | 336 ++++ server/src/api/user.rs | 503 ++++++ server/src/db/category.rs | 13 +- server/src/db/comment.rs | 6 +- server/src/db/comment_view.rs | 7 +- server/src/db/community.rs | 6 +- server/src/db/community_view.rs | 6 +- server/src/db/mod.rs | 72 + server/src/db/moderator.rs | 6 +- server/src/db/moderator_views.rs | 6 +- server/src/db/post.rs | 6 +- server/src/db/post_view.rs | 7 +- server/src/db/user.rs | 7 +- server/src/db/user_view.rs | 5 +- server/src/lib.rs | 83 +- server/src/websocket/server.rs | 2812 +++--------------------------- 20 files changed, 2535 insertions(+), 2750 deletions(-) create mode 100644 server/src/api/comment.rs create mode 100644 server/src/api/community.rs create mode 100644 server/src/api/mod.rs create mode 100644 server/src/api/post.rs create mode 100644 server/src/api/site.rs create mode 100644 server/src/api/user.rs diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs new file mode 100644 index 000000000..36a44b363 --- /dev/null +++ b/server/src/api/comment.rs @@ -0,0 +1,316 @@ +use super::*; + +#[derive(Serialize, Deserialize)] +pub struct CreateComment { + content: String, + parent_id: Option, + edit_id: Option, + pub post_id: i32, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct EditComment { + content: String, + parent_id: Option, + edit_id: i32, + creator_id: i32, + pub post_id: i32, + removed: Option, + deleted: Option, + reason: Option, + read: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct SaveComment { + comment_id: i32, + save: bool, + auth: String +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct CommentResponse { + op: String, + pub comment: CommentView +} + +#[derive(Serialize, Deserialize)] +pub struct CreateCommentLike { + comment_id: i32, + pub post_id: i32, + score: i16, + auth: String +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: CreateComment = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Check for a community ban + let post = Post::read(&conn, data.post_id)?; + if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + return Err(APIError::err(self.op, "You have been banned from this community"))? + } + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + let content_slurs_removed = remove_slurs(&data.content.to_owned()); + + let comment_form = CommentForm { + content: content_slurs_removed, + parent_id: data.parent_id.to_owned(), + post_id: data.post_id, + creator_id: user_id, + removed: None, + deleted: None, + read: None, + updated: None + }; + + let inserted_comment = match Comment::create(&conn, &comment_form) { + Ok(comment) => comment, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't create Comment"))? + } + }; + + // You like your own comment by default + let like_form = CommentLikeForm { + comment_id: inserted_comment.id, + post_id: data.post_id, + user_id: user_id, + score: 1 + }; + + let _inserted_like = match CommentLike::like(&conn, &like_form) { + Ok(like) => like, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't like comment."))? + } + }; + + let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?; + + Ok( + CommentResponse { + op: self.op.to_string(), + comment: comment_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: EditComment = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let orig_comment = CommentView::read(&conn, data.edit_id, None)?; + + // You are allowed to mark the comment as read even if you're banned. + if data.read.is_none() { + + // Verify its the creator or a mod, or an admin + let mut editors: Vec = vec![data.creator_id]; + editors.append( + &mut CommunityModeratorView::for_community(&conn, orig_comment.community_id) + ? + .into_iter() + .map(|m| m.user_id) + .collect() + ); + editors.append( + &mut UserView::admins(&conn) + ? + .into_iter() + .map(|a| a.id) + .collect() + ); + + if !editors.contains(&user_id) { + return Err(APIError::err(self.op, "Not allowed to edit comment."))? + } + + // Check for a community ban + if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { + return Err(APIError::err(self.op, "You have been banned from this community"))? + } + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + } + + let content_slurs_removed = remove_slurs(&data.content.to_owned()); + + let comment_form = CommentForm { + content: content_slurs_removed, + parent_id: data.parent_id, + post_id: data.post_id, + creator_id: data.creator_id, + removed: data.removed.to_owned(), + deleted: data.deleted.to_owned(), + read: data.read.to_owned(), + updated: if data.read.is_some() { orig_comment.updated } else {Some(naive_now())} + }; + + let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { + Ok(comment) => comment, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update Comment"))? + } + }; + + // Mod tables + if let Some(removed) = data.removed.to_owned() { + let form = ModRemoveCommentForm { + mod_user_id: user_id, + comment_id: data.edit_id, + removed: Some(removed), + reason: data.reason.to_owned(), + }; + ModRemoveComment::create(&conn, &form)?; + } + + + let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?; + + Ok( + CommentResponse { + op: self.op.to_string(), + comment: comment_view + } + ) + + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: SaveComment = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let comment_saved_form = CommentSavedForm { + comment_id: data.comment_id, + user_id: user_id, + }; + + if data.save { + match CommentSaved::save(&conn, &comment_saved_form) { + Ok(comment) => comment, + Err(_e) => { + return Err(APIError::err(self.op, "Couldnt do comment save"))? + } + }; + } else { + match CommentSaved::unsave(&conn, &comment_saved_form) { + Ok(comment) => comment, + Err(_e) => { + return Err(APIError::err(self.op, "Couldnt do comment save"))? + } + }; + } + + let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?; + + Ok( + CommentResponse { + op: self.op.to_string(), + comment: comment_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: CreateCommentLike = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Check for a community ban + let post = Post::read(&conn, data.post_id)?; + if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + return Err(APIError::err(self.op, "You have been banned from this community"))? + } + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + let like_form = CommentLikeForm { + comment_id: data.comment_id, + post_id: data.post_id, + user_id: user_id, + score: data.score + }; + + // Remove any likes first + CommentLike::remove(&conn, &like_form)?; + + // Only add the like if the score isnt 0 + if &like_form.score != &0 { + let _inserted_like = match CommentLike::like(&conn, &like_form) { + Ok(like) => like, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't like comment."))? + } + }; + } + + // Have to refetch the comment to get the current state + let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?; + + Ok( + CommentResponse { + op: self.op.to_string(), + comment: liked_comment + } + ) + } +} diff --git a/server/src/api/community.rs b/server/src/api/community.rs new file mode 100644 index 000000000..5059b17f0 --- /dev/null +++ b/server/src/api/community.rs @@ -0,0 +1,559 @@ +use super::*; +use std::str::FromStr; + +#[derive(Serialize, Deserialize)] +pub struct GetCommunity { + id: Option, + name: Option, + auth: Option +} + +#[derive(Serialize, Deserialize)] +pub struct GetCommunityResponse { + op: String, + community: CommunityView, + moderators: Vec, + admins: Vec, +} + + +#[derive(Serialize, Deserialize)] +pub struct CreateCommunity { + name: String, + title: String, + description: Option, + category_id: i32 , + auth: String +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct CommunityResponse { + op: String, + pub community: CommunityView +} + +#[derive(Serialize, Deserialize)] +pub struct ListCommunities { + sort: String, + page: Option, + limit: Option, + auth: Option +} + +#[derive(Serialize, Deserialize)] +pub struct ListCommunitiesResponse { + op: String, + communities: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct BanFromCommunity { + pub community_id: i32, + user_id: i32, + ban: bool, + reason: Option, + expires: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct BanFromCommunityResponse { + op: String, + user: UserView, + banned: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct AddModToCommunity { + pub community_id: i32, + user_id: i32, + added: bool, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct AddModToCommunityResponse { + op: String, + moderators: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct EditCommunity { + pub edit_id: i32, + name: String, + title: String, + description: Option, + category_id: i32, + removed: Option, + deleted: Option, + reason: Option, + expires: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct FollowCommunity { + community_id: i32, + follow: bool, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct GetFollowedCommunities { + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct GetFollowedCommunitiesResponse { + op: String, + communities: Vec +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetCommunity = self.data; + let conn = establish_connection(); + + let user_id: Option = match &data.auth { + Some(auth) => { + match Claims::decode(&auth) { + Ok(claims) => { + let user_id = claims.claims.id; + Some(user_id) + } + Err(_e) => None + } + } + None => None + }; + + let community_id = match data.id { + Some(id) => id, + None => Community::read_from_name(&conn, data.name.to_owned().unwrap_or("main".to_string()))?.id + }; + + let community_view = match CommunityView::read(&conn, community_id, user_id) { + Ok(community) => community, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't find Community"))? + } + }; + + let moderators = match CommunityModeratorView::for_community(&conn, community_id) { + Ok(moderators) => moderators, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't find Community"))? + } + }; + + let admins = UserView::admins(&conn)?; + + // Return the jwt + Ok( + GetCommunityResponse { + op: self.op.to_string(), + community: community_view, + moderators: moderators, + admins: admins, + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: CreateCommunity = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + if has_slurs(&data.name) || + has_slurs(&data.title) || + (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) { + return Err(APIError::err(self.op, "No slurs"))? + } + + let user_id = claims.id; + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + // When you create a community, make sure the user becomes a moderator and a follower + 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, + updated: None, + }; + + let inserted_community = match Community::create(&conn, &community_form) { + Ok(community) => community, + Err(_e) => { + return Err(APIError::err(self.op, "Community already exists."))? + } + }; + + let community_moderator_form = CommunityModeratorForm { + community_id: inserted_community.id, + user_id: user_id + }; + + let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community moderator already exists."))? + } + }; + + let community_follower_form = CommunityFollowerForm { + community_id: inserted_community.id, + user_id: user_id + }; + + let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community follower already exists."))? + } + }; + + let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?; + + Ok( + CommunityResponse { + op: self.op.to_string(), + community: community_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: EditCommunity = self.data; + + if has_slurs(&data.name) || has_slurs(&data.title) { + return Err(APIError::err(self.op, "No slurs"))? + } + + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + // Verify its a mod + let mut editors: Vec = 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() + ); + if !editors.contains(&user_id) { + return Err(APIError::err(self.op, "Not allowed to edit community"))? + } + + 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(), + updated: Some(naive_now()) + }; + + let _updated_community = match Community::update(&conn, data.edit_id, &community_form) { + Ok(community) => community, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update Community"))? + } + }; + + // Mod tables + if let Some(removed) = data.removed.to_owned() { + 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: expires + }; + ModRemoveCommunity::create(&conn, &form)?; + } + + let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?; + + Ok( + CommunityResponse { + op: self.op.to_string(), + community: community_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: ListCommunities = self.data; + let conn = establish_connection(); + + let user_id: Option = match &data.auth { + Some(auth) => { + match Claims::decode(&auth) { + Ok(claims) => { + let user_id = claims.claims.id; + Some(user_id) + } + Err(_e) => None + } + } + None => None + }; + + let sort = SortType::from_str(&data.sort)?; + + let communities: Vec = CommunityView::list(&conn, user_id, sort, data.page, data.limit)?; + + // Return the jwt + Ok( + ListCommunitiesResponse { + op: self.op.to_string(), + communities: communities + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: FollowCommunity = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let community_follower_form = CommunityFollowerForm { + community_id: data.community_id, + user_id: user_id + }; + + if data.follow { + match CommunityFollower::follow(&conn, &community_follower_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community follower already exists."))? + } + }; + } else { + match CommunityFollower::ignore(&conn, &community_follower_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community follower already exists."))? + } + }; + } + + let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?; + + Ok( + CommunityResponse { + op: self.op.to_string(), + community: community_view + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetFollowedCommunities = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let communities: Vec = match CommunityFollowerView::for_user(&conn, user_id) { + Ok(communities) => communities, + Err(_e) => { + return Err(APIError::err(self.op, "System error, try logging out and back in."))? + } + }; + + // Return the jwt + Ok( + GetFollowedCommunitiesResponse { + op: self.op.to_string(), + communities: communities + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: BanFromCommunity = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + 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, + Err(_e) => { + return Err(APIError::err(self.op, "Community user ban already exists"))? + } + }; + } else { + match CommunityUserBan::unban(&conn, &community_user_ban_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community user ban already exists"))? + } + }; + } + + // Mod tables + let expires = match data.expires { + Some(time) => Some(naive_from_unix(time)), + None => None + }; + + let form = ModBanFromCommunityForm { + mod_user_id: user_id, + other_user_id: data.user_id, + community_id: data.community_id, + reason: data.reason.to_owned(), + banned: Some(data.ban), + expires: expires, + }; + ModBanFromCommunity::create(&conn, &form)?; + + let user_view = UserView::read(&conn, data.user_id)?; + + Ok( + BanFromCommunityResponse { + op: self.op.to_string(), + user: user_view, + banned: data.ban + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: AddModToCommunity = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let community_moderator_form = CommunityModeratorForm { + community_id: data.community_id, + user_id: data.user_id + }; + + if data.added { + match CommunityModerator::join(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community moderator already exists."))? + } + }; + } else { + match CommunityModerator::leave(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community moderator already exists."))? + } + }; + } + + // 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)?; + + Ok( + AddModToCommunityResponse { + op: self.op.to_string(), + moderators: moderators, + } + ) + } +} diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs new file mode 100644 index 000000000..4bf6f7fee --- /dev/null +++ b/server/src/api/mod.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; +use failure::Error; +use db::*; +use db::community::*; +use db::user::*; +use db::post::*; +use db::comment::*; +use db::post_view::*; +use db::comment_view::*; +use db::category::*; +use db::community_view::*; +use db::user_view::*; +use db::moderator_views::*; +use db::moderator::*; +use {has_slurs, remove_slurs, Settings, naive_now, naive_from_unix}; + +pub mod user; +pub mod community; +pub mod post; +pub mod comment; +pub mod site; + +#[derive(EnumString,ToString,Debug)] +pub enum UserOperation { + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead +} + +#[derive(Fail, Debug)] +#[fail(display = "{{\"op\":\"{}\", \"error\":\"{}\"}}", op, message)] +pub struct APIError { + op: String, + message: String, +} + +impl APIError { + pub fn err(op: UserOperation, msg: &str) -> Self { + APIError { + op: op.to_string(), + message: msg.to_string(), + } + } +} + +pub struct Oper { + op: UserOperation, + data: T +} + +impl Oper { + pub fn new(op: UserOperation, data: T) -> Oper { + Oper { + op: op, + data: data + } + } +} + +pub trait Perform { + fn perform(&self) -> Result where T: Sized; +} diff --git a/server/src/api/post.rs b/server/src/api/post.rs new file mode 100644 index 000000000..e7bec69e8 --- /dev/null +++ b/server/src/api/post.rs @@ -0,0 +1,469 @@ +use super::*; +use std::str::FromStr; + +#[derive(Serialize, Deserialize)] +pub struct CreatePost { + name: String, + url: Option, + body: Option, + community_id: i32, + auth: String +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct PostResponse { + op: String, + pub post: PostView +} + + +#[derive(Serialize, Deserialize)] +pub struct GetPost { + pub id: i32, + auth: Option +} + +#[derive(Serialize, Deserialize)] +pub struct GetPostResponse { + op: String, + post: PostView, + comments: Vec, + community: CommunityView, + moderators: Vec, + admins: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct GetPosts { + type_: String, + sort: String, + page: Option, + limit: Option, + community_id: Option, + auth: Option +} + +#[derive(Serialize, Deserialize)] +pub struct GetPostsResponse { + op: String, + posts: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct CreatePostLike { + post_id: i32, + score: i16, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct CreatePostLikeResponse { + op: String, + post: PostView +} + + +#[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, + locked: Option, + reason: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct SavePost { + post_id: i32, + save: bool, + auth: String +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: CreatePost = self.data; + let conn = establish_connection(); + + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + if has_slurs(&data.name) || + (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { + return Err(APIError::err(self.op, "No slurs"))? + } + + let user_id = claims.id; + + // Check for a community ban + if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { + return Err(APIError::err(self.op, "You have been banned from this community"))? + } + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + let post_form = PostForm { + name: data.name.to_owned(), + url: data.url.to_owned(), + body: data.body.to_owned(), + community_id: data.community_id, + creator_id: user_id, + removed: None, + deleted: None, + locked: None, + updated: None + }; + + let inserted_post = match Post::create(&conn, &post_form) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't create Post"))? + } + }; + + // They like their own post by default + let like_form = PostLikeForm { + post_id: inserted_post.id, + user_id: user_id, + score: 1 + }; + + // Only add the like if the score isnt 0 + let _inserted_like = match PostLike::like(&conn, &like_form) { + Ok(like) => like, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't like post."))? + } + }; + + // Refetch the view + let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't find Post"))? + } + }; + + Ok( + PostResponse { + op: self.op.to_string(), + post: post_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetPost = self.data; + let conn = establish_connection(); + + let user_id: Option = match &data.auth { + Some(auth) => { + match Claims::decode(&auth) { + Ok(claims) => { + let user_id = claims.claims.id; + Some(user_id) + } + Err(_e) => None + } + } + None => None + }; + + let post_view = match PostView::read(&conn, data.id, user_id) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't find Post"))? + } + }; + + let comments = CommentView::list(&conn, &SortType::New, Some(data.id), None, None, user_id, false, None, Some(9999))?; + + let community = CommunityView::read(&conn, post_view.community_id, user_id)?; + + let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?; + + let admins = UserView::admins(&conn)?; + + // Return the jwt + Ok( + GetPostResponse { + op: self.op.to_string(), + post: post_view, + comments: comments, + community: community, + moderators: moderators, + admins: admins, + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetPosts = self.data; + let conn = establish_connection(); + + let user_id: Option = match &data.auth { + Some(auth) => { + match Claims::decode(&auth) { + Ok(claims) => { + let user_id = claims.claims.id; + Some(user_id) + } + Err(_e) => None + } + } + None => None + }; + + let type_ = PostListingType::from_str(&data.type_)?; + let sort = SortType::from_str(&data.sort)?; + + let posts = match PostView::list(&conn, + type_, + &sort, + data.community_id, + None, + None, + user_id, + false, + false, + data.page, + data.limit) { + Ok(posts) => posts, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't get posts"))? + } + }; + + // Return the jwt + Ok( + GetPostsResponse { + op: self.op.to_string(), + posts: posts + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: CreatePostLike = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Check for a community ban + let post = Post::read(&conn, data.post_id)?; + if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + return Err(APIError::err(self.op, "You have been banned from this community"))? + } + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + let like_form = PostLikeForm { + post_id: data.post_id, + user_id: user_id, + score: data.score + }; + + // Remove any likes first + PostLike::remove(&conn, &like_form)?; + + // Only add the like if the score isnt 0 + if &like_form.score != &0 { + let _inserted_like = match PostLike::like(&conn, &like_form) { + Ok(like) => like, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't like post."))? + } + }; + } + + let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't find Post"))? + } + }; + + // just output the score + Ok( + CreatePostLikeResponse { + op: self.op.to_string(), + post: post_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: EditPost = self.data; + if has_slurs(&data.name) || + (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { + return Err(APIError::err(self.op, "No slurs"))? + } + + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Verify its the creator or a mod or admin + let mut editors: Vec = vec![data.creator_id]; + editors.append( + &mut CommunityModeratorView::for_community(&conn, data.community_id) + ? + .into_iter() + .map(|m| m.user_id) + .collect() + ); + editors.append( + &mut UserView::admins(&conn) + ? + .into_iter() + .map(|a| a.id) + .collect() + ); + if !editors.contains(&user_id) { + return Err(APIError::err(self.op, "Not allowed to edit post."))? + } + + // Check for a community ban + if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { + return Err(APIError::err(self.op, "You have been banned from this community"))? + } + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { + return Err(APIError::err(self.op, "You have been banned from the site"))? + } + + let post_form = PostForm { + name: data.name.to_owned(), + url: data.url.to_owned(), + body: data.body.to_owned(), + creator_id: data.creator_id.to_owned(), + community_id: data.community_id, + removed: data.removed.to_owned(), + deleted: data.deleted.to_owned(), + locked: data.locked.to_owned(), + updated: Some(naive_now()) + }; + + let _updated_post = match Post::update(&conn, data.edit_id, &post_form) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update Post"))? + } + }; + + // 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(), + }; + ModRemovePost::create(&conn, &form)?; + } + + if let Some(locked) = data.locked.to_owned() { + let form = ModLockPostForm { + mod_user_id: user_id, + post_id: data.edit_id, + locked: Some(locked), + }; + ModLockPost::create(&conn, &form)?; + } + + let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; + + Ok( + PostResponse { + op: self.op.to_string(), + post: post_view + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: SavePost = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let post_saved_form = PostSavedForm { + post_id: data.post_id, + user_id: user_id, + }; + + if data.save { + match PostSaved::save(&conn, &post_saved_form) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldnt do post save"))? + } + }; + } else { + match PostSaved::unsave(&conn, &post_saved_form) { + Ok(post) => post, + Err(_e) => { + return Err(APIError::err(self.op, "Couldnt do post save"))? + } + }; + } + + let post_view = PostView::read(&conn, data.post_id, Some(user_id))?; + + Ok( + PostResponse { + op: self.op.to_string(), + post: post_view + } + ) + } +} diff --git a/server/src/api/site.rs b/server/src/api/site.rs new file mode 100644 index 000000000..3140788da --- /dev/null +++ b/server/src/api/site.rs @@ -0,0 +1,336 @@ +use super::*; +use std::str::FromStr; + +#[derive(Serialize, Deserialize)] +pub struct ListCategories; + +#[derive(Serialize, Deserialize)] +pub struct ListCategoriesResponse { + op: String, + categories: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct Search { + q: String, + type_: String, + community_id: Option, + sort: String, + page: Option, + limit: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct SearchResponse { + op: String, + comments: Vec, + posts: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct GetModlog { + mod_user_id: Option, + community_id: Option, + page: Option, + limit: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct GetModlogResponse { + op: String, + removed_posts: Vec, + locked_posts: Vec, + removed_comments: Vec, + removed_communities: Vec, + banned_from_community: Vec, + banned: Vec, + added_to_community: Vec, + added: Vec, +} + + +#[derive(Serialize, Deserialize)] +pub struct CreateSite { + name: String, + description: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct EditSite { + name: String, + description: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct GetSite { +} + +#[derive(Serialize, Deserialize)] +pub struct SiteResponse { + op: String, + site: SiteView, +} + +#[derive(Serialize, Deserialize)] +pub struct GetSiteResponse { + op: String, + site: Option, + admins: Vec, + banned: Vec, +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: ListCategories = self.data; + let conn = establish_connection(); + + let categories: Vec = Category::list_all(&conn)?; + + // Return the jwt + Ok( + ListCategoriesResponse { + op: self.op.to_string(), + categories: categories + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetModlog = self.data; + let conn = establish_connection(); + + let removed_posts = ModRemovePostView::list(&conn, data.community_id, data.mod_user_id, data.page, data.limit)?; + let locked_posts = ModLockPostView::list(&conn, data.community_id, data.mod_user_id, data.page, data.limit)?; + let removed_comments = ModRemoveCommentView::list(&conn, data.community_id, data.mod_user_id, data.page, data.limit)?; + let banned_from_community = ModBanFromCommunityView::list(&conn, data.community_id, data.mod_user_id, data.page, data.limit)?; + let added_to_community = ModAddCommunityView::list(&conn, data.community_id, data.mod_user_id, data.page, data.limit)?; + + // These arrays are only for the full modlog, when a community isn't given + let mut removed_communities = Vec::new(); + let mut banned = Vec::new(); + let mut added = Vec::new(); + + if data.community_id.is_none() { + removed_communities = ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?; + banned = ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?; + added = ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?; + } + + // Return the jwt + Ok( + GetModlogResponse { + op: self.op.to_string(), + removed_posts: removed_posts, + locked_posts: locked_posts, + removed_comments: removed_comments, + removed_communities: removed_communities, + banned_from_community: banned_from_community, + banned: banned, + added_to_community: added_to_community, + added: added, + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: CreateSite = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + if has_slurs(&data.name) || + (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) { + return Err(APIError::err(self.op, "No slurs"))? + } + + let user_id = claims.id; + + // Make sure user is an admin + if !UserView::read(&conn, user_id)?.admin { + return Err(APIError::err(self.op, "Not an admin."))? + } + + let site_form = SiteForm { + name: data.name.to_owned(), + description: data.description.to_owned(), + creator_id: user_id, + updated: None, + }; + + match Site::create(&conn, &site_form) { + Ok(site) => site, + Err(_e) => { + return Err(APIError::err(self.op, "Site exists already"))? + } + }; + + let site_view = SiteView::read(&conn)?; + + Ok( + SiteResponse { + op: self.op.to_string(), + site: site_view, + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: EditSite = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + if has_slurs(&data.name) || + (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) { + return Err(APIError::err(self.op, "No slurs"))? + } + + let user_id = claims.id; + + // Make sure user is an admin + if UserView::read(&conn, user_id)?.admin == false { + return Err(APIError::err(self.op, "Not an admin."))? + } + + let found_site = Site::read(&conn, 1)?; + + let site_form = SiteForm { + name: data.name.to_owned(), + description: data.description.to_owned(), + creator_id: found_site.creator_id, + updated: Some(naive_now()), + }; + + match Site::update(&conn, 1, &site_form) { + Ok(site) => site, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update site."))? + } + }; + + let site_view = SiteView::read(&conn)?; + + Ok( + SiteResponse { + op: self.op.to_string(), + site: site_view, + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetSite = self.data; + let conn = establish_connection(); + + // It can return a null site in order to redirect + let site_view = match Site::read(&conn, 1) { + Ok(_site) => Some(SiteView::read(&conn)?), + Err(_e) => None + }; + + let admins = UserView::admins(&conn)?; + let banned = UserView::banned(&conn)?; + + Ok( + GetSiteResponse { + op: self.op.to_string(), + site: site_view, + admins: admins, + banned: banned, + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: Search = self.data; + let conn = establish_connection(); + + let sort = SortType::from_str(&data.sort)?; + let type_ = SearchType::from_str(&data.type_)?; + + let mut posts = Vec::new(); + let mut comments = Vec::new(); + + match type_ { + SearchType::Posts => { + posts = PostView::list(&conn, + PostListingType::All, + &sort, + data.community_id, + None, + Some(data.q.to_owned()), + None, + false, + false, + data.page, + data.limit)?; + }, + SearchType::Comments => { + comments = CommentView::list(&conn, + &sort, + None, + None, + Some(data.q.to_owned()), + None, + false, + data.page, + data.limit)?; + }, + SearchType::Both => { + posts = PostView::list(&conn, + PostListingType::All, + &sort, + data.community_id, + None, + Some(data.q.to_owned()), + None, + false, + false, + data.page, + data.limit)?; + comments = CommentView::list(&conn, + &sort, + None, + None, + Some(data.q.to_owned()), + None, + false, + data.page, + data.limit)?; + } + }; + + + // Return the jwt + Ok( + SearchResponse { + op: self.op.to_string(), + comments: comments, + posts: posts, + } + ) + } +} diff --git a/server/src/api/user.rs b/server/src/api/user.rs new file mode 100644 index 000000000..82ec11cc9 --- /dev/null +++ b/server/src/api/user.rs @@ -0,0 +1,503 @@ +use super::*; +use std::str::FromStr; +use bcrypt::{verify}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Login { + username_or_email: String, + password: String +} + +#[derive(Serialize, Deserialize)] +pub struct Register { + username: String, + email: Option, + password: String, + password_verify: String, + admin: bool, + spam_timeri: i64, +} + +#[derive(Serialize, Deserialize)] +pub struct LoginResponse { + op: String, + jwt: String +} + +#[derive(Serialize, Deserialize)] +pub struct GetUserDetails { + user_id: Option, + username: Option, + sort: String, + page: Option, + limit: Option, + community_id: Option, + saved_only: bool, + auth: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct GetUserDetailsResponse { + op: String, + user: UserView, + follows: Vec, + moderates: Vec, + comments: Vec, + posts: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct GetRepliesResponse { + op: String, + replies: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct MarkAllAsRead { + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct AddAdmin { + user_id: i32, + added: bool, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct AddAdminResponse { + op: String, + admins: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct BanUser { + user_id: i32, + ban: bool, + reason: Option, + expires: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct BanUserResponse { + op: String, + user: UserView, + banned: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct GetReplies { + sort: String, + page: Option, + limit: Option, + unread_only: bool, + auth: String +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: Login = self.data; + let conn = establish_connection(); + + // Fetch that username / email + let user: User_ = match User_::find_by_email_or_username(&conn, &data.username_or_email) { + Ok(user) => user, + Err(_e) => return Err(APIError::err(self.op, "Couldn't find that username or email"))? + }; + + // Verify the password + let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); + if !valid { + return Err(APIError::err(self.op, "Password incorrect"))? + } + + // Return the jwt + Ok( + LoginResponse { + op: self.op.to_string(), + jwt: user.jwt() + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: Register = self.data; + let conn = establish_connection(); + + // Make sure passwords match + if &data.password != &data.password_verify { + return Err(APIError::err(self.op, "Passwords do not match."))? + } + + if data.spam_timeri < 1142 { + return Err(APIError::err(self.op, "Too fast"))? + } + + if has_slurs(&data.username) { + return Err(APIError::err(self.op, "No slurs"))? + } + + // Make sure there are no admins + if data.admin && UserView::admins(&conn)?.len() > 0 { + return Err(APIError::err(self.op, "Sorry, there's already an admin."))? + } + + // Register the new user + let user_form = UserForm { + name: data.username.to_owned(), + fedi_name: Settings::get().hostname.into(), + email: data.email.to_owned(), + password_encrypted: data.password.to_owned(), + preferred_username: None, + updated: None, + admin: data.admin, + banned: false, + }; + + // Create the user + let inserted_user = match User_::register(&conn, &user_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "User already exists."))? + } + }; + + // Sign them up for main community no matter what + let community_follower_form = CommunityFollowerForm { + community_id: 1, + user_id: inserted_user.id, + }; + + let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community follower already exists."))? + } + }; + + // If its an admin, add them as a mod and follower to main + if data.admin { + let community_moderator_form = CommunityModeratorForm { + community_id: 1, + user_id: inserted_user.id, + }; + + let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Community moderator already exists."))? + } + }; + + } + + // Return the jwt + Ok( + LoginResponse { + op: self.op.to_string(), + jwt: inserted_user.jwt() + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetUserDetails = self.data; + let conn = establish_connection(); + + let user_id: Option = match &data.auth { + Some(auth) => { + match Claims::decode(&auth) { + Ok(claims) => { + let user_id = claims.claims.id; + Some(user_id) + } + Err(_e) => None + } + } + None => None + }; + + //TODO add save + let sort = SortType::from_str(&data.sort)?; + + let user_details_id = match data.user_id { + Some(id) => id, + None => User_::read_from_name(&conn, data.username.to_owned().unwrap_or("admin".to_string()))?.id + }; + + let user_view = UserView::read(&conn, user_details_id)?; + + // If its saved only, you don't care what creator it was + let posts = if data.saved_only { + PostView::list(&conn, + PostListingType::All, + &sort, + data.community_id, + None, + None, + Some(user_details_id), + data.saved_only, + false, + data.page, + data.limit)? + } else { + PostView::list(&conn, + PostListingType::All, + &sort, + data.community_id, + Some(user_details_id), + None, + user_id, + data.saved_only, + false, + data.page, + data.limit)? + }; + let comments = if data.saved_only { + CommentView::list(&conn, + &sort, + None, + None, + None, + Some(user_details_id), + data.saved_only, + data.page, + data.limit)? + } else { + CommentView::list(&conn, + &sort, + None, + Some(user_details_id), + None, + user_id, + data.saved_only, + data.page, + data.limit)? + }; + + let follows = CommunityFollowerView::for_user(&conn, user_details_id)?; + let moderates = CommunityModeratorView::for_user(&conn, user_details_id)?; + + // Return the jwt + Ok( + GetUserDetailsResponse { + op: self.op.to_string(), + user: user_view, + follows: follows, + moderates: moderates, + comments: comments, + posts: posts, + } + ) + } +} + + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: AddAdmin = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Make sure user is an admin + if UserView::read(&conn, user_id)?.admin == false { + return Err(APIError::err(self.op, "Not an admin."))? + } + + let read_user = User_::read(&conn, data.user_id)?; + + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: read_user.password_encrypted, + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: data.added, + banned: read_user.banned, + }; + + match User_::update(&conn, data.user_id, &user_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update user"))? + } + }; + + // Mod tables + let form = ModAddForm { + mod_user_id: user_id, + other_user_id: data.user_id, + removed: Some(!data.added), + }; + + ModAdd::create(&conn, &form)?; + + let admins = UserView::admins(&conn)?; + + Ok( + AddAdminResponse { + op: self.op.to_string(), + admins: admins, + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: BanUser = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + // Make sure user is an admin + if UserView::read(&conn, user_id)?.admin == false { + return Err(APIError::err(self.op, "Not an admin."))? + } + + let read_user = User_::read(&conn, data.user_id)?; + + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: read_user.password_encrypted, + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: read_user.admin, + banned: data.ban, + }; + + match User_::update(&conn, data.user_id, &user_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update user"))? + } + }; + + // Mod tables + let expires = match data.expires { + Some(time) => Some(naive_from_unix(time)), + None => None + }; + + let form = ModBanForm { + mod_user_id: user_id, + other_user_id: data.user_id, + reason: data.reason.to_owned(), + banned: Some(data.ban), + expires: expires, + }; + + ModBan::create(&conn, &form)?; + + let user_view = UserView::read(&conn, data.user_id)?; + + Ok( + BanUserResponse { + op: self.op.to_string(), + user: user_view, + banned: data.ban + } + ) + + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: GetReplies = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let sort = SortType::from_str(&data.sort)?; + + let replies = ReplyView::get_replies(&conn, user_id, &sort, data.unread_only, data.page, data.limit)?; + + // Return the jwt + Ok( + GetRepliesResponse { + op: self.op.to_string(), + replies: replies, + } + ) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: MarkAllAsRead = self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(self.op, "Not logged in."))? + } + }; + + let user_id = claims.id; + + let replies = ReplyView::get_replies(&conn, user_id, &SortType::New, true, Some(1), Some(999))?; + + for reply in &replies { + let comment_form = CommentForm { + content: reply.to_owned().content, + parent_id: reply.to_owned().parent_id, + post_id: reply.to_owned().post_id, + creator_id: reply.to_owned().creator_id, + removed: None, + deleted: None, + read: Some(true), + updated: reply.to_owned().updated + }; + + let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) { + Ok(comment) => comment, + Err(_e) => { + return Err(APIError::err(self.op, "Couldn't update Comment"))? + } + }; + } + + let replies = ReplyView::get_replies(&conn, user_id, &SortType::New, true, Some(1), Some(999))?; + + Ok( + GetRepliesResponse { + op: self.op.to_string(), + replies: replies, + } + ) + } +} diff --git a/server/src/db/category.rs b/server/src/db/category.rs index 8491f1ec4..7835fedd8 100644 --- a/server/src/db/category.rs +++ b/server/src/db/category.rs @@ -1,9 +1,6 @@ -extern crate diesel; use schema::{category}; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {Crud}; +use schema::category::dsl::*; +use super::*; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="category"] @@ -20,26 +17,22 @@ pub struct CategoryForm { impl Crud for Category { fn read(conn: &PgConnection, category_id: i32) -> Result { - use schema::category::dsl::*; category.find(category_id) .first::(conn) } fn delete(conn: &PgConnection, category_id: i32) -> Result { - use schema::category::dsl::*; diesel::delete(category.find(category_id)) .execute(conn) } fn create(conn: &PgConnection, new_category: &CategoryForm) -> Result { - use schema::category::dsl::*; insert_into(category) .values(new_category) .get_result::(conn) } fn update(conn: &PgConnection, category_id: i32, new_category: &CategoryForm) -> Result { - use schema::category::dsl::*; diesel::update(category.find(category_id)) .set(new_category) .get_result::(conn) @@ -48,7 +41,6 @@ impl Crud for Category { impl Category { pub fn list_all(conn: &PgConnection) -> Result, Error> { - use schema::category::dsl::*; category.load::(conn) } } @@ -57,7 +49,6 @@ impl Category { mod tests { use establish_connection; use super::*; - // use Crud; #[test] fn test_crud() { let conn = establish_connection(); diff --git a/server/src/db/comment.rs b/server/src/db/comment.rs index d63837a09..4e57aaa42 100644 --- a/server/src/db/comment.rs +++ b/server/src/db/comment.rs @@ -1,9 +1,5 @@ -extern crate diesel; use schema::{comment, comment_like, comment_saved}; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {Crud, Likeable, Saveable}; +use super::*; use super::post::Post; // WITH RECURSIVE MyTree AS ( diff --git a/server/src/db/comment_view.rs b/server/src/db/comment_view.rs index 903c4839d..eb1d302f7 100644 --- a/server/src/db/comment_view.rs +++ b/server/src/db/comment_view.rs @@ -1,9 +1,4 @@ -extern crate diesel; -use diesel::*; -use diesel::result::Error; -use diesel::dsl::*; -use serde::{Deserialize, Serialize}; -use { SortType, limit_and_offset, fuzzy_search }; +use super::*; // The faked schema since diesel doesn't do views table! { diff --git a/server/src/db/community.rs b/server/src/db/community.rs index 2e75828fc..a2c300f29 100644 --- a/server/src/db/community.rs +++ b/server/src/db/community.rs @@ -1,9 +1,5 @@ -extern crate diesel; use schema::{community, community_moderator, community_follower, community_user_ban, site}; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {Crud, Followable, Joinable, Bannable}; +use super::*; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="community"] diff --git a/server/src/db/community_view.rs b/server/src/db/community_view.rs index 5ec22f4e3..ec77cc8fe 100644 --- a/server/src/db/community_view.rs +++ b/server/src/db/community_view.rs @@ -1,8 +1,4 @@ -extern crate diesel; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {SortType, limit_and_offset}; +use super::*; table! { community_view (id) { diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index ece1e885a..a3edb1b9c 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -1,3 +1,9 @@ +use diesel::*; +use diesel::dsl::*; +use diesel::result::Error; +use {Settings}; +use serde::{Deserialize, Serialize}; + pub mod user; pub mod community; pub mod post; @@ -9,3 +15,69 @@ pub mod community_view; pub mod user_view; pub mod moderator; pub mod moderator_views; + +pub trait Crud { + fn create(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn read(conn: &PgConnection, id: i32) -> Result where Self: Sized; + fn update(conn: &PgConnection, id: i32, form: &T) -> Result where Self: Sized; + fn delete(conn: &PgConnection, id: i32) -> Result where Self: Sized; +} + +pub trait Followable { + fn follow(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn ignore(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub trait Joinable { + fn join(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn leave(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub trait Likeable { + fn read(conn: &PgConnection, id: i32) -> Result, Error> where Self: Sized; + fn like(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn remove(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub trait Bannable { + fn ban(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn unban(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub trait Saveable { + fn save(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn unsave(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub trait Readable { + fn mark_as_read(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn mark_as_unread(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub fn establish_connection() -> PgConnection { + let db_url = Settings::get().db_url; + PgConnection::establish(&db_url) + .expect(&format!("Error connecting to {}", db_url)) +} + +#[derive(EnumString,ToString,Debug, Serialize, Deserialize)] +pub enum SortType { + Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll +} + +#[derive(EnumString,ToString,Debug, Serialize, Deserialize)] +pub enum SearchType { + Both, Comments, Posts +} + +pub fn fuzzy_search(q: &str) -> String { + let replaced = q.replace(" ", "%"); + format!("%{}%", replaced) +} + +pub fn limit_and_offset(page: Option, limit: Option) -> (i64, i64) { + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + (limit, offset) +} diff --git a/server/src/db/moderator.rs b/server/src/db/moderator.rs index 74068e0a6..c23cf9e64 100644 --- a/server/src/db/moderator.rs +++ b/server/src/db/moderator.rs @@ -1,9 +1,5 @@ -extern crate diesel; use schema::{mod_remove_post, mod_lock_post, mod_remove_comment, mod_remove_community, mod_ban_from_community, mod_ban, mod_add_community, mod_add}; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {Crud}; +use super::*; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="mod_remove_post"] diff --git a/server/src/db/moderator_views.rs b/server/src/db/moderator_views.rs index 56881ef54..3c8e2e9a6 100644 --- a/server/src/db/moderator_views.rs +++ b/server/src/db/moderator_views.rs @@ -1,8 +1,4 @@ -extern crate diesel; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {limit_and_offset}; +use super::*; table! { mod_remove_post_view (id) { diff --git a/server/src/db/post.rs b/server/src/db/post.rs index 59a7a1d44..009543d3b 100644 --- a/server/src/db/post.rs +++ b/server/src/db/post.rs @@ -1,9 +1,5 @@ -extern crate diesel; use schema::{post, post_like, post_saved, post_read}; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; -use {Crud, Likeable, Saveable, Readable}; +use super::*; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="post"] diff --git a/server/src/db/post_view.rs b/server/src/db/post_view.rs index 6dc6b6ef8..f51eab10d 100644 --- a/server/src/db/post_view.rs +++ b/server/src/db/post_view.rs @@ -1,9 +1,4 @@ -extern crate diesel; -use diesel::*; -use diesel::result::Error; -use diesel::dsl::*; -use serde::{Deserialize, Serialize}; -use { SortType, limit_and_offset, fuzzy_search }; +use super::*; #[derive(EnumString,ToString,Debug, Serialize, Deserialize)] pub enum PostListingType { diff --git a/server/src/db/user.rs b/server/src/db/user.rs index 9c9e0a52e..254030691 100644 --- a/server/src/db/user.rs +++ b/server/src/db/user.rs @@ -1,9 +1,7 @@ use schema::user_; -use diesel::*; -use diesel::result::Error; use schema::user_::dsl::*; -use serde::{Serialize, Deserialize}; -use {Crud,is_email_regex, Settings}; +use super::*; +use {Settings, is_email_regex}; use jsonwebtoken::{encode, decode, Header, Validation, TokenData}; use bcrypt::{DEFAULT_COST, hash}; @@ -38,6 +36,7 @@ pub struct UserForm { impl Crud for User_ { fn read(conn: &PgConnection, user_id: i32) -> Result { + use schema::user_::dsl::*; user_.find(user_id) .first::(conn) } diff --git a/server/src/db/user_view.rs b/server/src/db/user_view.rs index f17fd8c95..3d78ae1a0 100644 --- a/server/src/db/user_view.rs +++ b/server/src/db/user_view.rs @@ -1,7 +1,4 @@ -extern crate diesel; -use diesel::*; -use diesel::result::Error; -use serde::{Deserialize, Serialize}; +use super::*; table! { user_view (id) { diff --git a/server/src/lib.rs b/server/src/lib.rs index a453633e2..9d87d704d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,5 +1,7 @@ -#[macro_use] -pub extern crate diesel; +#[macro_use] pub extern crate strum_macros; +#[macro_use] pub extern crate lazy_static; +#[macro_use] pub extern crate failure; +#[macro_use] pub extern crate diesel; pub extern crate dotenv; pub extern crate chrono; pub extern crate serde; @@ -11,68 +13,18 @@ pub extern crate strum; pub extern crate jsonwebtoken; pub extern crate bcrypt; pub extern crate regex; -#[macro_use] pub extern crate strum_macros; -#[macro_use] pub extern crate lazy_static; -#[macro_use] extern crate failure; pub mod schema; +pub mod api; pub mod apub; pub mod db; pub mod websocket; -use diesel::*; -use diesel::pg::PgConnection; -use diesel::result::Error; use dotenv::dotenv; use std::env; use regex::Regex; -use serde::{Deserialize, Serialize}; use chrono::{DateTime, NaiveDateTime, Utc}; -pub trait Crud { - fn create(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn read(conn: &PgConnection, id: i32) -> Result where Self: Sized; - fn update(conn: &PgConnection, id: i32, form: &T) -> Result where Self: Sized; - fn delete(conn: &PgConnection, id: i32) -> Result where Self: Sized; -} - -pub trait Followable { - fn follow(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn ignore(conn: &PgConnection, form: &T) -> Result where Self: Sized; -} - -pub trait Joinable { - fn join(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn leave(conn: &PgConnection, form: &T) -> Result where Self: Sized; -} - -pub trait Likeable { - fn read(conn: &PgConnection, id: i32) -> Result, Error> where Self: Sized; - fn like(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn remove(conn: &PgConnection, form: &T) -> Result where Self: Sized; -} - -pub trait Bannable { - fn ban(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn unban(conn: &PgConnection, form: &T) -> Result where Self: Sized; -} - -pub trait Saveable { - fn save(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn unsave(conn: &PgConnection, form: &T) -> Result where Self: Sized; -} - -pub trait Readable { - fn mark_as_read(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn mark_as_unread(conn: &PgConnection, form: &T) -> Result where Self: Sized; -} - -pub fn establish_connection() -> PgConnection { - let db_url = Settings::get().db_url; - PgConnection::establish(&db_url) - .expect(&format!("Error connecting to {}", db_url)) -} - pub struct Settings { db_url: String, hostname: String, @@ -94,16 +46,6 @@ impl Settings { } } -#[derive(EnumString,ToString,Debug, Serialize, Deserialize)] -pub enum SortType { - Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll -} - -#[derive(EnumString,ToString,Debug, Serialize, Deserialize)] -pub enum SearchType { - Both, Comments, Posts -} - pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime { DateTime::::from_utc(ndt, Utc) } @@ -128,18 +70,6 @@ pub fn has_slurs(test: &str) -> bool { SLUR_REGEX.is_match(test) } -pub fn fuzzy_search(q: &str) -> String { - let replaced = q.replace(" ", "%"); - format!("%{}%", replaced) -} - -pub fn limit_and_offset(page: Option, limit: Option) -> (i64, i64) { - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); - (limit, offset) -} - #[cfg(test)] mod tests { use {Settings, is_email_regex, remove_slurs, has_slurs, fuzzy_search}; @@ -167,10 +97,7 @@ mod tests { } } - - lazy_static! { static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap(); } - diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 07378d37a..857c626a8 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -7,41 +7,22 @@ use rand::{rngs::ThreadRng, Rng}; use std::collections::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use serde_json::{Value}; -use bcrypt::{verify}; use std::str::FromStr; -use diesel::PgConnection; use failure::Error; use std::time::{SystemTime}; -use {Crud, Joinable, Likeable, Followable, Bannable, Saveable, establish_connection, naive_now, naive_from_unix, SortType, SearchType, has_slurs, remove_slurs, Settings}; -use db::community::*; -use db::user::*; -use db::post::*; -use db::comment::*; -use db::post_view::*; -use db::comment_view::*; -use db::category::*; -use db::community_view::*; -use db::user_view::*; -use db::moderator_views::*; -use db::moderator::*; +use api::*; +use api::user::*; +use api::community::*; +use api::post::*; +use api::comment::*; +use api::site::*; const RATE_LIMIT_MESSAGES: i32 = 30; const RATE_LIMIT_PER_SECOND: i32 = 60; const RATE_LIMIT_REGISTER_MESSAGES: i32 = 1; const RATE_LIMIT_REGISTER_PER_SECOND: i32 = 60; -#[derive(EnumString,ToString,Debug)] -pub enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead -} - -#[derive(Fail, Debug)] -#[fail(display = "{{\"op\":\"{}\", \"error\":\"{}\"}}", op, message)] -pub struct ErrorMessage { - op: String, - message: String -} /// Chat server sends this messages to session #[derive(Message)] @@ -87,414 +68,6 @@ impl actix::Message for StandardMessage { type Result = String; } -#[derive(Serialize, Deserialize)] -pub struct Login { - pub username_or_email: String, - pub password: String -} - -#[derive(Serialize, Deserialize)] -pub struct Register { - username: String, - email: Option, - password: String, - password_verify: String, - admin: bool, - spam_timeri: i64, -} - -#[derive(Serialize, Deserialize)] -pub struct LoginResponse { - op: String, - jwt: String -} - -#[derive(Serialize, Deserialize)] -pub struct CreateCommunity { - name: String, - title: String, - description: Option, - category_id: i32 , - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct CommunityResponse { - op: String, - community: CommunityView -} - -#[derive(Serialize, Deserialize)] -pub struct ListCommunities { - sort: String, - page: Option, - limit: Option, - auth: Option -} - -#[derive(Serialize, Deserialize)] -pub struct ListCommunitiesResponse { - op: String, - communities: Vec -} - -#[derive(Serialize, Deserialize)] -pub struct ListCategories; - -#[derive(Serialize, Deserialize)] -pub struct ListCategoriesResponse { - op: String, - categories: Vec -} - -#[derive(Serialize, Deserialize)] -pub struct CreatePost { - name: String, - url: Option, - body: Option, - community_id: i32, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct PostResponse { - op: String, - post: PostView -} - - -#[derive(Serialize, Deserialize)] -pub struct GetPost { - id: i32, - auth: Option -} - -#[derive(Serialize, Deserialize)] -pub struct GetPostResponse { - op: String, - post: PostView, - comments: Vec, - community: CommunityView, - moderators: Vec, - admins: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct GetPosts { - type_: String, - sort: String, - page: Option, - limit: Option, - community_id: Option, - auth: Option -} - -#[derive(Serialize, Deserialize)] -pub struct GetPostsResponse { - op: String, - posts: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct GetCommunity { - id: Option, - name: Option, - auth: Option -} - -#[derive(Serialize, Deserialize)] -pub struct GetCommunityResponse { - op: String, - community: CommunityView, - moderators: Vec, - admins: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct CreateComment { - content: String, - parent_id: Option, - edit_id: Option, - post_id: i32, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct EditComment { - content: String, - parent_id: Option, - edit_id: i32, - creator_id: i32, - post_id: i32, - removed: Option, - deleted: Option, - reason: Option, - read: Option, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct SaveComment { - comment_id: i32, - save: bool, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct CommentResponse { - op: String, - comment: CommentView -} - -#[derive(Serialize, Deserialize)] -pub struct CreateCommentLike { - comment_id: i32, - post_id: i32, - score: i16, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct CreatePostLike { - post_id: i32, - score: i16, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct CreatePostLikeResponse { - op: String, - post: PostView -} - - -#[derive(Serialize, Deserialize)] -pub struct EditPost { - 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 -} - -#[derive(Serialize, Deserialize)] -pub struct SavePost { - post_id: i32, - save: bool, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct EditCommunity { - edit_id: i32, - name: String, - title: String, - description: Option, - category_id: i32, - removed: Option, - deleted: Option, - reason: Option, - expires: Option, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct FollowCommunity { - community_id: i32, - follow: bool, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct GetFollowedCommunities { - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct GetFollowedCommunitiesResponse { - op: String, - communities: Vec -} - -#[derive(Serialize, Deserialize)] -pub struct GetUserDetails { - user_id: Option, - username: Option, - sort: String, - page: Option, - limit: Option, - community_id: Option, - saved_only: bool, - auth: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct GetUserDetailsResponse { - op: String, - user: UserView, - follows: Vec, - moderates: Vec, - comments: Vec, - posts: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct GetModlog { - mod_user_id: Option, - community_id: Option, - page: Option, - limit: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct GetModlogResponse { - op: String, - removed_posts: Vec, - locked_posts: Vec, - removed_comments: Vec, - removed_communities: Vec, - banned_from_community: Vec, - banned: Vec, - added_to_community: Vec, - added: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct BanFromCommunity { - community_id: i32, - user_id: i32, - ban: bool, - reason: Option, - expires: Option, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct BanFromCommunityResponse { - op: String, - user: UserView, - banned: bool, -} - - -#[derive(Serialize, Deserialize)] -pub struct AddModToCommunity { - community_id: i32, - user_id: i32, - added: bool, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct AddModToCommunityResponse { - op: String, - moderators: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct CreateSite { - name: String, - description: Option, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct EditSite { - name: String, - description: Option, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct GetSite { -} - -#[derive(Serialize, Deserialize)] -pub struct SiteResponse { - op: String, - site: SiteView, -} - -#[derive(Serialize, Deserialize)] -pub struct GetSiteResponse { - op: String, - site: Option, - admins: Vec, - banned: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct AddAdmin { - user_id: i32, - added: bool, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct AddAdminResponse { - op: String, - admins: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct BanUser { - user_id: i32, - ban: bool, - reason: Option, - expires: Option, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct BanUserResponse { - op: String, - user: UserView, - banned: bool, -} - -#[derive(Serialize, Deserialize)] -pub struct GetReplies { - sort: String, - page: Option, - limit: Option, - unread_only: bool, - auth: String -} - -#[derive(Serialize, Deserialize)] -pub struct GetRepliesResponse { - op: String, - replies: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct Search { - q: String, - type_: String, - community_id: Option, - sort: String, - page: Option, - limit: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct SearchResponse { - op: String, - comments: Vec, - posts: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct MarkAllAsRead { - auth: String -} - #[derive(Debug)] pub struct RateLimitBucket { last_checked: SystemTime, @@ -543,8 +116,25 @@ impl ChatServer { } } - fn send_community_message(&self, conn: &PgConnection, community_id: i32, message: &str, skip_id: usize) -> Result<(), Error> { - let posts = PostView::list(conn, + fn join_room(&self, room_id: i32, id: usize) { + // remove session from all rooms + for (_n, sessions) in &mut self.rooms { + sessions.remove(&id); + } + + // If the room doesn't exist yet + if self.rooms.get_mut(&room_id).is_none() { + self.rooms.insert(room_id, HashSet::new()); + } + + self.rooms.get_mut(&room_id).unwrap().insert(id); + } + + fn send_community_message(&self, community_id: i32, message: &str, skip_id: usize) -> Result<(), Error> { + use db::*; + use db::post_view::*; + let conn = establish_connection(); + let posts = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(community_id), @@ -562,16 +152,16 @@ impl ChatServer { Ok(()) } - fn check_rate_limit_register(&mut self, addr: usize) -> Result<(), Error> { - self.check_rate_limit_full(addr, RATE_LIMIT_REGISTER_MESSAGES, RATE_LIMIT_REGISTER_PER_SECOND) + fn check_rate_limit_register(&mut self, id: usize) -> Result<(), Error> { + self.check_rate_limit_full(id, RATE_LIMIT_REGISTER_MESSAGES, RATE_LIMIT_REGISTER_PER_SECOND) } - fn check_rate_limit(&mut self, addr: usize) -> Result<(), Error> { - self.check_rate_limit_full(addr, RATE_LIMIT_MESSAGES, RATE_LIMIT_PER_SECOND) + fn check_rate_limit(&mut self, id: usize) -> Result<(), Error> { + self.check_rate_limit_full(id, RATE_LIMIT_MESSAGES, RATE_LIMIT_PER_SECOND) } - fn check_rate_limit_full(&mut self, addr: usize, rate: i32, per: i32) -> Result<(), Error> { - if let Some(info) = self.sessions.get(&addr) { + fn check_rate_limit_full(&mut self, id: usize, rate: i32, per: i32) -> Result<(), Error> { + if let Some(info) = self.sessions.get(&id) { if let Some(rate_limit) = self.rate_limits.get_mut(&info.ip) { // The initial value if rate_limit.allowance == -2f64 { @@ -588,7 +178,7 @@ impl ChatServer { if rate_limit.allowance < 1.0 { println!("Rate limited IP: {}, time_passed: {}, allowance: {}", &info.ip, time_passed, rate_limit.allowance); - Err(ErrorMessage { + Err(APIError { op: "Rate Limit".to_string(), message: format!("Too many requests. {} per {} seconds", rate, per), })? @@ -652,7 +242,6 @@ impl Handler for ChatServer { } } - /// Handler for Disconnect message. impl Handler for ChatServer { type Result = (); @@ -700,2192 +289,193 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { let login: Login = serde_json::from_str(data)?; - login.perform(chat, msg.id) + let res = Oper::new(user_operation, login).perform()?; + Ok(serde_json::to_string(&res)?) }, UserOperation::Register => { + chat.check_rate_limit_register(msg.id)?; let register: Register = serde_json::from_str(data)?; - register.perform(chat, msg.id) - }, - UserOperation::CreateCommunity => { - let create_community: CreateCommunity = serde_json::from_str(data)?; - create_community.perform(chat, msg.id) - }, - UserOperation::ListCommunities => { - let list_communities: ListCommunities = serde_json::from_str(data)?; - list_communities.perform(chat, msg.id) - }, - UserOperation::ListCategories => { - let list_categories: ListCategories = ListCategories; - list_categories.perform(chat, msg.id) - }, - UserOperation::CreatePost => { - let create_post: CreatePost = serde_json::from_str(data)?; - create_post.perform(chat, msg.id) - }, - UserOperation::GetPost => { - let get_post: GetPost = serde_json::from_str(data)?; - get_post.perform(chat, msg.id) - }, - UserOperation::GetCommunity => { - let get_community: GetCommunity = serde_json::from_str(data)?; - get_community.perform(chat, msg.id) - }, - UserOperation::CreateComment => { - let create_comment: CreateComment = serde_json::from_str(data)?; - create_comment.perform(chat, msg.id) - }, - UserOperation::EditComment => { - let edit_comment: EditComment = serde_json::from_str(data)?; - edit_comment.perform(chat, msg.id) - }, - UserOperation::SaveComment => { - let save_post: SaveComment = serde_json::from_str(data)?; - save_post.perform(chat, msg.id) - }, - UserOperation::CreateCommentLike => { - let create_comment_like: CreateCommentLike = serde_json::from_str(data)?; - create_comment_like.perform(chat, msg.id) - }, - UserOperation::GetPosts => { - let get_posts: GetPosts = serde_json::from_str(data)?; - get_posts.perform(chat, msg.id) - }, - UserOperation::CreatePostLike => { - let create_post_like: CreatePostLike = serde_json::from_str(data)?; - create_post_like.perform(chat, msg.id) - }, - UserOperation::EditPost => { - let edit_post: EditPost = serde_json::from_str(data)?; - edit_post.perform(chat, msg.id) - }, - UserOperation::SavePost => { - let save_post: SavePost = serde_json::from_str(data)?; - save_post.perform(chat, msg.id) - }, - UserOperation::EditCommunity => { - let edit_community: EditCommunity = serde_json::from_str(data)?; - edit_community.perform(chat, msg.id) - }, - UserOperation::FollowCommunity => { - let follow_community: FollowCommunity = serde_json::from_str(data)?; - follow_community.perform(chat, msg.id) - }, - UserOperation::GetFollowedCommunities => { - let followed_communities: GetFollowedCommunities = serde_json::from_str(data)?; - followed_communities.perform(chat, msg.id) + let res = Oper::new(user_operation, register).perform()?; + Ok(serde_json::to_string(&res)?) }, UserOperation::GetUserDetails => { let get_user_details: GetUserDetails = serde_json::from_str(data)?; - get_user_details.perform(chat, msg.id) - }, - UserOperation::GetModlog => { - let get_modlog: GetModlog = serde_json::from_str(data)?; - get_modlog.perform(chat, msg.id) - }, - UserOperation::BanFromCommunity => { - let ban_from_community: BanFromCommunity = serde_json::from_str(data)?; - ban_from_community.perform(chat, msg.id) - }, - UserOperation::AddModToCommunity => { - let mod_add_to_community: AddModToCommunity = serde_json::from_str(data)?; - mod_add_to_community.perform(chat, msg.id) - }, - UserOperation::CreateSite => { - let create_site: CreateSite = serde_json::from_str(data)?; - create_site.perform(chat, msg.id) - }, - UserOperation::EditSite => { - let edit_site: EditSite = serde_json::from_str(data)?; - edit_site.perform(chat, msg.id) - }, - UserOperation::GetSite => { - let get_site: GetSite = serde_json::from_str(data)?; - get_site.perform(chat, msg.id) + let res = Oper::new(user_operation, get_user_details).perform()?; + Ok(serde_json::to_string(&res)?) }, UserOperation::AddAdmin => { let add_admin: AddAdmin = serde_json::from_str(data)?; - add_admin.perform(chat, msg.id) + let res = Oper::new(user_operation, add_admin).perform()?; + Ok(serde_json::to_string(&res)?) }, UserOperation::BanUser => { let ban_user: BanUser = serde_json::from_str(data)?; - ban_user.perform(chat, msg.id) + let res = Oper::new(user_operation, ban_user).perform()?; + Ok(serde_json::to_string(&res)?) }, UserOperation::GetReplies => { let get_replies: GetReplies = serde_json::from_str(data)?; - get_replies.perform(chat, msg.id) - }, - UserOperation::Search => { - let search: Search = serde_json::from_str(data)?; - search.perform(chat, msg.id) + let res = Oper::new(user_operation, get_replies).perform()?; + Ok(serde_json::to_string(&res)?) }, UserOperation::MarkAllAsRead => { let mark_all_as_read: MarkAllAsRead = serde_json::from_str(data)?; - mark_all_as_read.perform(chat, msg.id) + let res = Oper::new(user_operation, mark_all_as_read).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::GetCommunity => { + let get_community: GetCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_community).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::ListCommunities => { + let list_communities: ListCommunities = serde_json::from_str(data)?; + let res = Oper::new(user_operation, list_communities).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::CreateCommunity => { + chat.check_rate_limit_register(msg.id)?; + let create_community: CreateCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_community).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::EditCommunity => { + let edit_community: EditCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_community).perform()?; + let mut community_sent: CommunityResponse = res.clone(); + community_sent.community.user_id = None; + community_sent.community.subscribed = None; + let community_sent_str = serde_json::to_string(&community_sent)?; + chat.send_community_message(edit_community.edit_id, &community_sent_str, msg.id)?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::FollowCommunity => { + let follow_community: FollowCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, follow_community).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::GetFollowedCommunities => { + let followed_communities: GetFollowedCommunities = serde_json::from_str(data)?; + let res = Oper::new(user_operation, followed_communities).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::BanFromCommunity => { + let ban_from_community: BanFromCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, ban_from_community).perform()?; + let res_str = serde_json::to_string(&res)?; + chat.send_community_message(ban_from_community.community_id, &res_str, msg.id)?; + Ok(res_str) + }, + UserOperation::AddModToCommunity => { + let mod_add_to_community: AddModToCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, mod_add_to_community).perform()?; + let res_str = serde_json::to_string(&res)?; + chat.send_community_message(mod_add_to_community.community_id, &res_str, msg.id)?; + Ok(res_str) + }, + UserOperation::ListCategories => { + let list_categories: ListCategories = ListCategories; + let res = Oper::new(user_operation, list_categories).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::CreatePost => { + chat.check_rate_limit_register(msg.id)?; + let create_post: CreatePost = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_post).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::GetPost => { + let get_post: GetPost = serde_json::from_str(data)?; + chat.join_room(get_post.id, msg.id); + let res = Oper::new(user_operation, get_post).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::GetPosts => { + let get_posts: GetPosts = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_posts).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::CreatePostLike => { + chat.check_rate_limit(msg.id)?; + let create_post_like: CreatePostLike = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_post_like).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::EditPost => { + let edit_post: EditPost = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_post).perform()?; + let mut post_sent = res.clone(); + post_sent.post.my_vote = None; + let post_sent_str = serde_json::to_string(&post_sent)?; + chat.send_room_message(edit_post.edit_id, &post_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + }, + UserOperation::SavePost => { + let save_post: SavePost = serde_json::from_str(data)?; + let res = Oper::new(user_operation, save_post).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::CreateComment => { + chat.check_rate_limit(msg.id)?; + let create_comment: CreateComment = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_comment).perform()?; + let mut comment_sent = res.clone(); + comment_sent.comment.my_vote = None; + comment_sent.comment.user_id = None; + let comment_sent_str = serde_json::to_string(&comment_sent)?; + chat.send_room_message(create_comment.post_id, &comment_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + }, + UserOperation::EditComment => { + let edit_comment: EditComment = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_comment).perform()?; + let mut comment_sent = res.clone(); + comment_sent.comment.my_vote = None; + comment_sent.comment.user_id = None; + let comment_sent_str = serde_json::to_string(&comment_sent)?; + chat.send_room_message(edit_comment.post_id, &comment_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + }, + UserOperation::SaveComment => { + let save_comment: SaveComment = serde_json::from_str(data)?; + let res = Oper::new(user_operation, save_comment).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::CreateCommentLike => { + chat.check_rate_limit(msg.id)?; + let create_comment_like: CreateCommentLike = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_comment_like).perform()?; + let mut comment_sent = res.clone(); + comment_sent.comment.my_vote = None; + comment_sent.comment.user_id = None; + let comment_sent_str = serde_json::to_string(&comment_sent)?; + chat.send_room_message(create_comment_like.post_id, &comment_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + }, + UserOperation::GetModlog => { + let get_modlog: GetModlog = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_modlog).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::CreateSite => { + let create_site: CreateSite = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_site).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::EditSite => { + let edit_site: EditSite = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_site).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::GetSite => { + let get_site: GetSite = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_site).perform()?; + Ok(serde_json::to_string(&res)?) + }, + UserOperation::Search => { + let search: Search = serde_json::from_str(data)?; + let res = Oper::new(user_operation, search).perform()?; + Ok(serde_json::to_string(&res)?) }, } } - -pub trait Perform { - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result; - fn op_type(&self) -> UserOperation; - fn error(&self, error_msg: &str) -> ErrorMessage { - ErrorMessage { - op: self.op_type().to_string(), - message: error_msg.to_string() - } - } -} - -impl Perform for Login { - - fn op_type(&self) -> UserOperation { - UserOperation::Login - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - // Fetch that username / email - let user: User_ = match User_::find_by_email_or_username(&conn, &self.username_or_email) { - Ok(user) => user, - Err(_e) => return Err(self.error("Couldn't find that username or email"))? - }; - - // Verify the password - let valid: bool = verify(&self.password, &user.password_encrypted).unwrap_or(false); - if !valid { - return Err(self.error("Password incorrect"))? - } - - // Return the jwt - Ok( - serde_json::to_string( - &LoginResponse { - op: self.op_type().to_string(), - jwt: user.jwt() - } - )? - ) - } - -} - -impl Perform for Register { - fn op_type(&self) -> UserOperation { - UserOperation::Register - } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - chat.check_rate_limit_register(addr)?; - - // Make sure passwords match - if &self.password != &self.password_verify { - return Err(self.error("Passwords do not match."))? - } - - if self.spam_timeri < 1142 { - return Err(self.error("Too fast"))? - } - - if has_slurs(&self.username) { - return Err(self.error("No slurs"))? - } - - // Make sure there are no admins - if self.admin && UserView::admins(&conn)?.len() > 0 { - return Err(self.error("Sorry, there's already an admin."))? - } - - // Register the new user - let user_form = UserForm { - name: self.username.to_owned(), - fedi_name: Settings::get().hostname.into(), - email: self.email.to_owned(), - password_encrypted: self.password.to_owned(), - preferred_username: None, - updated: None, - admin: self.admin, - banned: false, - }; - - // Create the user - let inserted_user = match User_::register(&conn, &user_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("User already exists."))? - } - }; - - // Sign them up for main community no matter what - let community_follower_form = CommunityFollowerForm { - community_id: 1, - user_id: inserted_user.id, - }; - - let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community follower already exists."))? - } - }; - - // If its an admin, add them as a mod and follower to main - if self.admin { - let community_moderator_form = CommunityModeratorForm { - community_id: 1, - user_id: inserted_user.id, - }; - - let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community moderator already exists."))? - } - }; - - } - - - // Return the jwt - Ok( - serde_json::to_string( - &LoginResponse { - op: self.op_type().to_string(), - jwt: inserted_user.jwt() - } - )? - ) - - } -} - -impl Perform for CreateCommunity { - fn op_type(&self) -> UserOperation { - UserOperation::CreateCommunity - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - chat.check_rate_limit_register(addr)?; - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - if has_slurs(&self.name) || - has_slurs(&self.title) || - (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { - return Err(self.error("No slurs"))? - } - - let user_id = claims.id; - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - // When you create a community, make sure the user becomes a moderator and a follower - let community_form = CommunityForm { - name: self.name.to_owned(), - title: self.title.to_owned(), - description: self.description.to_owned(), - category_id: self.category_id, - creator_id: user_id, - removed: None, - deleted: None, - updated: None, - }; - - let inserted_community = match Community::create(&conn, &community_form) { - Ok(community) => community, - Err(_e) => { - return Err(self.error("Community already exists."))? - } - }; - - let community_moderator_form = CommunityModeratorForm { - community_id: inserted_community.id, - user_id: user_id - }; - - let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community moderator already exists."))? - } - }; - - let community_follower_form = CommunityFollowerForm { - community_id: inserted_community.id, - user_id: user_id - }; - - let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community follower already exists."))? - } - }; - - let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?; - - Ok( - serde_json::to_string( - &CommunityResponse { - op: self.op_type().to_string(), - community: community_view - } - )? - ) - } -} - -impl Perform for ListCommunities { - fn op_type(&self) -> UserOperation { - UserOperation::ListCommunities - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let user_id: Option = match &self.auth { - Some(auth) => { - match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None - } - } - None => None - }; - - let sort = SortType::from_str(&self.sort)?; - - let communities: Vec = CommunityView::list(&conn, user_id, sort, self.page, self.limit)?; - - // Return the jwt - Ok( - serde_json::to_string( - &ListCommunitiesResponse { - op: self.op_type().to_string(), - communities: communities - } - )? - ) - } -} - -impl Perform for ListCategories { - fn op_type(&self) -> UserOperation { - UserOperation::ListCategories - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let categories: Vec = Category::list_all(&conn)?; - - // Return the jwt - Ok( - serde_json::to_string( - &ListCategoriesResponse { - op: self.op_type().to_string(), - categories: categories - } - )? - ) - } -} - -impl Perform for CreatePost { - fn op_type(&self) -> UserOperation { - UserOperation::CreatePost - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - chat.check_rate_limit_register(addr)?; - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - if has_slurs(&self.name) || - (self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) { - return Err(self.error("No slurs"))? - } - - let user_id = claims.id; - - // Check for a community ban - if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() { - return Err(self.error("You have been banned from this community"))? - } - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - let post_form = PostForm { - name: self.name.to_owned(), - url: self.url.to_owned(), - body: self.body.to_owned(), - community_id: self.community_id, - creator_id: user_id, - removed: None, - deleted: None, - locked: None, - updated: None - }; - - let inserted_post = match Post::create(&conn, &post_form) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldn't create Post"))? - } - }; - - // They like their own post by default - let like_form = PostLikeForm { - post_id: inserted_post.id, - user_id: user_id, - score: 1 - }; - - // Only add the like if the score isnt 0 - let _inserted_like = match PostLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => { - return Err(self.error("Couldn't like post."))? - } - }; - - // Refetch the view - let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldn't find Post"))? - } - }; - - Ok( - serde_json::to_string( - &PostResponse { - op: self.op_type().to_string(), - post: post_view - } - )? - ) - } -} - - -impl Perform for GetPost { - fn op_type(&self) -> UserOperation { - UserOperation::GetPost - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - let user_id: Option = match &self.auth { - Some(auth) => { - match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None - } - } - None => None - }; - - let post_view = match PostView::read(&conn, self.id, user_id) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldn't find Post"))? - } - }; - - // remove session from all rooms - for (_n, sessions) in &mut chat.rooms { - sessions.remove(&addr); - } - - // If the room doesn't exist yet - if chat.rooms.get_mut(&self.id).is_none() { - chat.rooms.insert(self.id, HashSet::new()); - } - - chat.rooms.get_mut(&self.id).unwrap().insert(addr); - - let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, None, user_id, false, None, Some(9999))?; - - let community = CommunityView::read(&conn, post_view.community_id, user_id)?; - - let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?; - - let admins = UserView::admins(&conn)?; - - // Return the jwt - Ok( - serde_json::to_string( - &GetPostResponse { - op: self.op_type().to_string(), - post: post_view, - comments: comments, - community: community, - moderators: moderators, - admins: admins, - } - )? - ) - } -} - -impl Perform for GetCommunity { - fn op_type(&self) -> UserOperation { - UserOperation::GetCommunity - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let user_id: Option = match &self.auth { - Some(auth) => { - match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None - } - } - None => None - }; - - let community_id = match self.id { - Some(id) => id, - None => Community::read_from_name(&conn, self.name.to_owned().unwrap_or("main".to_string()))?.id - }; - - let community_view = match CommunityView::read(&conn, community_id, user_id) { - Ok(community) => community, - Err(_e) => { - return Err(self.error("Couldn't find Community"))? - } - }; - - let moderators = match CommunityModeratorView::for_community(&conn, community_id) { - Ok(moderators) => moderators, - Err(_e) => { - return Err(self.error("Couldn't find Community"))? - } - }; - - let admins = UserView::admins(&conn)?; - - // Return the jwt - Ok( - serde_json::to_string( - &GetCommunityResponse { - op: self.op_type().to_string(), - community: community_view, - moderators: moderators, - admins: admins, - } - )? - ) - } -} - -impl Perform for CreateComment { - fn op_type(&self) -> UserOperation { - UserOperation::CreateComment - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - chat.check_rate_limit(addr)?; - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Check for a community ban - let post = Post::read(&conn, self.post_id)?; - if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(self.error("You have been banned from this community"))? - } - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - let content_slurs_removed = remove_slurs(&self.content.to_owned()); - - let comment_form = CommentForm { - content: content_slurs_removed, - parent_id: self.parent_id.to_owned(), - post_id: self.post_id, - creator_id: user_id, - removed: None, - deleted: None, - read: None, - updated: None - }; - - let inserted_comment = match Comment::create(&conn, &comment_form) { - Ok(comment) => comment, - Err(_e) => { - return Err(self.error("Couldn't create Comment"))? - } - }; - - // You like your own comment by default - let like_form = CommentLikeForm { - comment_id: inserted_comment.id, - post_id: self.post_id, - user_id: user_id, - score: 1 - }; - - let _inserted_like = match CommentLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => { - return Err(self.error("Couldn't like comment."))? - } - }; - - let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?; - - let mut comment_sent = comment_view.clone(); - comment_sent.my_vote = None; - comment_sent.user_id = None; - - let comment_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: comment_view - } - )?; - - let comment_sent_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: comment_sent - } - )?; - - chat.send_room_message(self.post_id, &comment_sent_out, addr); - - Ok(comment_out) - } -} - -impl Perform for EditComment { - fn op_type(&self) -> UserOperation { - UserOperation::EditComment - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let orig_comment = CommentView::read(&conn, self.edit_id, None)?; - - // You are allowed to mark the comment as read even if you're banned. - if self.read.is_none() { - - // Verify its the creator or a mod, or an admin - let mut editors: Vec = vec![self.creator_id]; - editors.append( - &mut CommunityModeratorView::for_community(&conn, orig_comment.community_id) - ? - .into_iter() - .map(|m| m.user_id) - .collect() - ); - editors.append( - &mut UserView::admins(&conn) - ? - .into_iter() - .map(|a| a.id) - .collect() - ); - - if !editors.contains(&user_id) { - return Err(self.error("Not allowed to edit comment."))? - } - - // Check for a community ban - if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { - return Err(self.error("You have been banned from this community"))? - } - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - } - - let content_slurs_removed = remove_slurs(&self.content.to_owned()); - - let comment_form = CommentForm { - content: content_slurs_removed, - parent_id: self.parent_id, - post_id: self.post_id, - creator_id: self.creator_id, - removed: self.removed.to_owned(), - deleted: self.deleted.to_owned(), - read: self.read.to_owned(), - updated: if self.read.is_some() { orig_comment.updated } else {Some(naive_now())} - }; - - let _updated_comment = match Comment::update(&conn, self.edit_id, &comment_form) { - Ok(comment) => comment, - Err(_e) => { - return Err(self.error("Couldn't update Comment"))? - } - }; - - // Mod tables - if let Some(removed) = self.removed.to_owned() { - let form = ModRemoveCommentForm { - mod_user_id: user_id, - comment_id: self.edit_id, - removed: Some(removed), - reason: self.reason.to_owned(), - }; - ModRemoveComment::create(&conn, &form)?; - } - - - let comment_view = CommentView::read(&conn, self.edit_id, Some(user_id))?; - - let mut comment_sent = comment_view.clone(); - comment_sent.my_vote = None; - comment_sent.user_id = None; - - let comment_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: comment_view - } - )?; - - let comment_sent_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: comment_sent - } - )?; - - chat.send_room_message(self.post_id, &comment_sent_out, addr); - - Ok(comment_out) - } -} - -impl Perform for SaveComment { - fn op_type(&self) -> UserOperation { - UserOperation::SaveComment - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let comment_saved_form = CommentSavedForm { - comment_id: self.comment_id, - user_id: user_id, - }; - - if self.save { - match CommentSaved::save(&conn, &comment_saved_form) { - Ok(comment) => comment, - Err(_e) => { - return Err(self.error("Couldnt do comment save"))? - } - }; - } else { - match CommentSaved::unsave(&conn, &comment_saved_form) { - Ok(comment) => comment, - Err(_e) => { - return Err(self.error("Couldnt do comment save"))? - } - }; - } - - let comment_view = CommentView::read(&conn, self.comment_id, Some(user_id))?; - - let comment_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: comment_view - } - ) - ?; - - Ok(comment_out) - } -} - - -impl Perform for CreateCommentLike { - fn op_type(&self) -> UserOperation { - UserOperation::CreateCommentLike - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - chat.check_rate_limit(addr)?; - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Check for a community ban - let post = Post::read(&conn, self.post_id)?; - if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(self.error("You have been banned from this community"))? - } - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - let like_form = CommentLikeForm { - comment_id: self.comment_id, - post_id: self.post_id, - user_id: user_id, - score: self.score - }; - - // Remove any likes first - CommentLike::remove(&conn, &like_form)?; - - // Only add the like if the score isnt 0 - if &like_form.score != &0 { - let _inserted_like = match CommentLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => { - return Err(self.error("Couldn't like comment."))? - } - }; - } - - // Have to refetch the comment to get the current state - let liked_comment = CommentView::read(&conn, self.comment_id, Some(user_id))?; - - let mut liked_comment_sent = liked_comment.clone(); - liked_comment_sent.my_vote = None; - liked_comment_sent.user_id = None; - - let like_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: liked_comment - } - )?; - - let like_sent_out = serde_json::to_string( - &CommentResponse { - op: self.op_type().to_string(), - comment: liked_comment_sent - } - )?; - - chat.send_room_message(self.post_id, &like_sent_out, addr); - - Ok(like_out) - } -} - - -impl Perform for GetPosts { - fn op_type(&self) -> UserOperation { - UserOperation::GetPosts - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let user_id: Option = match &self.auth { - Some(auth) => { - match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None - } - } - None => None - }; - - let type_ = PostListingType::from_str(&self.type_)?; - let sort = SortType::from_str(&self.sort)?; - - let posts = match PostView::list(&conn, - type_, - &sort, - self.community_id, - None, - None, - user_id, - false, - false, - self.page, - self.limit) { - Ok(posts) => posts, - Err(_e) => { - return Err(self.error("Couldn't get posts"))? - } - }; - - // Return the jwt - Ok( - serde_json::to_string( - &GetPostsResponse { - op: self.op_type().to_string(), - posts: posts - } - )? - ) - } -} - - -impl Perform for CreatePostLike { - fn op_type(&self) -> UserOperation { - UserOperation::CreatePostLike - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - chat.check_rate_limit(addr)?; - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Check for a community ban - let post = Post::read(&conn, self.post_id)?; - if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(self.error("You have been banned from this community"))? - } - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - let like_form = PostLikeForm { - post_id: self.post_id, - user_id: user_id, - score: self.score - }; - - // Remove any likes first - PostLike::remove(&conn, &like_form)?; - - // Only add the like if the score isnt 0 - if &like_form.score != &0 { - let _inserted_like = match PostLike::like(&conn, &like_form) { - Ok(like) => like, - Err(_e) => { - return Err(self.error("Couldn't like post."))? - } - }; - } - - let post_view = match PostView::read(&conn, self.post_id, Some(user_id)) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldn't find Post"))? - } - }; - - // just output the score - - let like_out = serde_json::to_string( - &CreatePostLikeResponse { - op: self.op_type().to_string(), - post: post_view - } - )?; - - Ok(like_out) - } -} - -impl Perform for EditPost { - fn op_type(&self) -> UserOperation { - UserOperation::EditPost - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - if has_slurs(&self.name) || - (self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) { - return Err(self.error("No slurs"))? - } - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Verify its the creator or a mod or admin - let mut editors: Vec = vec![self.creator_id]; - editors.append( - &mut CommunityModeratorView::for_community(&conn, self.community_id) - ? - .into_iter() - .map(|m| m.user_id) - .collect() - ); - editors.append( - &mut UserView::admins(&conn) - ? - .into_iter() - .map(|a| a.id) - .collect() - ); - if !editors.contains(&user_id) { - return Err(self.error("Not allowed to edit post."))? - } - - // Check for a community ban - if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() { - return Err(self.error("You have been banned from this community"))? - } - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - let post_form = PostForm { - name: self.name.to_owned(), - url: self.url.to_owned(), - body: self.body.to_owned(), - creator_id: self.creator_id.to_owned(), - community_id: self.community_id, - removed: self.removed.to_owned(), - deleted: self.deleted.to_owned(), - locked: self.locked.to_owned(), - updated: Some(naive_now()) - }; - - let _updated_post = match Post::update(&conn, self.edit_id, &post_form) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldn't update Post"))? - } - }; - - // Mod tables - if let Some(removed) = self.removed.to_owned() { - let form = ModRemovePostForm { - mod_user_id: user_id, - post_id: self.edit_id, - removed: Some(removed), - reason: self.reason.to_owned(), - }; - ModRemovePost::create(&conn, &form)?; - } - - if let Some(locked) = self.locked.to_owned() { - let form = ModLockPostForm { - mod_user_id: user_id, - post_id: self.edit_id, - locked: Some(locked), - }; - ModLockPost::create(&conn, &form)?; - } - - let post_view = PostView::read(&conn, self.edit_id, Some(user_id))?; - - let mut post_sent = post_view.clone(); - post_sent.my_vote = None; - - let post_out = serde_json::to_string( - &PostResponse { - op: self.op_type().to_string(), - post: post_view - } - ) - ?; - - let post_sent_out = serde_json::to_string( - &PostResponse { - op: self.op_type().to_string(), - post: post_sent - } - ) - ?; - - chat.send_room_message(self.edit_id, &post_sent_out, addr); - - Ok(post_out) - } -} - -impl Perform for SavePost { - fn op_type(&self) -> UserOperation { - UserOperation::SavePost - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let post_saved_form = PostSavedForm { - post_id: self.post_id, - user_id: user_id, - }; - - if self.save { - match PostSaved::save(&conn, &post_saved_form) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldnt do post save"))? - } - }; - } else { - match PostSaved::unsave(&conn, &post_saved_form) { - Ok(post) => post, - Err(_e) => { - return Err(self.error("Couldnt do post save"))? - } - }; - } - - let post_view = PostView::read(&conn, self.post_id, Some(user_id))?; - - let post_out = serde_json::to_string( - &PostResponse { - op: self.op_type().to_string(), - post: post_view - } - ) - ?; - - Ok(post_out) - } -} - -impl Perform for EditCommunity { - fn op_type(&self) -> UserOperation { - UserOperation::EditCommunity - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - if has_slurs(&self.name) || has_slurs(&self.title) { - return Err(self.error("No slurs"))? - } - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(self.error("You have been banned from the site"))? - } - - // Verify its a mod - let mut editors: Vec = Vec::new(); - editors.append( - &mut CommunityModeratorView::for_community(&conn, self.edit_id) - ? - .into_iter() - .map(|m| m.user_id) - .collect() - ); - editors.append( - &mut UserView::admins(&conn) - ? - .into_iter() - .map(|a| a.id) - .collect() - ); - if !editors.contains(&user_id) { - return Err(self.error("Not allowed to edit community"))? - } - - let community_form = CommunityForm { - name: self.name.to_owned(), - title: self.title.to_owned(), - description: self.description.to_owned(), - category_id: self.category_id.to_owned(), - creator_id: user_id, - removed: self.removed.to_owned(), - deleted: self.deleted.to_owned(), - updated: Some(naive_now()) - }; - - let _updated_community = match Community::update(&conn, self.edit_id, &community_form) { - Ok(community) => community, - Err(_e) => { - return Err(self.error("Couldn't update Community"))? - } - }; - - // Mod tables - if let Some(removed) = self.removed.to_owned() { - let expires = match self.expires { - Some(time) => Some(naive_from_unix(time)), - None => None - }; - let form = ModRemoveCommunityForm { - mod_user_id: user_id, - community_id: self.edit_id, - removed: Some(removed), - reason: self.reason.to_owned(), - expires: expires - }; - ModRemoveCommunity::create(&conn, &form)?; - } - - let community_view = CommunityView::read(&conn, self.edit_id, Some(user_id))?; - - let community_out = serde_json::to_string( - &CommunityResponse { - op: self.op_type().to_string(), - community: community_view - } - ) - ?; - - let community_view_sent = CommunityView::read(&conn, self.edit_id, None)?; - - let community_sent = serde_json::to_string( - &CommunityResponse { - op: self.op_type().to_string(), - community: community_view_sent - } - ) - ?; - - chat.send_community_message(&conn, self.edit_id, &community_sent, addr)?; - - Ok(community_out) - } -} - - -impl Perform for FollowCommunity { - fn op_type(&self) -> UserOperation { - UserOperation::FollowCommunity - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let community_follower_form = CommunityFollowerForm { - community_id: self.community_id, - user_id: user_id - }; - - if self.follow { - - match CommunityFollower::follow(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community follower already exists."))? - } - }; - } else { - match CommunityFollower::ignore(&conn, &community_follower_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community follower already exists."))? - } - }; - } - - let community_view = CommunityView::read(&conn, self.community_id, Some(user_id))?; - - Ok( - serde_json::to_string( - &CommunityResponse { - op: self.op_type().to_string(), - community: community_view - } - )? - ) - } -} - -impl Perform for GetFollowedCommunities { - fn op_type(&self) -> UserOperation { - UserOperation::GetFollowedCommunities - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let communities: Vec = match CommunityFollowerView::for_user(&conn, user_id) { - Ok(communities) => communities, - Err(_e) => { - return Err(self.error("System error, try logging out and back in."))? - } - }; - - // Return the jwt - Ok( - serde_json::to_string( - &GetFollowedCommunitiesResponse { - op: self.op_type().to_string(), - communities: communities - } - )? - ) - } -} - -impl Perform for GetUserDetails { - fn op_type(&self) -> UserOperation { - UserOperation::GetUserDetails - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let user_id: Option = match &self.auth { - Some(auth) => { - match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None - } - } - None => None - }; - - //TODO add save - let sort = SortType::from_str(&self.sort)?; - - let user_details_id = match self.user_id { - Some(id) => id, - None => User_::read_from_name(&conn, self.username.to_owned().unwrap_or("admin".to_string()))?.id - }; - - let user_view = UserView::read(&conn, user_details_id)?; - - // If its saved only, you don't care what creator it was - let posts = if self.saved_only { - PostView::list(&conn, - PostListingType::All, - &sort, - self.community_id, - None, - None, - Some(user_details_id), - self.saved_only, - false, - self.page, - self.limit)? - } else { - PostView::list(&conn, - PostListingType::All, - &sort, - self.community_id, - Some(user_details_id), - None, - user_id, - self.saved_only, - false, - self.page, - self.limit)? - }; - let comments = if self.saved_only { - CommentView::list(&conn, - &sort, - None, - None, - None, - Some(user_details_id), - self.saved_only, - self.page, - self.limit)? - } else { - CommentView::list(&conn, - &sort, - None, - Some(user_details_id), - None, - user_id, - self.saved_only, - self.page, - self.limit)? - }; - - let follows = CommunityFollowerView::for_user(&conn, user_details_id)?; - let moderates = CommunityModeratorView::for_user(&conn, user_details_id)?; - - // Return the jwt - Ok( - serde_json::to_string( - &GetUserDetailsResponse { - op: self.op_type().to_string(), - user: user_view, - follows: follows, - moderates: moderates, - comments: comments, - posts: posts, - } - )? - ) - } -} - -impl Perform for GetModlog { - fn op_type(&self) -> UserOperation { - UserOperation::GetModlog - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let removed_posts = ModRemovePostView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit)?; - let locked_posts = ModLockPostView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit)?; - let removed_comments = ModRemoveCommentView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit)?; - let banned_from_community = ModBanFromCommunityView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit)?; - let added_to_community = ModAddCommunityView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit)?; - - // These arrays are only for the full modlog, when a community isn't given - let mut removed_communities = Vec::new(); - let mut banned = Vec::new(); - let mut added = Vec::new(); - - if self.community_id.is_none() { - removed_communities = ModRemoveCommunityView::list(&conn, self.mod_user_id, self.page, self.limit)?; - banned = ModBanView::list(&conn, self.mod_user_id, self.page, self.limit)?; - added = ModAddView::list(&conn, self.mod_user_id, self.page, self.limit)?; - } - - // Return the jwt - Ok( - serde_json::to_string( - &GetModlogResponse { - op: self.op_type().to_string(), - removed_posts: removed_posts, - locked_posts: locked_posts, - removed_comments: removed_comments, - removed_communities: removed_communities, - banned_from_community: banned_from_community, - banned: banned, - added_to_community: added_to_community, - added: added, - } - )? - ) - } -} - -impl Perform for GetReplies { - fn op_type(&self) -> UserOperation { - UserOperation::GetReplies - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let sort = SortType::from_str(&self.sort)?; - - let replies = ReplyView::get_replies(&conn, user_id, &sort, self.unread_only, self.page, self.limit)?; - - // Return the jwt - Ok( - serde_json::to_string( - &GetRepliesResponse { - op: self.op_type().to_string(), - replies: replies, - } - )? - ) - } -} - -impl Perform for BanFromCommunity { - fn op_type(&self) -> UserOperation { - UserOperation::BanFromCommunity - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let community_user_ban_form = CommunityUserBanForm { - community_id: self.community_id, - user_id: self.user_id, - }; - - if self.ban { - match CommunityUserBan::ban(&conn, &community_user_ban_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community user ban already exists"))? - } - }; - } else { - match CommunityUserBan::unban(&conn, &community_user_ban_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community user ban already exists"))? - } - }; - } - - // Mod tables - let expires = match self.expires { - Some(time) => Some(naive_from_unix(time)), - None => None - }; - - let form = ModBanFromCommunityForm { - mod_user_id: user_id, - other_user_id: self.user_id, - community_id: self.community_id, - reason: self.reason.to_owned(), - banned: Some(self.ban), - expires: expires, - }; - ModBanFromCommunity::create(&conn, &form)?; - - let user_view = UserView::read(&conn, self.user_id)?; - - let res = serde_json::to_string( - &BanFromCommunityResponse { - op: self.op_type().to_string(), - user: user_view, - banned: self.ban - } - ) - ?; - - - chat.send_community_message(&conn, self.community_id, &res, addr)?; - - Ok(res) - } -} - -impl Perform for AddModToCommunity { - fn op_type(&self) -> UserOperation { - UserOperation::AddModToCommunity - } - - fn perform(&self, chat: &mut ChatServer, addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let community_moderator_form = CommunityModeratorForm { - community_id: self.community_id, - user_id: self.user_id - }; - - if self.added { - match CommunityModerator::join(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community moderator already exists."))? - } - }; - } else { - match CommunityModerator::leave(&conn, &community_moderator_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Community moderator already exists."))? - } - }; - } - - // Mod tables - let form = ModAddCommunityForm { - mod_user_id: user_id, - other_user_id: self.user_id, - community_id: self.community_id, - removed: Some(!self.added), - }; - ModAddCommunity::create(&conn, &form)?; - - let moderators = CommunityModeratorView::for_community(&conn, self.community_id)?; - - let res = serde_json::to_string( - &AddModToCommunityResponse { - op: self.op_type().to_string(), - moderators: moderators, - } - ) - ?; - - - chat.send_community_message(&conn, self.community_id, &res, addr)?; - - Ok(res) - - } -} - -impl Perform for CreateSite { - fn op_type(&self) -> UserOperation { - UserOperation::CreateSite - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - if has_slurs(&self.name) || - (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { - return Err(self.error("No slurs"))? - } - - let user_id = claims.id; - - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { - return Err(self.error("Not an admin."))? - } - - let site_form = SiteForm { - name: self.name.to_owned(), - description: self.description.to_owned(), - creator_id: user_id, - updated: None, - }; - - match Site::create(&conn, &site_form) { - Ok(site) => site, - Err(_e) => { - return Err(self.error("Site exists already"))? - } - }; - - let site_view = SiteView::read(&conn)?; - - Ok( - serde_json::to_string( - &SiteResponse { - op: self.op_type().to_string(), - site: site_view, - } - )? - ) - } -} - -impl Perform for EditSite { - fn op_type(&self) -> UserOperation { - UserOperation::EditSite - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - if has_slurs(&self.name) || - (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { - return Err(self.error("No slurs"))? - } - - let user_id = claims.id; - - // Make sure user is an admin - if UserView::read(&conn, user_id)?.admin == false { - return Err(self.error("Not an admin."))? - } - - let found_site = Site::read(&conn, 1)?; - - let site_form = SiteForm { - name: self.name.to_owned(), - description: self.description.to_owned(), - creator_id: found_site.creator_id, - updated: Some(naive_now()), - }; - - match Site::update(&conn, 1, &site_form) { - Ok(site) => site, - Err(_e) => { - return Err(self.error("Couldn't update site."))? - } - }; - - let site_view = SiteView::read(&conn)?; - - Ok( - serde_json::to_string( - &SiteResponse { - op: self.op_type().to_string(), - site: site_view, - } - )? - ) - } -} - -impl Perform for GetSite { - fn op_type(&self) -> UserOperation { - UserOperation::GetSite - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - // It can return a null site in order to redirect - let site_view = match Site::read(&conn, 1) { - Ok(_site) => Some(SiteView::read(&conn)?), - Err(_e) => None - }; - - let admins = UserView::admins(&conn)?; - let banned = UserView::banned(&conn)?; - - Ok( - serde_json::to_string( - &GetSiteResponse { - op: self.op_type().to_string(), - site: site_view, - admins: admins, - banned: banned, - } - )? - ) - } -} - -impl Perform for AddAdmin { - fn op_type(&self) -> UserOperation { - UserOperation::AddAdmin - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Make sure user is an admin - if UserView::read(&conn, user_id)?.admin == false { - return Err(self.error("Not an admin."))? - } - - let read_user = User_::read(&conn, self.user_id)?; - - let user_form = UserForm { - name: read_user.name, - fedi_name: read_user.fedi_name, - email: read_user.email, - password_encrypted: read_user.password_encrypted, - preferred_username: read_user.preferred_username, - updated: Some(naive_now()), - admin: self.added, - banned: read_user.banned, - }; - - match User_::update(&conn, self.user_id, &user_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Couldn't update user"))? - } - }; - - // Mod tables - let form = ModAddForm { - mod_user_id: user_id, - other_user_id: self.user_id, - removed: Some(!self.added), - }; - - ModAdd::create(&conn, &form)?; - - let admins = UserView::admins(&conn)?; - - let res = serde_json::to_string( - &AddAdminResponse { - op: self.op_type().to_string(), - admins: admins, - } - ) - ?; - - - Ok(res) - - } -} - -impl Perform for BanUser { - fn op_type(&self) -> UserOperation { - UserOperation::BanUser - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - // Make sure user is an admin - if UserView::read(&conn, user_id)?.admin == false { - return Err(self.error("Not an admin."))? - } - - let read_user = User_::read(&conn, self.user_id)?; - - let user_form = UserForm { - name: read_user.name, - fedi_name: read_user.fedi_name, - email: read_user.email, - password_encrypted: read_user.password_encrypted, - preferred_username: read_user.preferred_username, - updated: Some(naive_now()), - admin: read_user.admin, - banned: self.ban, - }; - - match User_::update(&conn, self.user_id, &user_form) { - Ok(user) => user, - Err(_e) => { - return Err(self.error("Couldn't update user"))? - } - }; - - // Mod tables - let expires = match self.expires { - Some(time) => Some(naive_from_unix(time)), - None => None - }; - - let form = ModBanForm { - mod_user_id: user_id, - other_user_id: self.user_id, - reason: self.reason.to_owned(), - banned: Some(self.ban), - expires: expires, - }; - - ModBan::create(&conn, &form)?; - - let user_view = UserView::read(&conn, self.user_id)?; - - let res = serde_json::to_string( - &BanUserResponse { - op: self.op_type().to_string(), - user: user_view, - banned: self.ban - } - ) - ?; - - Ok(res) - - } -} - -impl Perform for Search { - fn op_type(&self) -> UserOperation { - UserOperation::Search - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let sort = SortType::from_str(&self.sort)?; - let type_ = SearchType::from_str(&self.type_)?; - - let mut posts = Vec::new(); - let mut comments = Vec::new(); - - match type_ { - SearchType::Posts => { - posts = PostView::list(&conn, - PostListingType::All, - &sort, - self.community_id, - None, - Some(self.q.to_owned()), - None, - false, - false, - self.page, - self.limit)?; - }, - SearchType::Comments => { - comments = CommentView::list(&conn, - &sort, - None, - None, - Some(self.q.to_owned()), - None, - false, - self.page, - self.limit)?; - }, - SearchType::Both => { - posts = PostView::list(&conn, - PostListingType::All, - &sort, - self.community_id, - None, - Some(self.q.to_owned()), - None, - false, - false, - self.page, - self.limit)?; - comments = CommentView::list(&conn, - &sort, - None, - None, - Some(self.q.to_owned()), - None, - false, - self.page, - self.limit)?; - } - }; - - - // Return the jwt - Ok( - serde_json::to_string( - &SearchResponse { - op: self.op_type().to_string(), - comments: comments, - posts: posts, - } - )? - ) - } -} - - -impl Perform for MarkAllAsRead { - fn op_type(&self) -> UserOperation { - UserOperation::MarkAllAsRead - } - - fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result { - - let conn = establish_connection(); - - let claims = match Claims::decode(&self.auth) { - Ok(claims) => claims.claims, - Err(_e) => { - return Err(self.error("Not logged in."))? - } - }; - - let user_id = claims.id; - - let replies = ReplyView::get_replies(&conn, user_id, &SortType::New, true, Some(1), Some(999))?; - - for reply in &replies { - let comment_form = CommentForm { - content: reply.to_owned().content, - parent_id: reply.to_owned().parent_id, - post_id: reply.to_owned().post_id, - creator_id: reply.to_owned().creator_id, - removed: None, - deleted: None, - read: Some(true), - updated: reply.to_owned().updated - }; - - let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) { - Ok(comment) => comment, - Err(_e) => { - return Err(self.error("Couldn't update Comment"))? - } - }; - } - - let replies = ReplyView::get_replies(&conn, user_id, &SortType::New, true, Some(1), Some(999))?; - - Ok( - serde_json::to_string( - &GetRepliesResponse { - op: self.op_type().to_string(), - replies: replies, - } - )? - ) - } -}