feat: report notifications
This commit is contained in:
parent
6e8f5beb3e
commit
36f0ff8398
4 changed files with 50 additions and 16 deletions
|
|
@ -18,4 +18,7 @@ AUTH_GITHUB_SECRET=XXXXXXXXXXXXXXXX
|
||||||
# Currently only supports one admin
|
# Currently only supports one admin
|
||||||
NEXT_PUBLIC_ADMIN_USER_ID=1
|
NEXT_PUBLIC_ADMIN_USER_ID=1
|
||||||
# Separated by commas
|
# 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"
|
||||||
|
|
@ -5,6 +5,7 @@ import { ReportReason, ReportType } from "@prisma/client";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { RateLimit } from "@/lib/rate-limit";
|
import { RateLimit } from "@/lib/rate-limit";
|
||||||
|
import { MiiWithUsername } from "@/types";
|
||||||
|
|
||||||
const reportSchema = z.object({
|
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" }),
|
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);
|
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||||
const { id, type, reason, notes } = parsed.data;
|
const { id, type, reason, notes } = parsed.data;
|
||||||
|
|
||||||
|
let mii: MiiWithUsername | null = null;
|
||||||
|
|
||||||
// Check if the Mii or User exists
|
// Check if the Mii or User exists
|
||||||
if (type === "mii") {
|
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);
|
if (!mii) return rateLimit.sendResponse({ error: "Mii not found" }, 404);
|
||||||
} else {
|
} 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);
|
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,
|
reason: reason.toUpperCase() as ReportReason,
|
||||||
reasonNotes: notes,
|
reasonNotes: notes,
|
||||||
authorId: Number(session.user.id),
|
authorId: Number(session.user.id),
|
||||||
|
creatorId: mii ? mii.userId : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -64,5 +79,21 @@ export async function POST(request: NextRequest) {
|
||||||
return rateLimit.sendResponse({ error: "Failed to create report" }, 500);
|
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 });
|
return rateLimit.sendResponse({ success: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,11 @@ import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Prisma } from "@prisma/client";
|
|
||||||
|
|
||||||
import { idSchema } from "@/lib/schemas";
|
import { idSchema } from "@/lib/schemas";
|
||||||
import { RateLimit } from "@/lib/rate-limit";
|
import { RateLimit } from "@/lib/rate-limit";
|
||||||
import { generateMetadataImage } from "@/lib/images";
|
import { generateMetadataImage } from "@/lib/images";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { MiiWithUsername } from "@/types";
|
||||||
type MiiWithUser = Prisma.MiiGetPayload<{
|
|
||||||
include: {
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
username: true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const searchParamsSchema = z.object({
|
const searchParamsSchema = z.object({
|
||||||
type: z
|
type: z
|
||||||
|
|
@ -48,7 +37,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||||
|
|
||||||
let buffer: Buffer | undefined;
|
let buffer: Buffer | undefined;
|
||||||
// Only find Mii if image type is 'metadata'
|
// Only find Mii if image type is 'metadata'
|
||||||
let mii: MiiWithUser | null = null;
|
let mii: MiiWithUsername | null = null;
|
||||||
|
|
||||||
if (imageType === "metadata") {
|
if (imageType === "metadata") {
|
||||||
mii = await prisma.mii.findUnique({
|
mii = await prisma.mii.findUnique({
|
||||||
|
|
|
||||||
11
src/types.d.ts
vendored
11
src/types.d.ts
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
import { DefaultSession } from "next-auth";
|
import { DefaultSession } from "next-auth";
|
||||||
|
|
||||||
declare module "next-auth" {
|
declare module "next-auth" {
|
||||||
|
|
@ -11,3 +12,13 @@ declare module "next-auth" {
|
||||||
username?: string;
|
username?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MiiWithUsername = Prisma.MiiGetPayload<{
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
username: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue