From 36f0ff83983cd3f826f4544d825cac39bf66ffbf Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 23 May 2025 15:33:35 +0100 Subject: [PATCH] feat: report notifications --- .env.example | 5 ++++- src/app/api/report/route.ts | 35 +++++++++++++++++++++++++++++++-- src/app/mii/[id]/image/route.ts | 15 ++------------ src/types.d.ts | 11 +++++++++++ 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index c4bf42a..95618bd 100644 --- a/.env.example +++ b/.env.example @@ -18,4 +18,7 @@ AUTH_GITHUB_SECRET=XXXXXXXXXXXXXXXX # Currently only supports one admin NEXT_PUBLIC_ADMIN_USER_ID=1 # Separated by commas -NEXT_PUBLIC_CONTRIBUTORS_USER_IDS=176 \ No newline at end of file +NEXT_PUBLIC_CONTRIBUTORS_USER_IDS=176 + +# Sends notifications (such as admin reports) to ntfy +NTFY_URL="https://ntfy.yourdomain.com/tomodachi-share" \ No newline at end of file diff --git a/src/app/api/report/route.ts b/src/app/api/report/route.ts index c1442e4..d73f513 100644 --- a/src/app/api/report/route.ts +++ b/src/app/api/report/route.ts @@ -5,6 +5,7 @@ import { ReportReason, ReportType } from "@prisma/client"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { RateLimit } from "@/lib/rate-limit"; +import { MiiWithUsername } from "@/types"; const reportSchema = z.object({ id: z.coerce.number({ message: "ID must be a number" }).int({ message: "ID must be an integer" }).positive({ message: "ID must be valid" }), @@ -29,12 +30,25 @@ export async function POST(request: NextRequest) { if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400); const { id, type, reason, notes } = parsed.data; + let mii: MiiWithUsername | null = null; + // Check if the Mii or User exists if (type === "mii") { - const mii = await prisma.mii.findUnique({ where: { id } }); + mii = await prisma.mii.findUnique({ + where: { id }, + include: { + user: { + select: { + username: true, + }, + }, + }, + }); if (!mii) return rateLimit.sendResponse({ error: "Mii not found" }, 404); } else { - const user = await prisma.user.findUnique({ where: { id } }); + const user = await prisma.user.findUnique({ + where: { id }, + }); if (!user) return rateLimit.sendResponse({ error: "User not found" }, 404); } @@ -57,6 +71,7 @@ export async function POST(request: NextRequest) { reason: reason.toUpperCase() as ReportReason, reasonNotes: notes, authorId: Number(session.user.id), + creatorId: mii ? mii.userId : undefined, }, }); } catch (error) { @@ -64,5 +79,21 @@ export async function POST(request: NextRequest) { return rateLimit.sendResponse({ error: "Failed to create report" }, 500); } + // Send notification to ntfy + if (process.env.NTFY_URL) { + // This is only shown if report type is MII + const miiCreatorMessage = mii ? `by @${mii.user.username} (ID: ${mii.userId})` : ""; + + await fetch(process.env.NTFY_URL, { + method: "POST", + body: `Report by @${session.user.username} (ID: ${session.user.id}) on ${type.toUpperCase()} (ID: ${id}) ${miiCreatorMessage}`, + headers: { + Title: "Report recieved - TomodachiShare", + Priority: "urgent", + Tags: "triangular_flag_on_post", + }, + }); + } + return rateLimit.sendResponse({ success: true }); } diff --git a/src/app/mii/[id]/image/route.ts b/src/app/mii/[id]/image/route.ts index 76fff47..6e20465 100644 --- a/src/app/mii/[id]/image/route.ts +++ b/src/app/mii/[id]/image/route.ts @@ -4,22 +4,11 @@ import fs from "fs/promises"; import path from "path"; import { z } from "zod"; -import { Prisma } from "@prisma/client"; - import { idSchema } from "@/lib/schemas"; import { RateLimit } from "@/lib/rate-limit"; import { generateMetadataImage } from "@/lib/images"; import { prisma } from "@/lib/prisma"; - -type MiiWithUser = Prisma.MiiGetPayload<{ - include: { - user: { - select: { - username: true; - }; - }; - }; -}>; +import { MiiWithUsername } from "@/types"; const searchParamsSchema = z.object({ type: z @@ -48,7 +37,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ let buffer: Buffer | undefined; // Only find Mii if image type is 'metadata' - let mii: MiiWithUser | null = null; + let mii: MiiWithUsername | null = null; if (imageType === "metadata") { mii = await prisma.mii.findUnique({ diff --git a/src/types.d.ts b/src/types.d.ts index 0b3841a..c00b6a1 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,3 +1,4 @@ +import { Prisma } from "@prisma/client"; import { DefaultSession } from "next-auth"; declare module "next-auth" { @@ -11,3 +12,13 @@ declare module "next-auth" { username?: string; } } + +type MiiWithUsername = Prisma.MiiGetPayload<{ + include: { + user: { + select: { + username: true; + }; + }; + }; +}>;