From eea3df283c79750adc116997e3a8e8b5ca1216d7 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Wed, 30 Apr 2025 19:25:50 +0100 Subject: [PATCH] fix: move mii images to new uploads directory and add route to access it --- .gitignore | 3 +- src/app/api/mii/[id]/delete/route.ts | 1 - src/app/api/mii/[id]/edit/route.ts | 1 - src/app/api/mii/[id]/like/route.ts | 1 - src/app/api/submit/route.ts | 2 +- src/app/edit/{[slug] => [id]}/page.tsx | 10 +++--- src/app/mii/[id]/image/route.ts | 40 +++++++++++++++++++++++ src/app/mii/{[slug] => [id]}/page.tsx | 20 ++++++------ src/app/profile/{[slug] => [id]}/page.tsx | 14 ++++---- src/components/delete-mii.tsx | 2 +- src/components/mii-list/index.tsx | 6 ++-- src/components/submit-form/edit-form.tsx | 6 ++-- 12 files changed, 73 insertions(+), 33 deletions(-) rename src/app/edit/{[slug] => [id]}/page.tsx (87%) create mode 100644 src/app/mii/[id]/image/route.ts rename src/app/mii/{[slug] => [id]}/page.tsx (93%) rename src/app/profile/{[slug] => [id]}/page.tsx (94%) diff --git a/.gitignore b/.gitignore index 8903641..c058940 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -public/mii/ \ No newline at end of file +# tomodachi-share +uploads/ \ No newline at end of file diff --git a/src/app/api/mii/[id]/delete/route.ts b/src/app/api/mii/[id]/delete/route.ts index 2c64524..6bc0458 100644 --- a/src/app/api/mii/[id]/delete/route.ts +++ b/src/app/api/mii/[id]/delete/route.ts @@ -20,7 +20,6 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise const { id: slugId } = await params; const parsed = idSchema.safeParse(slugId); - if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400); const miiId = parsed.data; diff --git a/src/app/api/mii/[id]/edit/route.ts b/src/app/api/mii/[id]/edit/route.ts index 9728ea9..88ccd46 100644 --- a/src/app/api/mii/[id]/edit/route.ts +++ b/src/app/api/mii/[id]/edit/route.ts @@ -35,7 +35,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< // Get Mii ID const { id: slugId } = await params; const parsedId = idSchema.safeParse(slugId); - if (!parsedId.success) return rateLimit.sendResponse({ error: parsedId.error.errors[0].message }, 400); const miiId = parsedId.data; diff --git a/src/app/api/mii/[id]/like/route.ts b/src/app/api/mii/[id]/like/route.ts index c0a143a..f4167c2 100644 --- a/src/app/api/mii/[id]/like/route.ts +++ b/src/app/api/mii/[id]/like/route.ts @@ -15,7 +15,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< const { id: slugId } = await params; const parsed = idSchema.safeParse(slugId); - if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400); const miiId = parsed.data; diff --git a/src/app/api/submit/route.ts b/src/app/api/submit/route.ts index 005442c..5d25a1c 100644 --- a/src/app/api/submit/route.ts +++ b/src/app/api/submit/route.ts @@ -18,7 +18,7 @@ import { convertQrCode } from "@/lib/qr-codes"; import Mii from "@/lib/mii.js/mii"; import TomodachiLifeMii from "@/lib/tomodachi-life-mii"; -const uploadsDirectory = path.join(process.cwd(), "public", "mii"); +const uploadsDirectory = path.join(process.cwd(), "uploads"); const submitSchema = z.object({ name: nameSchema, diff --git a/src/app/edit/[slug]/page.tsx b/src/app/edit/[id]/page.tsx similarity index 87% rename from src/app/edit/[slug]/page.tsx rename to src/app/edit/[id]/page.tsx index efe67d6..bee778e 100644 --- a/src/app/edit/[slug]/page.tsx +++ b/src/app/edit/[id]/page.tsx @@ -6,15 +6,15 @@ import { prisma } from "@/lib/prisma"; import EditForm from "@/components/submit-form/edit-form"; interface Props { - params: Promise<{ slug: string }>; + params: Promise<{ id: string }>; } export async function generateMetadata({ params }: Props): Promise { - const { slug } = await params; + const { id } = await params; const mii = await prisma.mii.findUnique({ where: { - id: Number(slug), + id: Number(id), }, }); @@ -29,12 +29,12 @@ export async function generateMetadata({ params }: Props): Promise { } export default async function MiiPage({ params }: Props) { - const { slug } = await params; + const { id } = await params; const session = await auth(); const mii = await prisma.mii.findUnique({ where: { - id: Number(slug), + id: Number(id), }, include: { _count: { diff --git a/src/app/mii/[id]/image/route.ts b/src/app/mii/[id]/image/route.ts new file mode 100644 index 0000000..1baaa55 --- /dev/null +++ b/src/app/mii/[id]/image/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; + +import fs from "fs/promises"; +import path from "path"; + +import { idSchema } from "@/lib/schemas"; +import { RateLimit } from "@/lib/rate-limit"; + +const searchParamsSchema = z.object({ + type: z + .enum(["mii", "qr-code", "image0", "image1", "image2"], { + message: "Image type must be either 'mii', 'qr-code' or 'image[number from 0 to 2]'", + }) + .default("mii"), +}); + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const rateLimit = new RateLimit(request, 200); + const check = await rateLimit.handle(); + if (check) return check; + + const { id: slugId } = await params; + const parsed = idSchema.safeParse(slugId); + if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400); + const miiId = parsed.data; + + const searchParamsParsed = searchParamsSchema.safeParse(Object.fromEntries(request.nextUrl.searchParams)); + if (!searchParamsParsed.success) return rateLimit.sendResponse({ error: searchParamsParsed.error.errors[0].message }, 400); + const { type: imageType } = searchParamsParsed.data; + + const filePath = path.join(process.cwd(), "uploads", miiId.toString(), `${imageType}.webp`); + + try { + const buffer = await fs.readFile(filePath); + return new NextResponse(buffer); + } catch { + return rateLimit.sendResponse({ success: false, error: "Image not found" }, 404); + } +} diff --git a/src/app/mii/[slug]/page.tsx b/src/app/mii/[id]/page.tsx similarity index 93% rename from src/app/mii/[slug]/page.tsx rename to src/app/mii/[id]/page.tsx index 33c700d..3430e10 100644 --- a/src/app/mii/[slug]/page.tsx +++ b/src/app/mii/[id]/page.tsx @@ -14,15 +14,15 @@ import DeleteMiiButton from "@/components/delete-mii"; import ScanTutorialButton from "@/components/tutorial/scan"; interface Props { - params: Promise<{ slug: string }>; + params: Promise<{ id: string }>; } export async function generateMetadata({ params }: Props): Promise { - const { slug } = await params; + const { id } = await params; const mii = await prisma.mii.findUnique({ where: { - id: Number(slug), + id: Number(id), }, include: { user: { @@ -39,8 +39,8 @@ export async function generateMetadata({ params }: Props): Promise { // Bots get redirected anyways if (!mii) return {}; - const miiImageUrl = `/mii/${mii.id}/mii.webp`; - const qrCodeUrl = `/mii/${mii.id}/qrcode.webp`; + const miiImageUrl = `/mii/${mii.id}/image?type=mii`; + const qrCodeUrl = `/mii/${mii.id}/image?type=qr-code`; const username = `@${mii.user.username}`; @@ -73,12 +73,12 @@ export async function generateMetadata({ params }: Props): Promise { } export default async function MiiPage({ params }: Props) { - const { slug } = await params; + const { id } = await params; const session = await auth(); const mii = await prisma.mii.findUnique({ where: { - id: Number(slug), + id: Number(id), }, include: { user: { @@ -103,9 +103,9 @@ export default async function MiiPage({ params }: Props) { if (!mii) redirect("/404"); const images = [ - `/mii/${mii.id}/mii.webp`, - `/mii/${mii.id}/qr-code.webp`, - ...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image${index}.webp`), + `/mii/${mii.id}/image?type=mii`, + `/mii/${mii.id}/image?type=qr-code`, + ...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image?type=image${index}`), ]; return ( diff --git a/src/app/profile/[slug]/page.tsx b/src/app/profile/[id]/page.tsx similarity index 94% rename from src/app/profile/[slug]/page.tsx rename to src/app/profile/[id]/page.tsx index 1ac5069..1f5c554 100644 --- a/src/app/profile/[slug]/page.tsx +++ b/src/app/profile/[id]/page.tsx @@ -11,15 +11,15 @@ import { prisma } from "@/lib/prisma"; import MiiList from "@/components/mii-list"; interface Props { - params: Promise<{ slug: string }>; + params: Promise<{ id: string }>; } export async function generateMetadata({ params }: Props): Promise { - const { slug } = await params; + const { id } = await params; const user = await prisma.user.findUnique({ where: { - id: Number(slug), + id: Number(id), }, include: { _count: { @@ -68,17 +68,17 @@ export async function generateMetadata({ params }: Props): Promise { export default async function ProfilePage({ params }: Props) { const session = await auth(); - const { slug } = await params; + const { id } = await params; const user = await prisma.user.findUnique({ where: { - id: Number(slug), + id: Number(id), }, }); if (!user) redirect("/404"); - const likedMiis = await prisma.like.count({ where: { userId: Number(slug) } }); + const likedMiis = await prisma.like.count({ where: { userId: Number(id) } }); return (
@@ -102,7 +102,7 @@ export default async function ProfilePage({ params }: Props) { Created: {user?.createdAt.toLocaleDateString("en-GB", { month: "long", day: "2-digit", year: "numeric" })} - {session?.user.id == slug && ( + {session?.user.id == id && ( Settings diff --git a/src/components/delete-mii.tsx b/src/components/delete-mii.tsx index 07ce3c2..b4b84d5 100644 --- a/src/components/delete-mii.tsx +++ b/src/components/delete-mii.tsx @@ -78,7 +78,7 @@ export default function DeleteMiiButton({ miiId, miiName, likes }: Props) {

Are you sure? This will delete your Mii permanently. This action cannot be undone.

- mii image + mii image

{miiName} diff --git a/src/components/mii-list/index.tsx b/src/components/mii-list/index.tsx index 74cf8aa..c2f5574 100644 --- a/src/components/mii-list/index.tsx +++ b/src/components/mii-list/index.tsx @@ -83,9 +83,9 @@ export default function MiiList({ isLoggedIn, userId, sessionUserId }: Props) { > `/mii/${mii.id}/image${index}.webp`), + `/mii/${mii.id}/image?type=mii`, + `/mii/${mii.id}/image?type=qr-code`, + ...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image?type=image${index}`), ]} /> diff --git a/src/components/submit-form/edit-form.tsx b/src/components/submit-form/edit-form.tsx index 6e2f6f4..b6f6440 100644 --- a/src/components/submit-form/edit-form.tsx +++ b/src/components/submit-form/edit-form.tsx @@ -91,7 +91,7 @@ export default function EditForm({ mii, likes }: Props) { try { const existing = await Promise.all( Array.from({ length: mii.imageCount }, async (_, index) => { - const path = `/mii/${mii.id}/image${index}.webp`; + const path = `/mii/${mii.id}/image?type=image${index}`; const response = await fetch(path); const blob = await response.blob(); @@ -112,7 +112,9 @@ export default function EditForm({ mii, likes }: Props) {

- URL.createObjectURL(file))]} /> + URL.createObjectURL(file))]} + />