From 8d65996c51fc79e1d7d279ab41b2d48abf25f949 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 31 May 2024 12:00:57 +0200 Subject: [PATCH 1/3] Step 6: Add validation logic to shout reply --- server/data.ts | 3 ++ server/index.ts | 6 ++++ server/types.ts | 1 + src/components/shout/reply-dialog.tsx | 38 +++++++++++++++++++++++-- src/components/shout/shout.tsx | 3 +- src/domain/index.ts | 5 +++- src/infrastructure/feed/service.test.ts | 6 ++++ src/infrastructure/user/dto.ts | 2 ++ src/infrastructure/user/transform.ts | 6 +++- 9 files changed, 65 insertions(+), 5 deletions(-) diff --git a/server/data.ts b/server/data.ts index 63381ea..49f7cf8 100644 --- a/server/data.ts +++ b/server/data.ts @@ -8,6 +8,7 @@ export const users: DbUser[] = [ handle: "darklord", avatar: "/cdn/avatars/darklord.jpeg", info: "I am the dark lord, the root of all evil. 'Tis I who brought the world to its knees. In blood I was born, and in blood I shall have my vengeance.", + numShoutsPastDay: 3, blockedUserIds: ["user-2"], followsUserIds: ["user-3"], }, @@ -17,6 +18,7 @@ export const users: DbUser[] = [ type: "user", attributes: { handle: "prettypinkpony", + numShoutsPastDay: 4, avatar: "/cdn/avatars/prettypinkpony.jpeg", info: "I like colors. I'm a colorful person (although I'm pretty white *giggles*). I'd like to make this world a better place. And sometimes I feel like the only one who can...", blockedUserIds: ["user-1"], @@ -29,6 +31,7 @@ export const users: DbUser[] = [ attributes: { handle: "fcku", avatar: "/cdn/avatars/fcku.jpeg", + numShoutsPastDay: 2, blockedUserIds: [], followsUserIds: ["user-1", "user-2"], }, diff --git a/server/index.ts b/server/index.ts index 8bfd037..5348fa1 100644 --- a/server/index.ts +++ b/server/index.ts @@ -237,10 +237,16 @@ fastify.post( reply ) { await waitRandomTime(); + + if (req.body.message === "error") { + return reply.status(400).send({ error: "unkown error" }); + } + const user = getUserFromCookie(req.cookies); if (!user) { return reply.status(401).send({ error: true }); } + user.attributes.numShoutsPastDay += 1; const shout: DbShout = { id: `shout-${shouts.length + 1}`, type: "shout", diff --git a/server/types.ts b/server/types.ts index e8e4aaf..89c6702 100644 --- a/server/types.ts +++ b/server/types.ts @@ -9,6 +9,7 @@ export interface DbUser { handle: string; avatar: string; info?: string; + numShoutsPastDay: number; blockedUserIds: UserId[]; followsUserIds: UserId[]; }; diff --git a/src/components/shout/reply-dialog.tsx b/src/components/shout/reply-dialog.tsx index 4d69ba5..07e0fc8 100644 --- a/src/components/shout/reply-dialog.tsx +++ b/src/components/shout/reply-dialog.tsx @@ -18,6 +18,15 @@ import MediaService from "@/infrastructure/media"; import ShoutService from "@/infrastructure/shout"; import UserService from "@/infrastructure/user"; +const ErrorMessages = { + TooManyShouts: + "You have reached the maximum number of shouts per day. Please try again tomorrow.", + RecipientNotFound: "The user you want to reply to does not exist.", + AuthorBlockedByRecipient: + "You can't reply to this user. They have blocked you.", + UnknownError: "An unknown error occurred. Please try again later.", +} as const; + interface ReplyFormElements extends HTMLFormControlsCollection { message: HTMLTextAreaElement; image: HTMLInputElement; @@ -28,15 +37,21 @@ interface ReplyForm extends HTMLFormElement { } interface ReplyDialogProps { + recipientHandle: string; children: React.ReactNode; shoutId: string; } -export function ReplyDialog({ children, shoutId }: ReplyDialogProps) { +export function ReplyDialog({ + recipientHandle, + children, + shoutId, +}: ReplyDialogProps) { const [open, setOpen] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isAuthenticated, setIsAuthenticated] = useState(false); const [hasError, setHasError] = useState(false); + const [replyError, setReplyError] = useState(); useEffect(() => { UserService.getMe() @@ -52,6 +67,20 @@ export function ReplyDialog({ children, shoutId }: ReplyDialogProps) { async function handleSubmit(event: React.FormEvent) { event.preventDefault(); setIsLoading(true); + + const me = await UserService.getMe(); + if (me.numShoutsPastDay >= 5) { + return setReplyError(ErrorMessages.TooManyShouts); + } + + const recipient = await UserService.getUser(recipientHandle); + if (!recipient) { + return setReplyError(ErrorMessages.RecipientNotFound); + } + if (recipient.blockedUserIds.includes(me.id)) { + return setReplyError(ErrorMessages.AuthorBlockedByRecipient); + } + try { const message = event.currentTarget.elements.message.value; const files = event.currentTarget.elements.image.files; @@ -73,7 +102,7 @@ export function ReplyDialog({ children, shoutId }: ReplyDialogProps) { setOpen(false); } catch (error) { - console.error(error); + setReplyError(ErrorMessages.UnknownError); } finally { setIsLoading(false); } @@ -114,6 +143,11 @@ export function ReplyDialog({ children, shoutId }: ReplyDialogProps) { Shout out! + {replyError && ( +
+ {replyError} +
+ )} diff --git a/src/components/shout/shout.tsx b/src/components/shout/shout.tsx index 36873cc..4c77c4d 100644 --- a/src/components/shout/shout.tsx +++ b/src/components/shout/shout.tsx @@ -25,6 +25,7 @@ const defaultAuthor: User = { handle: "Deleted", avatar: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgMGMtNi42MjcgMC0xMiA1LjM3My0xMiAxMnM1LjM3MyAxMiAxMiAxMiAxMi01LjM3MyAxMi0xMi01LjM3My0xMi0xMi0xMnptOSAxMmMwIDEuOTQtLjYyNCAzLjczNS0xLjY3MiA1LjIwN2wtMTIuNTM1LTEyLjUzNWMxLjQ3Mi0xLjA0OCAzLjI2Ny0xLjY3MiA1LjIwNy0xLjY3MiA0Ljk2MiAwIDkgNC4wMzggOSA5em0tMTggMGMwLTEuOTQuNjI0LTMuNzM1IDEuNjcyLTUuMjA3bDEyLjUzNCAxMi41MzRjLTEuNDcxIDEuMDQ5LTMuMjY2IDEuNjczLTUuMjA2IDEuNjczLTQuOTYyIDAtOS00LjAzOC05LTl6Ii8+PC9zdmc+", + blockedUserIds: [], followerIds: [], }; @@ -59,7 +60,7 @@ export function Shout({ shout, author = defaultAuthor, image }: ShoutProps) { )} - +