From 016920aeb7dc47f32412829c70b872c54caf10e3 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sun, 21 Apr 2019 13:52:55 -0700 Subject: [PATCH] Community moderation fixes. - Don't show banned communities on main post list. Fixes #95 - Add back in community moderation and editing. Fixes #92 --- .../2019-03-30-212058_create_post_view/up.sql | 1 + server/src/actions/post_view.rs | 10 +++-- server/src/websocket_server/server.rs | 31 +++++++++++---- ui/src/components/comment-node.tsx | 14 +++---- ui/src/components/community.tsx | 14 +++++-- ui/src/components/post-listing.tsx | 20 ++++------ ui/src/components/post.tsx | 6 ++- ui/src/components/sidebar.tsx | 39 ++++++++++++------- ui/src/interfaces.ts | 2 + 9 files changed, 88 insertions(+), 49 deletions(-) diff --git a/server/migrations/2019-03-30-212058_create_post_view/up.sql b/server/migrations/2019-03-30-212058_create_post_view/up.sql index 17dc8604a..4a4fd146e 100644 --- a/server/migrations/2019-03-30-212058_create_post_view/up.sql +++ b/server/migrations/2019-03-30-212058_create_post_view/up.sql @@ -16,6 +16,7 @@ with all_post as p.*, (select name from user_ where p.creator_id = user_.id) as creator_name, (select name from community where p.community_id = community.id) as community_name, + (select removed from community c where p.community_id = c.id) as community_removed, (select count(*) from comment where comment.post_id = p.id) as number_of_comments, coalesce(sum(pl.score), 0) as score, count (case when pl.score = 1 then 1 else null end) as upvotes, diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index ba42fe277..ccad93178 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -25,6 +25,7 @@ table! { updated -> Nullable, creator_name -> Varchar, community_name -> Varchar, + community_removed -> Bool, number_of_comments -> BigInt, score -> BigInt, upvotes -> BigInt, @@ -54,6 +55,7 @@ pub struct PostView { pub updated: Option, pub creator_name: String, pub community_name: String, + pub community_removed: bool, pub number_of_comments: i64, pub score: i64, pub upvotes: i64, @@ -133,13 +135,11 @@ impl PostView { .order_by(score.desc()) }; - - // TODO make sure community removed isn't fetched either - query = query .limit(limit) .offset(offset) - .filter(removed.eq(false)); + .filter(removed.eq(false)) + .filter(community_removed.eq(false)); query.load::(conn) } @@ -255,6 +255,7 @@ mod tests { removed: false, locked: false, community_name: community_name.to_owned(), + community_removed: false, number_of_comments: 0, score: 1, upvotes: 1, @@ -280,6 +281,7 @@ mod tests { creator_name: user_name.to_owned(), community_id: inserted_community.id, community_name: community_name.to_owned(), + community_removed: false, number_of_comments: 0, score: 1, upvotes: 1, diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index ba6e176b5..068fd0275 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -196,7 +196,8 @@ pub struct GetCommunity { pub struct GetCommunityResponse { op: String, community: CommunityView, - moderators: Vec + moderators: Vec, + admins: Vec, } #[derive(Serialize, Deserialize)] @@ -1165,13 +1166,16 @@ impl Perform for GetCommunity { } }; + 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 + moderators: moderators, + admins: admins, } )? ) @@ -1817,11 +1821,24 @@ impl Perform for EditCommunity { } // Verify its a mod - let moderator_view = CommunityModeratorView::for_community(&conn, self.edit_id)?; - let mod_ids: Vec = moderator_view.into_iter().map(|m| m.user_id).collect(); - if !mod_ids.contains(&user_id) { - return Err(self.error("Incorrect creator."))? - }; + 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(), diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index cf7b1bcea..415dad77b 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -210,13 +210,6 @@ export class CommentNode extends Component { return UserService.Instance.user && this.props.node.comment.creator_id == UserService.Instance.user.id; } - get canMod(): boolean { - let adminsThenMods = this.props.admins.map(a => a.id) - .concat(this.props.moderators.map(m => m.user_id)); - - return canMod(UserService.Instance.user, adminsThenMods, this.props.node.comment.creator_id); - } - get isMod(): boolean { return this.props.moderators && isMod(this.props.moderators.map(m => m.user_id), this.props.node.comment.creator_id); } @@ -225,6 +218,13 @@ export class CommentNode extends Component { return this.props.admins && isMod(this.props.admins.map(a => a.id), this.props.node.comment.creator_id); } + get canMod(): boolean { + let adminsThenMods = this.props.admins.map(a => a.id) + .concat(this.props.moderators.map(m => m.user_id)); + + return canMod(UserService.Instance.user, adminsThenMods, this.props.node.comment.creator_id); + } + get canAdmin(): boolean { return this.props.admins && canMod(UserService.Instance.user, this.props.admins.map(a => a.id), this.props.node.comment.creator_id); } diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 6271bde5a..c89d2f062 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -1,7 +1,7 @@ import { Component } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { UserOperation, Community as CommunityI, GetCommunityResponse, CommunityResponse, CommunityUser} from '../interfaces'; +import { UserOperation, Community as CommunityI, GetCommunityResponse, CommunityResponse, CommunityUser, UserView } from '../interfaces'; import { WebSocketService } from '../services'; import { PostListings } from './post-listings'; import { Sidebar } from './sidebar'; @@ -11,6 +11,7 @@ interface State { community: CommunityI; communityId: number; moderators: Array; + admins: Array; loading: boolean; } @@ -29,9 +30,11 @@ export class Community extends Component { number_of_subscribers: null, number_of_posts: null, number_of_comments: null, - published: null + published: null, + removed: null, }, moderators: [], + admins: [], communityId: Number(this.props.match.params.id), loading: true } @@ -71,7 +74,11 @@ export class Community extends Component {
- +
} @@ -90,6 +97,7 @@ export class Community extends Component { let res: GetCommunityResponse = msg; this.state.community = res.community; this.state.moderators = res.moderators; + this.state.admins = res.admins; this.state.loading = false; this.setState(this.state); } else if (op == UserOperation.EditCommunity) { diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index 8803d6290..93e88071e 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -174,6 +174,14 @@ export class PostListing extends Component { return UserService.Instance.user && this.props.post.creator_id == UserService.Instance.user.id; } + get isMod(): boolean { + return this.props.moderators && isMod(this.props.moderators.map(m => m.user_id), this.props.post.creator_id); + } + + get isAdmin(): boolean { + return this.props.admins && isMod(this.props.admins.map(a => a.id), this.props.post.creator_id); + } + get canMod(): boolean { if (this.props.editable) { @@ -185,18 +193,6 @@ export class PostListing extends Component { } else return false; } - get isMod(): boolean { - return this.props.moderators && isMod(this.props.moderators.map(m => m.user_id), this.props.post.creator_id); - } - - get isAdmin(): boolean { - return this.props.admins && isMod(this.props.admins.map(a => a.id), this.props.post.creator_id); - } - - get canAdmin(): boolean { - return this.props.admins && canMod(UserService.Instance.user, this.props.admins.map(a => a.id), this.props.post.creator_id); - } - handlePostLike(i: PostListing) { let form: CreatePostLikeForm = { diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 3f243220a..3ece67479 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -148,7 +148,11 @@ export class Post extends Component { sidebar() { return (
- +
); } diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx index 2f231f9a0..958804484 100644 --- a/ui/src/components/sidebar.tsx +++ b/ui/src/components/sidebar.tsx @@ -1,6 +1,6 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; -import { Community, CommunityUser, FollowCommunityForm, CommunityForm as CommunityFormI } from '../interfaces'; +import { Community, CommunityUser, FollowCommunityForm, CommunityForm as CommunityFormI, UserView } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { mdToHtml, getUnixTime } from '../utils'; import { CommunityForm } from './community-form'; @@ -8,6 +8,7 @@ import { CommunityForm } from './community-form'; interface SidebarProps { community: Community; moderators: Array; + admins: Array; } interface SidebarState { @@ -54,24 +55,29 @@ export class Sidebar extends Component { } /f/{community.name} - {community.am_mod && -
    -
  • - edit -
  • - {this.amCreator && +
      + {this.canMod && + <>
    • - {/* delete */} + edit
    • - } + {this.amCreator && +
    • + {/* delete */} +
    • + } + + } + {this.canAdmin &&
    • {!this.props.community.removed ? remove : restore }
    • -
    - } + + } +
{this.state.showRemoveDialog &&
@@ -156,10 +162,13 @@ export class Sidebar extends Component { return this.props.community.creator_id == UserService.Instance.user.id; } - // private get amMod(): boolean { - // return UserService.Instance.loggedIn && - // this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id); - // } + get canMod(): boolean { + return UserService.Instance.user && this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id); + } + + get canAdmin(): boolean { + return UserService.Instance.user && this.props.admins.map(a => a.id).includes(UserService.Instance.user.id); + } handleDeleteClick() { } diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 24bb6157f..23b860745 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -72,6 +72,7 @@ export interface Post { updated?: string; creator_name: string; community_name: string; + community_removed: boolean; number_of_comments: number; score: number; upvotes: number; @@ -350,6 +351,7 @@ export interface GetCommunityResponse { op: string; community: Community; moderators: Array; + admins: Array; }