From 964ccfacbfae729f450a298cbdbcb2618cb71299 Mon Sep 17 00:00:00 2001 From: bytedream Date: Fri, 17 Oct 2025 20:15:06 +0200 Subject: [PATCH] rework api endpoints --- README.md | 70 ++++++++++++--- src/db/schema/report.ts | 6 +- src/pages/api/_api.ts | 34 ++++++++ src/pages/api/feedback.ts | 37 -------- src/pages/api/feedback/index.ts | 27 ++++++ src/pages/api/player.ts | 37 -------- src/pages/api/report.ts | 103 ----------------------- src/pages/api/reports/index.ts | 84 ++++++++++++++++++ src/pages/api/users/[...uuid]/index.ts | 23 +++++ src/pages/api/users/[...uuid]/reports.ts | 34 ++++++++ 10 files changed, 266 insertions(+), 189 deletions(-) create mode 100644 src/pages/api/_api.ts delete mode 100644 src/pages/api/feedback.ts create mode 100644 src/pages/api/feedback/index.ts delete mode 100644 src/pages/api/player.ts delete mode 100644 src/pages/api/report.ts create mode 100644 src/pages/api/reports/index.ts create mode 100644 src/pages/api/users/[...uuid]/index.ts create mode 100644 src/pages/api/users/[...uuid]/reports.ts diff --git a/README.md b/README.md index 23c4b0e..7ce2a8b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@
-POST /api/report (Erstellt einen Report) +POST /api/reports (Erstellt einen Report) ##### Request Body @@ -78,7 +78,7 @@
-PUT /api/report (Erstellt einen Abgeschlossenen Report) +PUT /api/reports (Erstellt einen Abgeschlossenen Report) ##### Request Body @@ -117,16 +117,13 @@
-POST /api/player (Status eines Spielers) +GET /api/users/{uuid} (Status eines Spielers) -##### Request Body +#### Path Parameters -``` -{ - // UUID eines Spielers - "uuid": string -} -``` +| parameter | beschreibung | +| --------- | ------------------- | +| `uuid` | UUID eines Spielers | ##### Response Codes @@ -161,6 +158,59 @@
+
+GET /api/users/{uuid}/reports (Reports eines Spielers) + +#### Path Parameters + +| parameter | beschreibung | +| --------- | ------------------- | +| `uuid` | UUID eines Spielers | + +##### Response Codes + +| http code | beschreibung | +| --------- | ------------------------------------------ | +| 200 | / | +| 400 | Der Request Body ist falsch | +| 401 | Es wurde ein falsches API Secret angegeben | +| 404 | Der Spieler existiert nicht | + +##### Response Body + +``` +{ + // Alle Reports, die der Spieler selber erstellt hat + "from_self": { + // Die UUID des reporteten Spielers oder null falls ein unbekannter Spieler reportet wurde + "reported": string | null, + // Grund des Reports + "reason": string, + // Wann der Report abgeschickt wurde als UTC Millisekunden oder null falls der Report noch nicht abgeschickt wurde (=> kann noch bearbeitet werden) + "created": number | null, + // Status des Reports, "open" wenn er gerade bearbeitet wird, "closed" falls er bearbeitet wurde, null wenn nichts von beidem + "status": "open" | "closed" | null, + // Url zum Report auf der Website + "url": string + }[], + // Alle Reports, die gegen den Spieler erstellt wurden + "to_self": { + // Die UUID des Spielers, der den Report erstellt hat oder null falls der Report vom System kommt + "reporter": string | null, + // Grund des Reports + "reason": string, + // Wann der Report abgeschickt wurde als UTC Millisekunden oder null falls der Report noch nicht abgeschickt wurde (=> kann noch bearbeitet werden) + "created": number | null, + // Status des Reports, "open" wenn er gerade bearbeitet wird, "closed" falls er bearbeitet wurde, null wenn nichts von beidem + "status": "open" | "closed" | null, + // Url zum Report auf der Website + "url": string + }[] +} +``` + +
+ ## Webhook > Die env variable `WEBHOOK_ENDPOINT` muss gesetzt und eine valide HTTP URL sein. diff --git a/src/db/schema/report.ts b/src/db/schema/report.ts index fc23c13..f2400a6 100644 --- a/src/db/schema/report.ts +++ b/src/db/schema/report.ts @@ -120,11 +120,13 @@ export async function getReports(db: Database, values: GetReportsReq) { createdAt: report.createdAt, reporter: { id: reporter.id, - username: reporter.username + username: reporter.username, + uuid: reporter.uuid }, reported: { id: reported.id, - username: reported.username + username: reported.username, + uuid: reported.uuid }, status: { status: reportStatus.status, diff --git a/src/pages/api/_api.ts b/src/pages/api/_api.ts new file mode 100644 index 0000000..df61fcb --- /dev/null +++ b/src/pages/api/_api.ts @@ -0,0 +1,34 @@ +import type { APIRoute } from 'astro'; +import { z } from 'astro:schema'; +import { checkApiBasicAuth } from '@util/auth.ts'; + +export function externalApi(params: { + input?: InputSchema; + auth?: boolean; + handler: ({ + input, + params + }: { + input: InputSchema extends z.ZodType ? z.infer : {}; + params: Record; + }) => Response | Promise; +}): APIRoute { + return async (context) => { + if (params.auth && !checkApiBasicAuth(context.request.headers)) { + return new Response(null, { status: 401 }); + } + + let input; + if (params.input) { + try { + input = await params.input.parseAsync(await context.request.json()); + } catch (_) { + return new Response(null, { status: 400 }); + } + } else { + input = {}; + } + + return params.handler({ input: input, params: context.params }); + }; +} diff --git a/src/pages/api/feedback.ts b/src/pages/api/feedback.ts deleted file mode 100644 index 061f617..0000000 --- a/src/pages/api/feedback.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from 'astro:schema'; -import type { APIRoute } from 'astro'; -import { db } from '@db/database.ts'; -import { BASE_PATH } from 'astro:env/server'; -import { checkApiBasicAuth } from '@util/auth.ts'; - -const postSchema = z.object({ - event: z.string(), - title: z.string(), - uuids: z.array(z.string()) -}); - -export const POST: APIRoute = async ({ request }) => { - if (!checkApiBasicAuth(request.headers)) { - return new Response(null, { status: 401 }); - } - - let parsed; - try { - parsed = await postSchema.parseAsync(await request.json()); - } catch (_) { - return new Response(null, { status: 400 }); - } - - const feedbacks = await db.addUserFeedbacks({ - event: parsed.event, - title: parsed.title, - uuids: parsed.uuids - }); - - const response = feedbacks.map((feedback) => ({ - uuid: feedback.uuid, - url: `${BASE_PATH}/feedback/${feedback.urlHash}` - })); - - return new Response(JSON.stringify({ feedback: response }), { status: 200 }); -}; diff --git a/src/pages/api/feedback/index.ts b/src/pages/api/feedback/index.ts new file mode 100644 index 0000000..5aa0bab --- /dev/null +++ b/src/pages/api/feedback/index.ts @@ -0,0 +1,27 @@ +import { externalApi } from '../_api.ts'; +import { z } from 'astro:schema'; +import { db } from '@db/database.ts'; +import { BASE_PATH } from 'astro:env/server'; + +export const POST = externalApi({ + input: z.object({ + event: z.string(), + title: z.string(), + uuids: z.array(z.string()) + }), + auth: true, + handler: async ({ input }) => { + const feedbacks = await db.addUserFeedbacks({ + event: input.event, + title: input.title, + uuids: input.uuids + }); + + const response = feedbacks.map((feedback) => ({ + uuid: feedback.uuid, + url: `${BASE_PATH}/feedback/${feedback.urlHash}` + })); + + return new Response(JSON.stringify({ feedback: response }), { status: 200 }); + } +}); diff --git a/src/pages/api/player.ts b/src/pages/api/player.ts deleted file mode 100644 index b562cef..0000000 --- a/src/pages/api/player.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from 'astro:schema'; -import type { APIRoute } from 'astro'; -import { db } from '@db/database.ts'; -import { checkApiBasicAuth } from '@util/auth.ts'; - -const postSchema = z.object({ - uuid: z.string() -}); - -export const POST: APIRoute = async ({ request }) => { - if (!checkApiBasicAuth(request.headers)) { - return new Response(null, { status: 401 }); - } - - let parsed; - try { - parsed = await postSchema.parseAsync(await request.json()); - } catch (_) { - return new Response(null, { status: 400 }); - } - - const user = await db.getUserByUuid({ uuid: parsed.uuid }); - if (!user) return new Response(null, { status: 404 }); - - const strikes = await db.getStrikesByUserId({ userId: user.id }); - - return new Response( - JSON.stringify({ - firstname: user.firstname, - lastname: user.lastname, - username: user.username, - uuid: user.uuid, - strikes: strikes.map((s) => ({ at: s.at.getTime(), weight: s.reason.weight })) - }), - { status: 200 } - ); -}; diff --git a/src/pages/api/report.ts b/src/pages/api/report.ts deleted file mode 100644 index 6e948dc..0000000 --- a/src/pages/api/report.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { APIRoute } from 'astro'; -import { z } from 'astro:schema'; -import { db } from '@db/database.ts'; -import { sendWebhook, WebhookAction } from '@util/webhook.ts'; -import { checkApiBasicAuth } from '@util/auth.ts'; - -const postSchema = z.object({ - reporter: z.string(), - reported: z.string().nullable(), - reason: z.string() -}); - -export const POST: APIRoute = async ({ request }) => { - if (!checkApiBasicAuth(request.headers)) { - return new Response(null, { status: 401 }); - } - - let parsed; - try { - parsed = await postSchema.parseAsync(await request.json()); - } catch (_) { - return new Response(null, { status: 400 }); - } - - const reporter = await db.getUserByUuid({ uuid: parsed.reporter }); - if (!reporter) return new Response(null, { status: 404 }); - - let reported = null; - if (parsed.reported) { - reported = await db.getUserByUuid({ uuid: parsed.reported }); - if (!reported) return new Response(null, { status: 404 }); - } - - const report = await db.addReport({ - reporterId: reporter.id, - reportedId: reported?.id, - reason: parsed.reason, - body: null - }); - - return new Response(JSON.stringify({ url: report.url }), { status: 200 }); -}; - -const putSchema = z.object({ - reporter: z.string().nullable(), - reported: z.string(), - reason: z.string(), - body: z.string().nullable(), - notice: z.string().nullable(), - statement: z.string().nullable(), - strike_reason_id: z.number() -}); - -export const PUT: APIRoute = async ({ request }) => { - if (!checkApiBasicAuth(request.headers)) { - return new Response(null, { status: 401 }); - } - - let parsed; - try { - parsed = await putSchema.parseAsync(await request.json()); - } catch (_) { - return new Response(null, { status: 400 }); - } - - const reported = await db.getUserByUuid({ uuid: parsed.reported }); - if (!reported) return new Response(null, { status: 404 }); - - let reporter = null; - if (parsed.reporter) { - reporter = await db.getUserByUuid({ uuid: parsed.reporter }); - if (!reporter) return new Response(null, { status: 404 }); - } - - await db.transaction(async (tx) => { - const report = await tx.addReport({ - reporterId: reporter?.id, - reportedId: reported.id, - createdAt: new Date(), - reason: parsed.reason, - body: parsed.body - }); - - await tx.editReportStatus({ - reportId: report.id, - notice: parsed.notice, - statement: parsed.statement, - status: 'closed' - }); - - await tx.editStrike({ - reportId: report.id, - strikeReasonId: parsed.strike_reason_id - }); - }); - - // send webhook in background - sendWebhook(WebhookAction.Strike, { - uuid: reported.uuid! - }); - - return new Response(null, { status: 200 }); -}; diff --git a/src/pages/api/reports/index.ts b/src/pages/api/reports/index.ts new file mode 100644 index 0000000..179e23c --- /dev/null +++ b/src/pages/api/reports/index.ts @@ -0,0 +1,84 @@ +import { externalApi } from '../_api.ts'; +import { z } from 'astro:schema'; +import { db } from '@db/database.ts'; +import { sendWebhook, WebhookAction } from '@util/webhook.ts'; + +export const POST = externalApi({ + input: z.object({ + reporter: z.string(), + reported: z.string().nullable(), + reason: z.string() + }), + auth: true, + handler: async ({ input }) => { + const reporter = await db.getUserByUuid({ uuid: input.reporter }); + if (!reporter) return new Response(null, { status: 404 }); + + let reported = null; + if (input.reported) { + reported = await db.getUserByUuid({ uuid: input.reported }); + if (!reported) return new Response(null, { status: 404 }); + } + + const report = await db.addReport({ + reporterId: reporter.id, + reportedId: reported?.id, + reason: input.reason, + body: null + }); + + return new Response(JSON.stringify({ url: report.url }), { status: 200 }); + } +}); + +export const PUT = externalApi({ + input: z.object({ + reporter: z.string().nullable(), + reported: z.string(), + reason: z.string(), + body: z.string().nullable(), + notice: z.string().nullable(), + statement: z.string().nullable(), + strike_reason_id: z.number() + }), + auth: true, + handler: async ({ input }) => { + const reported = await db.getUserByUuid({ uuid: input.reported }); + if (!reported) return new Response(null, { status: 404 }); + + let reporter = null; + if (input.reporter) { + reporter = await db.getUserByUuid({ uuid: input.reporter }); + if (!reporter) return new Response(null, { status: 404 }); + } + + await db.transaction(async (tx) => { + const report = await tx.addReport({ + reporterId: reporter?.id, + reportedId: reported.id, + createdAt: new Date(), + reason: input.reason, + body: input.body + }); + + await tx.editReportStatus({ + reportId: report.id, + notice: input.notice, + statement: input.statement, + status: 'closed' + }); + + await tx.editStrike({ + reportId: report.id, + strikeReasonId: input.strike_reason_id + }); + }); + + // send webhook in background + sendWebhook(WebhookAction.Strike, { + uuid: reported.uuid! + }); + + return new Response(null, { status: 200 }); + } +}); diff --git a/src/pages/api/users/[...uuid]/index.ts b/src/pages/api/users/[...uuid]/index.ts new file mode 100644 index 0000000..bb489e6 --- /dev/null +++ b/src/pages/api/users/[...uuid]/index.ts @@ -0,0 +1,23 @@ +import { externalApi } from '../../_api.ts'; +import { db } from '@db/database.ts'; + +export const GET = externalApi({ + auth: true, + handler: async ({ params }) => { + const user = await db.getUserByUuid({ uuid: params['uuid']! }); + if (!user) return new Response(null, { status: 404 }); + + const strikes = await db.getStrikesByUserId({ userId: user.id }); + + return new Response( + JSON.stringify({ + firstname: user.firstname, + lastname: user.lastname, + username: user.username, + uuid: user.uuid, + strikes: strikes.map((s) => ({ at: s.at.getTime(), weight: s.reason.weight })) + }), + { status: 200 } + ); + } +}); diff --git a/src/pages/api/users/[...uuid]/reports.ts b/src/pages/api/users/[...uuid]/reports.ts new file mode 100644 index 0000000..05f964f --- /dev/null +++ b/src/pages/api/users/[...uuid]/reports.ts @@ -0,0 +1,34 @@ +import { externalApi } from '../../_api.ts'; +import { db } from '@db/database.ts'; +import { BASE_PATH } from 'astro:env/server'; + +export const GET = externalApi({ + auth: true, + handler: async ({ params }) => { + const user = await db.getUserByUuid({ uuid: params['uuid']! }); + if (!user) return new Response(null, { status: 404 }); + + const fromSelf = await db.getReports({ reporter: user.username }); + const toSelf = await db.getReports({ reported: user.username }); + + return new Response( + JSON.stringify({ + from_self: fromSelf.map((report) => ({ + reported: report.reported?.uuid ?? null, + reason: report.reason, + created: report.createdAt?.getTime() ?? null, + status: report.status?.status ?? null, + url: `${BASE_PATH}/report/${report.urlHash}` + })), + to_self: toSelf.map((report) => ({ + reporter: report.reporter?.uuid ?? null, + reason: report.reason, + created: report.createdAt, + status: report.status?.status ?? null, + url: `${BASE_PATH}/report/${report.urlHash}` + })) + }), + { status: 200 } + ); + } +});