diff --git a/pages/api/v2/users/[id].ts b/pages/api/v2/users/[id]/index.ts similarity index 100% rename from pages/api/v2/users/[id].ts rename to pages/api/v2/users/[id]/index.ts diff --git a/pages/api/v2/users/[id]/report.ts b/pages/api/v2/users/[id]/report.ts new file mode 100644 index 0000000..7cab226 --- /dev/null +++ b/pages/api/v2/users/[id]/report.ts @@ -0,0 +1,52 @@ +import { NextApiRequest} from 'next' +import rateLimit from 'express-rate-limit' + +import { get } from '@utils/Query' +import RequestHandler from '@utils/RequestHandler' +import ResponseWrapper from '@utils/ResponseWrapper' +import { ReportSchema, Report} from '@utils/Yup' +import { getReportChannel } from '@utils/DiscordBot' +import { checkToken } from '@utils/Csrf' + +const limiter = rateLimit({ + windowMs: 5 * 60 * 1000, + max: 3, + statusCode: 429, + handler: (_req, res) => ResponseWrapper(res, { code: 429 }), + keyGenerator: (req) => req.headers.authorization, + skip: (_req, res) => { + res.removeHeader('X-RateLimit-Global') + return false + } +}) + +const UserReport = RequestHandler().post(limiter) + .post(async (req: PostApiRequest, res) => { + const user = await get.Authorization(req.cookies.token) + if(!user) return ResponseWrapper(res, { code: 401 }) + const userInfo = await get.user.load(req.query.id) + if(!userInfo) return ResponseWrapper(res, { code: 404, message: '존재하지 않는 유저입니다.' }) + const csrfValidated = checkToken(req, res, req.body._csrf) + if (!csrfValidated) return + if(!req.body) return ResponseWrapper(res, { code: 400 }) + const validated: Report = await ReportSchema.validate(req.body, { abortEarly: false }) + .then(el => el) + .catch(e => { + ResponseWrapper(res, { code: 400, errors: e.errors }) + return null + }) + + if(!validated) return + await getReportChannel().send(`Reported by <@${user}> (${user})\nReported **${userInfo.username}**#${userInfo.tag} <@${userInfo.id}> (${userInfo.id})\nCategory ${req.body.category}\nDesc\n\`\`\`${req.body.description}\`\`\``, { allowedMentions: { parse: ['users'] }}) + return ResponseWrapper(res, { code: 200, message: '성공적으로 처리되었습니다.' }) + }) + + +interface PostApiRequest extends NextApiRequest { + body: Report | null + query: { + id: string + } +} + +export default UserReport \ No newline at end of file diff --git a/pages/bots/[id]/index.tsx b/pages/bots/[id]/index.tsx index e6d153d..9077233 100644 --- a/pages/bots/[id]/index.tsx +++ b/pages/bots/[id]/index.tsx @@ -193,6 +193,7 @@ const Bots: NextPage = ({ data, date, user, theme, csrfToken, setThem { reportRes?.code === 200 ?

성공적으로 신고하였습니다!

+

더 자세한 설명이 필요할 수 있습니다! 공식 디스코드에 참여해주세요

: { const res = await Fetch(`/bots/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) }) setReportRes(res) diff --git a/pages/users/[id].tsx b/pages/users/[id].tsx index bc6538f..7da0397 100644 --- a/pages/users/[id].tsx +++ b/pages/users/[id].tsx @@ -1,14 +1,20 @@ import { NextPage, NextPageContext } from 'next' +import { useState } from 'react' import dynamic from 'next/dynamic' import { SnowflakeUtil } from 'discord.js' import { ParsedUrlQuery } from 'querystring' import { josa } from 'josa' +import { Field, Form, Formik } from 'formik' -import { Bot, User } from '@types' +import { Bot, User, ResponseProps, Theme } from '@types' import * as Query from '@utils/Query' import { checkUserFlag } from '@utils/Tools' +import { getToken } from '@utils/Csrf' +import Fetch from '@utils/Fetch' +import { ReportSchema } from '@utils/Yup' import NotFound from '../404' +import { reportCats } from '@utils/Constants' const Container = dynamic(() => import('@components/Container')) const SEO = dynamic(() => import('@components/SEO')) @@ -19,8 +25,14 @@ const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid')) const Tag = dynamic(() => import('@components/Tag')) const Advertisement = dynamic(() => import('@components/Advertisement')) const Tooltip = dynamic(() => import('@components/Tooltip')) +const Message = dynamic(() => import('@components/Message')) +const Modal = dynamic(() => import('@components/Modal')) +const Button = dynamic(() => import('@components/Button')) +const TextArea = dynamic(() => import('@components/Form/TextArea')) -const Users: NextPage = ({ data }) => { +const Users: NextPage = ({ data, csrfToken, theme }) => { + const [ reportModal, setReportModal ] = useState(false) + const [ reportRes, setReportRes ] = useState>(null) if (!data?.id) return return ( @@ -47,10 +59,8 @@ const Users: NextPage = ({ data }) => {
-

{data.username}

- #{data.tag} -
-
+

{data.username}#{data.tag}

+
{checkUserFlag(data.flags, 'staff') && (
@@ -66,7 +76,6 @@ const Users: NextPage = ({ data }) => { )}
-
{data.github && ( = ({ data }) => { href={`https://github.com/${data.github}`} /> )} + + { + setReportModal(false) + setReportRes(null) + }} full dark={theme === 'dark'}> + { + reportRes?.code === 200 ? +

성공적으로 신고하였습니다!

+

더 자세한 설명이 필요할 수 있습니다! 공식 디스코드에 참여해주세요

+
: { + const res = await Fetch(`/users/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) }) + setReportRes(res) + }} validationSchema={ReportSchema} initialValues={{ + category: null, + description: '', + _csrf: csrfToken + }}> + { + ({ errors, touched, values, setFieldValue }) => ( +
+
+ { + reportRes &&
+ +

{reportRes.message}

+
    + {reportRes.errors?.map((el, n) =>
  • {el}
  • )} +
+
+
+ } +

신고 구분

+

해당되는 항복을 선택해주세요.

+ { + reportCats.map(el => +
+ +
+ ) + } +
{errors.category && touched.category ? errors.category : null}
+

설명

+

신고하시는 내용을 자세하게 설명해주세요.

+