import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import { formatMessage } from 'services/format'; import { createIssue, createIssueComment, getIssue, updateIssue } from 'services/git'; import { RATE_LIMIT_1_PER_MIN } from 'services/rateLimit'; import { validatorCompiler } from 'services/validation'; import { UAParser } from 'ua-parser-js'; import * as yup from 'yup'; const PostReportBodySchema = yup.object().shape({ reason: yup.string().min(10).max(1000).required(), url: yup.string().max(1000).url().required(), userAgent: yup.string().max(1000).optional(), version: yup .string() .max(10) .matches(/^\d+(\.\d+){0,3}$/) .required(), }); type PostReportBody = yup.InferType; export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { server.post<{ Body: PostReportBody }>( '/report/', { config: { rateLimit: RATE_LIMIT_1_PER_MIN, }, schema: { body: PostReportBodySchema, }, validatorCompiler, }, async (request, reply) => { try { const { reason, url, userAgent, version } = request.body; const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); const issue = await getIssue({ title: hostname }); const ua = new UAParser(userAgent ?? '').getResult(); if (issue) { if (issue.state === 'closed') { await updateIssue({ event: 'reopen', iid: issue.iid, labels: ['bug'], labelsToRemove: issue.labels, }); } await createIssueComment({ description: formatMessage({ reason, ua, url, version }), iid: issue.iid, }); reply.send({ data: issue.web_url, success: true, }); return; } const newIssue = await createIssue({ description: formatMessage({ reason, ua, url, version }), labels: ['bug'], title: hostname, }); reply.send({ data: newIssue.web_url, success: true, }); } catch (error) { reply.send({ errors: [error.message], success: false, }); } } ); done(); };