From deea21126bdc893ae7ff956d8b867232fa65a362 Mon Sep 17 00:00:00 2001 From: wanhose Date: Wed, 9 Oct 2024 01:52:18 +0200 Subject: [PATCH] feat(api): add issues and version endpoints --- packages/api/src/index.ts | 10 ++- packages/api/src/routes/v1/entries.ts | 4 +- packages/api/src/routes/v1/report.ts | 2 +- packages/api/src/routes/v2/data.ts | 10 +-- packages/api/src/routes/v2/report.ts | 2 +- packages/api/src/routes/v3/data.ts | 6 +- packages/api/src/routes/v4/data.ts | 44 +++++----- packages/api/src/routes/v5/data.ts | 1 + packages/api/src/routes/{v4 => v5}/issues.ts | 0 packages/api/src/routes/v5/report.ts | 90 ++++++++++++++++++++ packages/api/src/routes/v5/version.ts | 27 ++++++ packages/api/src/services/environment.ts | 1 + 12 files changed, 158 insertions(+), 39 deletions(-) create mode 100644 packages/api/src/routes/v5/data.ts rename packages/api/src/routes/{v4 => v5}/issues.ts (100%) create mode 100644 packages/api/src/routes/v5/report.ts create mode 100644 packages/api/src/routes/v5/version.ts diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index e5e28fe..788b2d2 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -8,8 +8,11 @@ import v2ReportRoutes from 'routes/v2/report'; import v3DataRoutes from 'routes/v3/data'; import v3ReportRoutes from 'routes/v3/report'; import v4DataRoutes from 'routes/v4/data'; -import v4IssuesRoutes from 'routes/v4/issues'; import v4ReportRoutes from 'routes/v4/report'; +import v5DataRoutes from 'routes/v5/data'; +import v5IssuesRoutes from 'routes/v5/issues'; +import v5ReportRoutes from 'routes/v5/report'; +import v5VersionRoutes from 'routes/v5/version'; import environment from 'services/environment'; const server = fastify({ logger: true }); @@ -35,8 +38,11 @@ server.register(v2ReportRoutes, { prefix: '/rest/v2' }); server.register(v3DataRoutes, { prefix: '/rest/v3' }); server.register(v3ReportRoutes, { prefix: '/rest/v3' }); server.register(v4DataRoutes, { prefix: '/rest/v4' }); -server.register(v4IssuesRoutes, { prefix: '/rest/v4' }); server.register(v4ReportRoutes, { prefix: '/rest/v4' }); +server.register(v5DataRoutes, { prefix: '/rest/v5' }); +server.register(v5IssuesRoutes, { prefix: '/rest/v5' }); +server.register(v5ReportRoutes, { prefix: '/rest/v5' }); +server.register(v5VersionRoutes, { prefix: '/rest/v5' }); server.listen({ host: '0.0.0.0', port: environment.port }, (error, address) => { if (error) { diff --git a/packages/api/src/routes/v1/entries.ts b/packages/api/src/routes/v1/entries.ts index 24f7d38..63c2f5c 100644 --- a/packages/api/src/routes/v1/entries.ts +++ b/packages/api/src/routes/v1/entries.ts @@ -3,8 +3,8 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; /** * @deprecated This API route is no longer supported. Please use a newer version */ -export default (server: FastifyInstance, options: RouteShorthandOptions, done: () => void) => { - server.get('/entries/', async (request, reply) => { +export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { + server.get('/entries/', async (_request, reply) => { reply.send({ success: false, errors: ['This API route is no longer supported. Please use a newer version'], diff --git a/packages/api/src/routes/v1/report.ts b/packages/api/src/routes/v1/report.ts index 4bedb23..6ec6f57 100644 --- a/packages/api/src/routes/v1/report.ts +++ b/packages/api/src/routes/v1/report.ts @@ -3,7 +3,7 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; /** * @deprecated This API route is no longer supported. Please use a newer version */ -export default (server: FastifyInstance, options: RouteShorthandOptions, done: () => void) => { +export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { server.post('/report/', {}, async (_request, reply) => { reply.send({ success: false, diff --git a/packages/api/src/routes/v2/data.ts b/packages/api/src/routes/v2/data.ts index ea4e28c..fa0087a 100644 --- a/packages/api/src/routes/v2/data.ts +++ b/packages/api/src/routes/v2/data.ts @@ -1,13 +1,13 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import fetch from 'node-fetch'; import { parseNewFix } from 'services/compatibility'; +import environment from 'services/environment'; -export default (server: FastifyInstance, options: RouteShorthandOptions, done: () => void) => { - server.get('/data/', async (request, reply) => { +export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { + server.get('/data/', async (_request, reply) => { try { - const databaseUrl = - 'https://raw.githubusercontent.com/wanhose/cookie-dialog-monster/main/database.json'; - const result = await (await fetch(databaseUrl)).json(); + const url = `${environment.github.files}/database.json`; + const result = await (await fetch(url)).json(); reply.send({ data: { diff --git a/packages/api/src/routes/v2/report.ts b/packages/api/src/routes/v2/report.ts index 538d3e4..140d57c 100644 --- a/packages/api/src/routes/v2/report.ts +++ b/packages/api/src/routes/v2/report.ts @@ -18,7 +18,7 @@ const PostReportBodySchema = yup.object().shape({ type PostReportBody = yup.InferType; -export default (server: FastifyInstance, options: RouteShorthandOptions, done: () => void) => { +export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { server.post<{ Body: PostReportBody }>( '/report/', { diff --git a/packages/api/src/routes/v3/data.ts b/packages/api/src/routes/v3/data.ts index 9a48108..4dfcaa2 100644 --- a/packages/api/src/routes/v3/data.ts +++ b/packages/api/src/routes/v3/data.ts @@ -1,13 +1,13 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import fetch from 'node-fetch'; import { parseNewFix } from 'services/compatibility'; +import environment from 'services/environment'; export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { server.get('/data/', async (_request, reply) => { try { - const databaseUrl = - 'https://raw.githubusercontent.com/wanhose/cookie-dialog-monster/main/database.json'; - const result = await (await fetch(databaseUrl)).json(); + const url = `${environment.github.files}/database.json`; + const result = await (await fetch(url)).json(); reply.send({ data: { diff --git a/packages/api/src/routes/v4/data.ts b/packages/api/src/routes/v4/data.ts index 068dced..d7efbfd 100644 --- a/packages/api/src/routes/v4/data.ts +++ b/packages/api/src/routes/v4/data.ts @@ -1,38 +1,18 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import fetch from 'node-fetch'; +import environment from 'services/environment'; export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { server.get('/data/', async (_request, reply) => { try { - const databaseUrl = - 'https://raw.githubusercontent.com/wanhose/cookie-dialog-monster/main/database.json'; - const fetchOptions = { - headers: { 'Cache-Control': 'no-cache' }, - }; - const { rules, ...result } = await (await fetch(databaseUrl, fetchOptions)).json(); + const options = { headers: { 'Cache-Control': 'no-cache' } }; + const url = `${environment.github.files}/database.json`; + const { rules, ...result } = await (await fetch(url, options)).json(); reply.send({ data: { ...result, - rules: (rules as readonly string[]).map((urlFilter, index) => ({ - id: index + 1, - priority: 1, - action: { - type: 'block', - }, - condition: { - resourceTypes: [ - 'font', - 'image', - 'media', - 'object', - 'script', - 'stylesheet', - 'xmlhttprequest', - ], - urlFilter, - }, - })), + rules: rules.map(toDeclarativeNetRequestRule), }, success: true, }); @@ -46,3 +26,17 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: done(); }; + +function toDeclarativeNetRequestRule(urlFilter: string, index: number) { + return { + action: { + type: 'block', + }, + condition: { + resourceTypes: ['font', 'image', 'media', 'object', 'script', 'stylesheet', 'xmlhttprequest'], + urlFilter, + }, + id: index + 1, + priority: 1, + }; +} diff --git a/packages/api/src/routes/v5/data.ts b/packages/api/src/routes/v5/data.ts new file mode 100644 index 0000000..326d8e9 --- /dev/null +++ b/packages/api/src/routes/v5/data.ts @@ -0,0 +1 @@ +export { default as default } from '../v4/data'; diff --git a/packages/api/src/routes/v4/issues.ts b/packages/api/src/routes/v5/issues.ts similarity index 100% rename from packages/api/src/routes/v4/issues.ts rename to packages/api/src/routes/v5/issues.ts diff --git a/packages/api/src/routes/v5/report.ts b/packages/api/src/routes/v5/report.ts new file mode 100644 index 0000000..0436f94 --- /dev/null +++ b/packages/api/src/routes/v5/report.ts @@ -0,0 +1,90 @@ +import { FastifyInstance, RouteShorthandOptions } from 'fastify'; +import environment from 'services/environment'; +import { octokit } from 'services/octokit'; +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/', + { + schema: { + body: PostReportBodySchema, + }, + validatorCompiler, + }, + async (request, reply) => { + const { url, userAgent } = request.body; + const ua = new UAParser(userAgent ?? '').getResult(); + const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); + const existingIssues = await octokit.request('GET /search/issues', { + per_page: 1, + q: `in:title+is:issue+repo:${environment.github.owner}/${environment.github.repo}+${hostname}`, + }); + const existingIssue = existingIssues.data.items[0]; + + try { + if (existingIssue) { + reply.send({ + errors: [`Issue already exists, ${existingIssue.html_url}`], + success: false, + }); + return; + } + + const response = await octokit.request('POST /repos/{owner}/{repo}/issues', { + assignees: [environment.github.owner], + body: generateText(request.body, ua), + labels: ['bug'], + owner: environment.github.owner, + repo: environment.github.repo, + title: url, + }); + + reply.send({ + data: response.data.html_url, + success: true, + }); + } catch (error) { + reply.send({ + errors: [error.message], + success: false, + }); + } + } + ); + + done(); +}; + +function generateText(body: PostReportBody, ua: UAParser.IResult): string { + return [ + '## Issue information', + ...(ua.browser.name && ua.browser.version + ? ['#### 🖥️ Browser', `${ua.browser.name} (${ua.browser.version})`] + : []), + ...(ua.device.type && ua.device.vendor + ? ['#### 📱 Device', `${ua.device.vendor} (${ua.device.type})`] + : []), + '#### 📝 Reason', + body.reason, + '#### 🔗 URL', + body.url, + '#### 🏷️ Version', + body.version, + ].join('\n'); +} diff --git a/packages/api/src/routes/v5/version.ts b/packages/api/src/routes/v5/version.ts new file mode 100644 index 0000000..f6b7b51 --- /dev/null +++ b/packages/api/src/routes/v5/version.ts @@ -0,0 +1,27 @@ +import { FastifyInstance, RouteShorthandOptions } from 'fastify'; +import fetch from 'node-fetch'; +import environment from 'services/environment'; + +export default (server: FastifyInstance, _options: RouteShorthandOptions, done: () => void) => { + server.get('/version/', async (_request, reply) => { + try { + const options = { headers: { 'Cache-Control': 'no-cache' } }; + const url = `${environment.github.files}/packages/browser-extension/src/manifest.json`; + const { version } = await (await fetch(url, options)).json(); + + reply.send({ + data: { + version, + }, + success: true, + }); + } catch (error) { + reply.send({ + errors: [error.message], + success: false, + }); + } + }); + + done(); +}; diff --git a/packages/api/src/services/environment.ts b/packages/api/src/services/environment.ts index 07ccce8..85e1bf8 100644 --- a/packages/api/src/services/environment.ts +++ b/packages/api/src/services/environment.ts @@ -1,5 +1,6 @@ export default { github: { + files: 'https://raw.githubusercontent.com/wanhose/cookie-dialog-monster/main', owner: 'wanhose', repo: 'cookie-dialog-monster', token: process.env.GITHUB_TOKEN ?? '',