Skip to content

Commit

Permalink
Bepro 2258 limit the size of the comments on frontend and backend (#506)
Browse files Browse the repository at this point in the history
* validate comment length on fe

* validate comment length on be

* improve error feedback
  • Loading branch information
vhcsilva authored Sep 6, 2024
1 parent b4a02ed commit 1cc5a70
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 41 deletions.
18 changes: 15 additions & 3 deletions components/bounty/comments/input-comment/controller.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import {ChangeEvent, useState} from "react";

import { AxiosError } from "axios";
import {useTranslation} from "next-i18next";

import InputCommentView from "components/bounty/comments/input-comment/view";

import { COMMENT_MAX_LENGTH } from "helpers/constants";
import {QueryKeys} from "helpers/query-keys";

import {IdsComment, TypeComment} from "interfaces/comments";

import {CreateComment} from "x-hooks/api/comments";
import { useToastStore } from "x-hooks/stores/toasts/toasts.store";
import useReactQueryMutation from "x-hooks/use-react-query-mutation";
import { useTaskSubscription } from "x-hooks/use-task-subscription";

Expand All @@ -34,7 +37,10 @@ export default function InputComment({
deliverable: QueryKeys.deliverable(ids?.deliverableId?.toString()),
proposal: QueryKeys.proposalComments(ids?.proposalId?.toString())
}[type];
const commentLength = comment?.length || 0;
const error = commentLength > COMMENT_MAX_LENGTH ? "max-length" : null;

const { addError, addSuccess } = useToastStore();
const { refresh: refreshSubscriptions } = useTaskSubscription();
const { mutate: addComment } = useReactQueryMutation({
queryKey: queryKey,
Expand All @@ -43,9 +49,12 @@ export default function InputComment({
...ids,
type
}),
toastSuccess: t("bounty:actions.comment.success"),
toastError: t("bounty:actions.comment.error"),
onSuccess: () => {
onSettled: (data, error: AxiosError<{ message: string }>) => {
if (error) {
addError(t("actions.failed"), `${error?.response?.data?.message}`);
return;
}
addSuccess(t("actions.success"), t("bounty:actions.comment.success"));
setComment("");
refreshSubscriptions();
}
Expand All @@ -61,6 +70,9 @@ export default function InputComment({
userAddress={userAddress}
avatarHash={avatar}
comment={comment}
commentLength={commentLength}
maxLength={COMMENT_MAX_LENGTH}
error={error}
onCommentChange={onCommentChange}
onCommentSubmit={addComment}
/>
Expand Down
90 changes: 56 additions & 34 deletions components/bounty/comments/input-comment/view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {useTranslation} from "next-i18next";

import AvatarOrIdenticon from "components/avatar-or-identicon";
import Button from "components/button";
import If from "components/If";

import {truncateAddress} from "helpers/truncate-address";

Expand All @@ -12,55 +13,76 @@ export default function InputCommentView({
userAddress,
avatarHash,
comment,
commentLength,
maxLength,
error = null,
onCommentSubmit,
onCommentChange ,
}: {
handle?: string;
userAddress: string;
avatarHash: string;
comment: string;
commentLength: number;
maxLength: number;
error: "max-length" | null;
onCommentSubmit: (...props) => void;
onCommentChange : (e: ChangeEvent<HTMLTextAreaElement>) => void
}) {
const { t } = useTranslation("common");

const borderColor = error ? "danger" : "gray-700";
const errorMessage = {
"max-length": t("errors.comment.max-length", { max: maxLength }),
}[error];

return (
<div className="border-radius-8 p-3 bg-gray-850 mb-3 border-gray-700 border">
<div className="d-flex align-items-center mb-2">
<div className="d-flex align-items-center text-truncate">
<AvatarOrIdenticon
user={{
avatar: avatarHash,
handle,
address: userAddress,
}}
size="xsm"
/>
<span className="xs-medium ms-2 text-truncate">
{handle ? `@${handle}` : truncateAddress(userAddress)}{" "}
</span>
<>
<div className={`border-radius-8 p-3 bg-gray-850 border border-${borderColor}`}>
<div className="d-flex align-items-center justify-content-between mb-2">
<div className="d-flex align-items-center text-truncate">
<AvatarOrIdenticon
user={{
avatar: avatarHash,
handle,
address: userAddress,
}}
size="xsm"
/>
<span className="xs-medium ms-2 text-truncate">
{handle ? `@${handle}` : truncateAddress(userAddress)}{" "}
</span>
</div>

<div className="xs-medium text-gray-300">
{commentLength}/{maxLength}
</div>
</div>
</div>
<textarea
tabIndex={0}
className="ps-0 form-control input-comment"
rows={2}
data-testid="comments-textarea"
placeholder={t("comments.input.placeholder")}
value={comment}
onChange={onCommentChange}
/>
<textarea
tabIndex={0}
className="ps-0 form-control input-comment"
rows={2}
data-testid="comments-textarea"
placeholder={t("comments.input.placeholder")}
value={comment}
onChange={onCommentChange}
/>

<div className="d-flex justify-content-end mt-2">
<Button
className="btn-comment"
data-testid="comments-btn"
onClick={onCommentSubmit}
disabled={!comment?.length}
>
{t("comments.button")}
</Button>
<div className="d-flex justify-content-end mt-2">
<Button
className="btn-comment"
data-testid="comments-btn"
onClick={onCommentSubmit}
disabled={!comment?.length || !!error}
>
{t("comments.button")}
</Button>
</div>
</div>
</div>

<If condition={!!error}>
<small className="xs-small text-danger">{errorMessage}</small>
</If>
</>
);
}
4 changes: 3 additions & 1 deletion helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,6 @@ export const SPAM_TERMS = [
/^\./, /\.\w{0,3}$/, /robots/
]

export const UNSUBSCRIBE_EVENT = typeof window === "undefined" ? null : new CustomEvent("task-unsubscribe");
export const UNSUBSCRIBE_EVENT = typeof window === "undefined" ? null : new CustomEvent("task-unsubscribe");

export const COMMENT_MAX_LENGTH = 1000;
5 changes: 4 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,10 @@
"failed-load-network": "failed to load marketplace",
"file-size-exceeded": "Maximum file size exceeded",
"file-not-supported": "File type not supported",
"accepted-types": "Accepted types are {{types}}"
"accepted-types": "Accepted types are {{types}}",
"comment": {
"max-length": "Comment must have a maximum of {{max}} characters."
}
},
"warnings": {
"await-load-network": "await for a loading marketplace"
Expand Down
6 changes: 4 additions & 2 deletions server/common/comments/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {IncludeOptions, Op, Sequelize, WhereOptions} from "sequelize";

import models from "db/models";

import { COMMENT_MAX_LENGTH } from "helpers/constants";
import { truncateAddress } from "helpers/truncate-address";

import {HttpBadRequestError, HttpConflictError, HttpNotFoundError} from "server/errors/http-errors";
Expand All @@ -27,10 +28,12 @@ export default async function post(req: NextApiRequest, res: NextApiResponse) {

const foundOrValid = (v) => v ? 'found' : 'valid';

if (comment?.length > COMMENT_MAX_LENGTH)
throw new HttpBadRequestError(`Comment exceeds max length of ${COMMENT_MAX_LENGTH} characters`);

if (!["issue", "deliverable", "proposal", "review"].includes(type))
throw new HttpBadRequestError("type does not exist")


if (!issueId || !isValidNumber(issueId))
throw new HttpNotFoundError(`issueId not ${foundOrValid(!issueId)}`);

Expand All @@ -40,7 +43,6 @@ export default async function post(req: NextApiRequest, res: NextApiResponse) {
if (type === "proposal" && (!proposalId || !isValidNumber(proposalId)))
throw new HttpNotFoundError(`proposalId not ${foundOrValid(!proposalId)}`)


const user = context.user;

if (!user)
Expand Down

0 comments on commit 1cc5a70

Please sign in to comment.