feat: report notifications

This commit is contained in:
trafficlunar 2025-05-23 15:33:35 +01:00
parent 6e8f5beb3e
commit 36f0ff8398
4 changed files with 50 additions and 16 deletions

View file

@ -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
NEXT_PUBLIC_CONTRIBUTORS_USER_IDS=176
# Sends notifications (such as admin reports) to ntfy
NTFY_URL="https://ntfy.yourdomain.com/tomodachi-share"

View file

@ -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 });
}

View file

@ -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({

11
src/types.d.ts vendored
View file

@ -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;
};
};
};
}>;