fix: move mii images to new uploads directory and add route to access it
This commit is contained in:
parent
594309d22d
commit
eea3df283c
12 changed files with 73 additions and 33 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -41,4 +41,5 @@ yarn-error.log*
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
public/mii/
|
# tomodachi-share
|
||||||
|
uploads/
|
||||||
|
|
@ -20,7 +20,6 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise
|
||||||
|
|
||||||
const { id: slugId } = await params;
|
const { id: slugId } = await params;
|
||||||
const parsed = idSchema.safeParse(slugId);
|
const parsed = idSchema.safeParse(slugId);
|
||||||
|
|
||||||
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 miiId = parsed.data;
|
const miiId = parsed.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||||
// Get Mii ID
|
// Get Mii ID
|
||||||
const { id: slugId } = await params;
|
const { id: slugId } = await params;
|
||||||
const parsedId = idSchema.safeParse(slugId);
|
const parsedId = idSchema.safeParse(slugId);
|
||||||
|
|
||||||
if (!parsedId.success) return rateLimit.sendResponse({ error: parsedId.error.errors[0].message }, 400);
|
if (!parsedId.success) return rateLimit.sendResponse({ error: parsedId.error.errors[0].message }, 400);
|
||||||
const miiId = parsedId.data;
|
const miiId = parsedId.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||||
|
|
||||||
const { id: slugId } = await params;
|
const { id: slugId } = await params;
|
||||||
const parsed = idSchema.safeParse(slugId);
|
const parsed = idSchema.safeParse(slugId);
|
||||||
|
|
||||||
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 miiId = parsed.data;
|
const miiId = parsed.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { convertQrCode } from "@/lib/qr-codes";
|
||||||
import Mii from "@/lib/mii.js/mii";
|
import Mii from "@/lib/mii.js/mii";
|
||||||
import TomodachiLifeMii from "@/lib/tomodachi-life-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({
|
const submitSchema = z.object({
|
||||||
name: nameSchema,
|
name: nameSchema,
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ import { prisma } from "@/lib/prisma";
|
||||||
import EditForm from "@/components/submit-form/edit-form";
|
import EditForm from "@/components/submit-form/edit-form";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ id: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const mii = await prisma.mii.findUnique({
|
const mii = await prisma.mii.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(slug),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -29,12 +29,12 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function MiiPage({ params }: Props) {
|
export default async function MiiPage({ params }: Props) {
|
||||||
const { slug } = await params;
|
const { id } = await params;
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
||||||
const mii = await prisma.mii.findUnique({
|
const mii = await prisma.mii.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(slug),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
40
src/app/mii/[id]/image/route.ts
Normal file
40
src/app/mii/[id]/image/route.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,15 +14,15 @@ import DeleteMiiButton from "@/components/delete-mii";
|
||||||
import ScanTutorialButton from "@/components/tutorial/scan";
|
import ScanTutorialButton from "@/components/tutorial/scan";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ id: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const mii = await prisma.mii.findUnique({
|
const mii = await prisma.mii.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(slug),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: {
|
user: {
|
||||||
|
|
@ -39,8 +39,8 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
// Bots get redirected anyways
|
// Bots get redirected anyways
|
||||||
if (!mii) return {};
|
if (!mii) return {};
|
||||||
|
|
||||||
const miiImageUrl = `/mii/${mii.id}/mii.webp`;
|
const miiImageUrl = `/mii/${mii.id}/image?type=mii`;
|
||||||
const qrCodeUrl = `/mii/${mii.id}/qrcode.webp`;
|
const qrCodeUrl = `/mii/${mii.id}/image?type=qr-code`;
|
||||||
|
|
||||||
const username = `@${mii.user.username}`;
|
const username = `@${mii.user.username}`;
|
||||||
|
|
||||||
|
|
@ -73,12 +73,12 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function MiiPage({ params }: Props) {
|
export default async function MiiPage({ params }: Props) {
|
||||||
const { slug } = await params;
|
const { id } = await params;
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
||||||
const mii = await prisma.mii.findUnique({
|
const mii = await prisma.mii.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(slug),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: {
|
user: {
|
||||||
|
|
@ -103,9 +103,9 @@ export default async function MiiPage({ params }: Props) {
|
||||||
if (!mii) redirect("/404");
|
if (!mii) redirect("/404");
|
||||||
|
|
||||||
const images = [
|
const images = [
|
||||||
`/mii/${mii.id}/mii.webp`,
|
`/mii/${mii.id}/image?type=mii`,
|
||||||
`/mii/${mii.id}/qr-code.webp`,
|
`/mii/${mii.id}/image?type=qr-code`,
|
||||||
...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image${index}.webp`),
|
...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image?type=image${index}`),
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -11,15 +11,15 @@ import { prisma } from "@/lib/prisma";
|
||||||
import MiiList from "@/components/mii-list";
|
import MiiList from "@/components/mii-list";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ id: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(slug),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
|
|
@ -68,17 +68,17 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
|
|
||||||
export default async function ProfilePage({ params }: Props) {
|
export default async function ProfilePage({ params }: Props) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const { slug } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(slug),
|
id: Number(id),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) redirect("/404");
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -102,7 +102,7 @@ export default async function ProfilePage({ params }: Props) {
|
||||||
Created: {user?.createdAt.toLocaleDateString("en-GB", { month: "long", day: "2-digit", year: "numeric" })}
|
Created: {user?.createdAt.toLocaleDateString("en-GB", { month: "long", day: "2-digit", year: "numeric" })}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{session?.user.id == slug && (
|
{session?.user.id == id && (
|
||||||
<Link href="/profile/settings" className="pill button absolute right-0 bottom-0 !px-4">
|
<Link href="/profile/settings" className="pill button absolute right-0 bottom-0 !px-4">
|
||||||
<Icon icon="material-symbols:settings-rounded" className="text-2xl mr-2" />
|
<Icon icon="material-symbols:settings-rounded" className="text-2xl mr-2" />
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
|
|
@ -78,7 +78,7 @@ export default function DeleteMiiButton({ miiId, miiName, likes }: Props) {
|
||||||
<p className="text-sm text-zinc-500">Are you sure? This will delete your Mii permanently. This action cannot be undone.</p>
|
<p className="text-sm text-zinc-500">Are you sure? This will delete your Mii permanently. This action cannot be undone.</p>
|
||||||
|
|
||||||
<div className="bg-orange-100 rounded-xl border-2 border-orange-400 mt-4 flex">
|
<div className="bg-orange-100 rounded-xl border-2 border-orange-400 mt-4 flex">
|
||||||
<Image src={`/mii/${miiId}/mii.webp`} alt="mii image" width={128} height={128} />
|
<Image src={`/mii/${miiId}/image?type=mii`} alt="mii image" width={128} height={128} />
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<p className="text-xl font-bold line-clamp-1" title={miiName}>
|
<p className="text-xl font-bold line-clamp-1" title={miiName}>
|
||||||
{miiName}
|
{miiName}
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,9 @@ export default function MiiList({ isLoggedIn, userId, sessionUserId }: Props) {
|
||||||
>
|
>
|
||||||
<Carousel
|
<Carousel
|
||||||
images={[
|
images={[
|
||||||
`/mii/${mii.id}/mii.webp`,
|
`/mii/${mii.id}/image?type=mii`,
|
||||||
`/mii/${mii.id}/qr-code.webp`,
|
`/mii/${mii.id}/image?type=qr-code`,
|
||||||
...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image${index}.webp`),
|
...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image?type=image${index}`),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export default function EditForm({ mii, likes }: Props) {
|
||||||
try {
|
try {
|
||||||
const existing = await Promise.all(
|
const existing = await Promise.all(
|
||||||
Array.from({ length: mii.imageCount }, async (_, index) => {
|
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 response = await fetch(path);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
|
|
||||||
|
|
@ -112,7 +112,9 @@ export default function EditForm({ mii, likes }: Props) {
|
||||||
<form className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
<form className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="w-[18.75rem] h-min flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3">
|
<div className="w-[18.75rem] h-min flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3">
|
||||||
<Carousel images={[`/mii/${mii.id}/mii.webp`, `/mii/${mii.id}/qr-code.webp`, ...files.map((file) => URL.createObjectURL(file))]} />
|
<Carousel
|
||||||
|
images={[`/mii/${mii.id}/image?type=mii`, `/mii/${mii.id}/image?type=qr-code`, ...files.map((file) => URL.createObjectURL(file))]}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="p-4 flex flex-col gap-1 h-full">
|
<div className="p-4 flex flex-col gap-1 h-full">
|
||||||
<h1 className="font-bold text-2xl line-clamp-1" title={name}>
|
<h1 className="font-bold text-2xl line-clamp-1" title={name}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue