comment reporting for moderation
This commit is contained in:
parent
1a87cadc4d
commit
8ef9a7e5e4
|
@ -79,6 +79,24 @@ def get_proposal_comments(proposal_id, page, filters, search, sort):
|
||||||
return page
|
return page
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/<proposal_id>/comments/<comment_id>/report", methods=["PUT"])
|
||||||
|
@requires_email_verified_auth
|
||||||
|
@endpoint.api()
|
||||||
|
def report_proposal_comment(proposal_id, comment_id):
|
||||||
|
# Make sure proposal exists
|
||||||
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
||||||
|
if not proposal:
|
||||||
|
return {"message": "No proposal matching id"}, 404
|
||||||
|
|
||||||
|
comment = Comment.query.filter_by(id=comment_id).first()
|
||||||
|
if not comment:
|
||||||
|
return {"message": "Comment doesn’t exist"}, 404
|
||||||
|
|
||||||
|
comment.report(True)
|
||||||
|
db.session.commit()
|
||||||
|
return None, 200
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/<proposal_id>/comments", methods=["POST"])
|
@blueprint.route("/<proposal_id>/comments", methods=["POST"])
|
||||||
@requires_email_verified_auth
|
@requires_email_verified_auth
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
|
@ -138,23 +156,6 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id):
|
||||||
return dumped_comment, 201
|
return dumped_comment, 201
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/<proposal_id>/comments/<comment_id>/report", methods=["PUT"])
|
|
||||||
@requires_email_verified_auth
|
|
||||||
@endpoint.api
|
|
||||||
def report_proposal_comment(proposal_id, comment_id):
|
|
||||||
# Make sure proposal exists
|
|
||||||
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
|
||||||
if not proposal:
|
|
||||||
return {"message": "No proposal matching id"}, 404
|
|
||||||
|
|
||||||
comment = Comment.query.filter_by(id=comment_id).first()
|
|
||||||
if not comment:
|
|
||||||
return {"message": "Comment doesn’t exist"}, 404
|
|
||||||
|
|
||||||
comment.report(True)
|
|
||||||
return None, 200
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/", methods=["GET"])
|
@blueprint.route("/", methods=["GET"])
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('page', type=int, required=False),
|
parameter('page', type=int, required=False),
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Button } from 'antd';
|
import { Button, message } from 'antd';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Markdown from 'components/Markdown';
|
import Markdown from 'components/Markdown';
|
||||||
import UserAvatar from 'components/UserAvatar';
|
import UserAvatar from 'components/UserAvatar';
|
||||||
import MarkdownEditor, { MARKDOWN_TYPE } from 'components/MarkdownEditor';
|
import MarkdownEditor, { MARKDOWN_TYPE } from 'components/MarkdownEditor';
|
||||||
import { postProposalComment } from 'modules/proposals/actions';
|
import { postProposalComment, reportProposalComment } from 'modules/proposals/actions';
|
||||||
import { getIsSignedIn } from 'modules/auth/selectors';
|
import { getIsSignedIn } from 'modules/auth/selectors';
|
||||||
import { Comment as IComment } from 'types';
|
import { Comment as IComment } from 'types';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
@ -24,6 +24,7 @@ interface StateProps {
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
postProposalComment: typeof postProposalComment;
|
postProposalComment: typeof postProposalComment;
|
||||||
|
reportProposalComment: typeof reportProposalComment;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OwnProps & StateProps & DispatchProps;
|
type Props = OwnProps & StateProps & DispatchProps;
|
||||||
|
@ -76,7 +77,12 @@ class Comment extends React.Component<Props> {
|
||||||
<a className="Comment-controls-button" onClick={this.toggleReply}>
|
<a className="Comment-controls-button" onClick={this.toggleReply}>
|
||||||
{isReplying ? 'Cancel' : 'Reply'}
|
{isReplying ? 'Cancel' : 'Reply'}
|
||||||
</a>
|
</a>
|
||||||
{/*<a className="Comment-controls-button">Report</a>*/}
|
{!comment.hidden &&
|
||||||
|
!comment.reported && (
|
||||||
|
<a className="Comment-controls-button" onClick={this.report}>
|
||||||
|
Report
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -120,6 +126,16 @@ class Comment extends React.Component<Props> {
|
||||||
const { reply } = this.state;
|
const { reply } = this.state;
|
||||||
this.props.postProposalComment(comment.proposalId, reply, comment.id);
|
this.props.postProposalComment(comment.proposalId, reply, comment.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private report = async () => {
|
||||||
|
const { proposalId, id } = this.props.comment;
|
||||||
|
const res = await this.props.reportProposalComment(proposalId, id);
|
||||||
|
if ((res as any).error) {
|
||||||
|
message.error('Problem reporting comment: ' + (res as any).payload);
|
||||||
|
} else {
|
||||||
|
message.success('Comment reported');
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectedComment = connect<StateProps, DispatchProps, OwnProps, AppState>(
|
const ConnectedComment = connect<StateProps, DispatchProps, OwnProps, AppState>(
|
||||||
|
@ -130,6 +146,7 @@ const ConnectedComment = connect<StateProps, DispatchProps, OwnProps, AppState>(
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
postProposalComment,
|
postProposalComment,
|
||||||
|
reportProposalComment,
|
||||||
},
|
},
|
||||||
)(Comment);
|
)(Comment);
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import { Button, message, Skeleton, Alert } from 'antd';
|
import { Button, message, Skeleton, Alert } from 'antd';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Proposal } from 'types';
|
import { Proposal } from 'types';
|
||||||
import {
|
import { fetchProposalComments, postProposalComment } from 'modules/proposals/actions';
|
||||||
fetchProposalComments,
|
|
||||||
postProposalComment,
|
|
||||||
reportProposalComment,
|
|
||||||
} from 'modules/proposals/actions';
|
|
||||||
import { getIsVerified, getIsSignedIn } from 'modules/auth/selectors';
|
import { getIsVerified, getIsSignedIn } from 'modules/auth/selectors';
|
||||||
import Comments from 'components/Comments';
|
import Comments from 'components/Comments';
|
||||||
import Placeholder from 'components/Placeholder';
|
import Placeholder from 'components/Placeholder';
|
||||||
|
@ -29,7 +25,6 @@ interface StateProps {
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
fetchProposalComments: typeof fetchProposalComments;
|
fetchProposalComments: typeof fetchProposalComments;
|
||||||
postProposalComment: typeof postProposalComment;
|
postProposalComment: typeof postProposalComment;
|
||||||
reportProposalComment: typeof reportProposalComment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = DispatchProps & OwnProps & StateProps;
|
type Props = DispatchProps & OwnProps & StateProps;
|
||||||
|
@ -182,6 +177,5 @@ export default connect<StateProps, DispatchProps, OwnProps, AppState>(
|
||||||
{
|
{
|
||||||
fetchProposalComments,
|
fetchProposalComments,
|
||||||
postProposalComment,
|
postProposalComment,
|
||||||
reportProposalComment,
|
|
||||||
},
|
},
|
||||||
)(ProposalComments);
|
)(ProposalComments);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
getProposal,
|
getProposal,
|
||||||
getProposalComments,
|
getProposalComments,
|
||||||
getProposalUpdates,
|
getProposalUpdates,
|
||||||
|
reportProposalComment as apiReportProposalComment,
|
||||||
getProposalContributions,
|
getProposalContributions,
|
||||||
postProposalComment as apiPostProposalComment,
|
postProposalComment as apiPostProposalComment,
|
||||||
requestProposalPayout,
|
requestProposalPayout,
|
||||||
|
@ -209,9 +210,23 @@ export function reportProposalComment(
|
||||||
proposalId: Proposal['proposalId'],
|
proposalId: Proposal['proposalId'],
|
||||||
commentId: Comment['id'],
|
commentId: Comment['id'],
|
||||||
) {
|
) {
|
||||||
return (dispatch: Dispatch<any>) => {
|
return async (dispatch: Dispatch<any>) => {
|
||||||
// dispatch({
|
dispatch({ type: types.REPORT_PROPOSAL_COMMENT_PENDING, payload: { commentId } });
|
||||||
// type: types.POST_PROPOSAL_COMMENT,
|
|
||||||
// })
|
try {
|
||||||
|
await apiReportProposalComment(proposalId, commentId);
|
||||||
|
return dispatch({
|
||||||
|
type: types.REPORT_PROPOSAL_COMMENT_FULFILLED,
|
||||||
|
payload: {
|
||||||
|
commentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return dispatch({
|
||||||
|
type: types.REPORT_PROPOSAL_COMMENT_REJECTED,
|
||||||
|
payload: err.message || err.toString(),
|
||||||
|
error: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,42 @@ function addPostedComment(state: ProposalState, payload: PostCommentPayload) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateCommentInStore(
|
||||||
|
state: ProposalState,
|
||||||
|
commentId: Comment['id'],
|
||||||
|
update: Partial<Comment>,
|
||||||
|
) {
|
||||||
|
// clone so we can mutate with great abandon!
|
||||||
|
const pages = cloneDeep(state.detailComments.pages);
|
||||||
|
// recursive populate replies for nested comment
|
||||||
|
const f = (id: number, p: Comment) => {
|
||||||
|
if (p.id === id) {
|
||||||
|
Object.entries(update).forEach(([k, v]) => {
|
||||||
|
(p as any)[k] = v;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
p.replies.forEach(x => f(id, x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// pages > page > comments
|
||||||
|
pages.forEach(p =>
|
||||||
|
p.forEach(c => {
|
||||||
|
f(commentId, c);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isPostCommentPending: false,
|
||||||
|
detailComments: {
|
||||||
|
...state.detailComments,
|
||||||
|
pages,
|
||||||
|
total: state.detailComments.total + 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default (state = INITIAL_STATE, action: any) => {
|
export default (state = INITIAL_STATE, action: any) => {
|
||||||
const { payload } = action;
|
const { payload } = action;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
@ -336,6 +372,9 @@ export default (state = INITIAL_STATE, action: any) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case types.REPORT_PROPOSAL_COMMENT_FULFILLED:
|
||||||
|
return updateCommentInStore(state, payload.commentId, { reported: true });
|
||||||
|
|
||||||
case types.PROPOSAL_UPDATES_PENDING:
|
case types.PROPOSAL_UPDATES_PENDING:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
Loading…
Reference in New Issue