Community moderation fixes.
- Don't show banned communities on main post list. Fixes #95 - Add back in community moderation and editing. Fixes #92
This commit is contained in:
parent
273a38f61f
commit
016920aeb7
9 changed files with 88 additions and 49 deletions
|
@ -16,6 +16,7 @@ with all_post as
|
||||||
p.*,
|
p.*,
|
||||||
(select name from user_ where p.creator_id = user_.id) as creator_name,
|
(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 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,
|
(select count(*) from comment where comment.post_id = p.id) as number_of_comments,
|
||||||
coalesce(sum(pl.score), 0) as score,
|
coalesce(sum(pl.score), 0) as score,
|
||||||
count (case when pl.score = 1 then 1 else null end) as upvotes,
|
count (case when pl.score = 1 then 1 else null end) as upvotes,
|
||||||
|
|
|
@ -25,6 +25,7 @@ table! {
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
community_name -> Varchar,
|
community_name -> Varchar,
|
||||||
|
community_removed -> Bool,
|
||||||
number_of_comments -> BigInt,
|
number_of_comments -> BigInt,
|
||||||
score -> BigInt,
|
score -> BigInt,
|
||||||
upvotes -> BigInt,
|
upvotes -> BigInt,
|
||||||
|
@ -54,6 +55,7 @@ pub struct PostView {
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
pub community_name: String,
|
pub community_name: String,
|
||||||
|
pub community_removed: bool,
|
||||||
pub number_of_comments: i64,
|
pub number_of_comments: i64,
|
||||||
pub score: i64,
|
pub score: i64,
|
||||||
pub upvotes: i64,
|
pub upvotes: i64,
|
||||||
|
@ -133,13 +135,11 @@ impl PostView {
|
||||||
.order_by(score.desc())
|
.order_by(score.desc())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// TODO make sure community removed isn't fetched either
|
|
||||||
|
|
||||||
query = query
|
query = query
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.filter(removed.eq(false));
|
.filter(removed.eq(false))
|
||||||
|
.filter(community_removed.eq(false));
|
||||||
|
|
||||||
query.load::<Self>(conn)
|
query.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,7 @@ mod tests {
|
||||||
removed: false,
|
removed: false,
|
||||||
locked: false,
|
locked: false,
|
||||||
community_name: community_name.to_owned(),
|
community_name: community_name.to_owned(),
|
||||||
|
community_removed: false,
|
||||||
number_of_comments: 0,
|
number_of_comments: 0,
|
||||||
score: 1,
|
score: 1,
|
||||||
upvotes: 1,
|
upvotes: 1,
|
||||||
|
@ -280,6 +281,7 @@ mod tests {
|
||||||
creator_name: user_name.to_owned(),
|
creator_name: user_name.to_owned(),
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
community_name: community_name.to_owned(),
|
community_name: community_name.to_owned(),
|
||||||
|
community_removed: false,
|
||||||
number_of_comments: 0,
|
number_of_comments: 0,
|
||||||
score: 1,
|
score: 1,
|
||||||
upvotes: 1,
|
upvotes: 1,
|
||||||
|
|
|
@ -196,7 +196,8 @@ pub struct GetCommunity {
|
||||||
pub struct GetCommunityResponse {
|
pub struct GetCommunityResponse {
|
||||||
op: String,
|
op: String,
|
||||||
community: CommunityView,
|
community: CommunityView,
|
||||||
moderators: Vec<CommunityModeratorView>
|
moderators: Vec<CommunityModeratorView>,
|
||||||
|
admins: Vec<UserView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -1165,13 +1166,16 @@ impl Perform for GetCommunity {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let admins = UserView::admins(&conn)?;
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(
|
Ok(
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&GetCommunityResponse {
|
&GetCommunityResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
community: community_view,
|
community: community_view,
|
||||||
moderators: moderators
|
moderators: moderators,
|
||||||
|
admins: admins,
|
||||||
}
|
}
|
||||||
)?
|
)?
|
||||||
)
|
)
|
||||||
|
@ -1817,11 +1821,24 @@ impl Perform for EditCommunity {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify its a mod
|
// Verify its a mod
|
||||||
let moderator_view = CommunityModeratorView::for_community(&conn, self.edit_id)?;
|
let mut editors: Vec<i32> = Vec::new();
|
||||||
let mod_ids: Vec<i32> = moderator_view.into_iter().map(|m| m.user_id).collect();
|
editors.append(
|
||||||
if !mod_ids.contains(&user_id) {
|
&mut CommunityModeratorView::for_community(&conn, self.edit_id)
|
||||||
return Err(self.error("Incorrect creator."))?
|
?
|
||||||
};
|
.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 {
|
let community_form = CommunityForm {
|
||||||
name: self.name.to_owned(),
|
name: self.name.to_owned(),
|
||||||
|
|
|
@ -210,13 +210,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
return UserService.Instance.user && this.props.node.comment.creator_id == UserService.Instance.user.id;
|
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 {
|
get isMod(): boolean {
|
||||||
return this.props.moderators && isMod(this.props.moderators.map(m => m.user_id), this.props.node.comment.creator_id);
|
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<CommentNodeProps, CommentNodeState> {
|
||||||
return this.props.admins && isMod(this.props.admins.map(a => a.id), this.props.node.comment.creator_id);
|
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 {
|
get canAdmin(): boolean {
|
||||||
return this.props.admins && canMod(UserService.Instance.user, this.props.admins.map(a => a.id), this.props.node.comment.creator_id);
|
return this.props.admins && canMod(UserService.Instance.user, this.props.admins.map(a => a.id), this.props.node.comment.creator_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component } from 'inferno';
|
import { Component } from 'inferno';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
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 { WebSocketService } from '../services';
|
||||||
import { PostListings } from './post-listings';
|
import { PostListings } from './post-listings';
|
||||||
import { Sidebar } from './sidebar';
|
import { Sidebar } from './sidebar';
|
||||||
|
@ -11,6 +11,7 @@ interface State {
|
||||||
community: CommunityI;
|
community: CommunityI;
|
||||||
communityId: number;
|
communityId: number;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
|
admins: Array<UserView>;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +30,11 @@ export class Community extends Component<any, State> {
|
||||||
number_of_subscribers: null,
|
number_of_subscribers: null,
|
||||||
number_of_posts: null,
|
number_of_posts: null,
|
||||||
number_of_comments: null,
|
number_of_comments: null,
|
||||||
published: null
|
published: null,
|
||||||
|
removed: null,
|
||||||
},
|
},
|
||||||
moderators: [],
|
moderators: [],
|
||||||
|
admins: [],
|
||||||
communityId: Number(this.props.match.params.id),
|
communityId: Number(this.props.match.params.id),
|
||||||
loading: true
|
loading: true
|
||||||
}
|
}
|
||||||
|
@ -71,7 +74,11 @@ export class Community extends Component<any, State> {
|
||||||
<PostListings communityId={this.state.communityId} />
|
<PostListings communityId={this.state.communityId} />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<Sidebar community={this.state.community} moderators={this.state.moderators} />
|
<Sidebar
|
||||||
|
community={this.state.community}
|
||||||
|
moderators={this.state.moderators}
|
||||||
|
admins={this.state.admins}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -90,6 +97,7 @@ export class Community extends Component<any, State> {
|
||||||
let res: GetCommunityResponse = msg;
|
let res: GetCommunityResponse = msg;
|
||||||
this.state.community = res.community;
|
this.state.community = res.community;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = res.moderators;
|
||||||
|
this.state.admins = res.admins;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditCommunity) {
|
} else if (op == UserOperation.EditCommunity) {
|
||||||
|
|
|
@ -174,6 +174,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
return UserService.Instance.user && this.props.post.creator_id == UserService.Instance.user.id;
|
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 {
|
get canMod(): boolean {
|
||||||
|
|
||||||
if (this.props.editable) {
|
if (this.props.editable) {
|
||||||
|
@ -185,18 +193,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
} else return false;
|
} 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) {
|
handlePostLike(i: PostListing) {
|
||||||
|
|
||||||
let form: CreatePostLikeForm = {
|
let form: CreatePostLikeForm = {
|
||||||
|
|
|
@ -148,7 +148,11 @@ export class Post extends Component<any, PostState> {
|
||||||
sidebar() {
|
sidebar() {
|
||||||
return (
|
return (
|
||||||
<div class="sticky-top">
|
<div class="sticky-top">
|
||||||
<Sidebar community={this.state.community} moderators={this.state.moderators} />
|
<Sidebar
|
||||||
|
community={this.state.community}
|
||||||
|
moderators={this.state.moderators}
|
||||||
|
admins={this.state.admins}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import { mdToHtml, getUnixTime } from '../utils';
|
import { mdToHtml, getUnixTime } from '../utils';
|
||||||
import { CommunityForm } from './community-form';
|
import { CommunityForm } from './community-form';
|
||||||
|
@ -8,6 +8,7 @@ import { CommunityForm } from './community-form';
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
community: Community;
|
community: Community;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
|
admins: Array<UserView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarState {
|
interface SidebarState {
|
||||||
|
@ -54,24 +55,29 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
}
|
}
|
||||||
</h5>
|
</h5>
|
||||||
<Link className="text-muted" to={`/community/${community.id}`}>/f/{community.name}</Link>
|
<Link className="text-muted" to={`/community/${community.id}`}>/f/{community.name}</Link>
|
||||||
{community.am_mod &&
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
{this.canMod &&
|
||||||
<li className="list-inline-item">
|
<>
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
|
||||||
</li>
|
|
||||||
{this.amCreator &&
|
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
{/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */}
|
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
||||||
</li>
|
</li>
|
||||||
}
|
{this.amCreator &&
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{this.canAdmin &&
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
{!this.props.community.removed ?
|
{!this.props.community.removed ?
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
|
||||||
}
|
}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
|
||||||
}
|
}
|
||||||
|
</ul>
|
||||||
{this.state.showRemoveDialog &&
|
{this.state.showRemoveDialog &&
|
||||||
<form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
|
<form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -156,10 +162,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
return this.props.community.creator_id == UserService.Instance.user.id;
|
return this.props.community.creator_id == UserService.Instance.user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private get amMod(): boolean {
|
get canMod(): boolean {
|
||||||
// return UserService.Instance.loggedIn &&
|
return UserService.Instance.user && this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id);
|
||||||
// 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() {
|
handleDeleteClick() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ export interface Post {
|
||||||
updated?: string;
|
updated?: string;
|
||||||
creator_name: string;
|
creator_name: string;
|
||||||
community_name: string;
|
community_name: string;
|
||||||
|
community_removed: boolean;
|
||||||
number_of_comments: number;
|
number_of_comments: number;
|
||||||
score: number;
|
score: number;
|
||||||
upvotes: number;
|
upvotes: number;
|
||||||
|
@ -350,6 +351,7 @@ export interface GetCommunityResponse {
|
||||||
op: string;
|
op: string;
|
||||||
community: Community;
|
community: Community;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
|
admins: Array<UserView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in a new issue