From 1bf83e4cae88ad09def4b2b6e43afcad54d5350d Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Mon, 30 Mar 2026 12:50:17 +0100 Subject: [PATCH] fix: likes not working with cache rules (#18) --- src/app/api/mii/has-liked/route.ts | 28 +++++++++ src/app/mii/[id]/page.tsx | 12 +--- src/components/like-button.tsx | 4 ++ src/components/mii/author-buttons.tsx | 37 +++++++++++ src/components/mii/list/index.tsx | 71 ++------------------- src/components/mii/list/mii-grid.tsx | 88 +++++++++++++++++++++++++++ 6 files changed, 164 insertions(+), 76 deletions(-) create mode 100644 src/app/api/mii/has-liked/route.ts create mode 100644 src/components/mii/author-buttons.tsx create mode 100644 src/components/mii/list/mii-grid.tsx diff --git a/src/app/api/mii/has-liked/route.ts b/src/app/api/mii/has-liked/route.ts new file mode 100644 index 0000000..ecb0e87 --- /dev/null +++ b/src/app/api/mii/has-liked/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { RateLimit } from "@/lib/rate-limit"; + +export async function GET(request: NextRequest) { + const session = await auth(); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const rateLimit = new RateLimit(request, 50, "/api/mii/like_get"); + const check = await rateLimit.handle(); + if (check) return check; + + const idsParam = new URL(request.url).searchParams.get("ids"); + if (!idsParam) return NextResponse.json({ error: "Missing IDs parameter" }, { status: 400 }); + + const ids = idsParam.split(",").map(Number).filter(Boolean); + if (!ids.length) return NextResponse.json({ error: "No valid IDs provided" }, { status: 400 }); + if (ids.length > 100) return NextResponse.json({ error: "Too many IDs, maximum is 100" }, { status: 400 }); + + const liked = await prisma.like.findMany({ + where: { userId: Number(session.user?.id), miiId: { in: ids } }, + select: { miiId: true }, + }); + + // Return only Miis that are liked + return NextResponse.json(liked.map((l) => l.miiId)); +} diff --git a/src/app/mii/[id]/page.tsx b/src/app/mii/[id]/page.tsx index 8747124..491944b 100644 --- a/src/app/mii/[id]/page.tsx +++ b/src/app/mii/[id]/page.tsx @@ -11,7 +11,7 @@ import { MiiPlatform } from "@prisma/client"; import LikeButton from "@/components/like-button"; import ImageViewer from "@/components/image-viewer"; -import DeleteMiiButton from "@/components/mii/delete-mii-button"; +import AuthorButtons from "@/components/mii/author-buttons"; import ShareMiiButton from "@/components/mii/share-mii-button"; import ThreeDsScanTutorialButton from "@/components/tutorial/3ds-scan"; import SwitchScanTutorialButton from "@/components/tutorial/switch-add-mii"; @@ -359,15 +359,7 @@ export default async function MiiPage({ params }: Props) { {/* Buttons */}
- {session && (Number(session.user?.id) === mii.userId || Number(session.user?.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) && ( - <> - - - Edit - - - - )} + diff --git a/src/components/like-button.tsx b/src/components/like-button.tsx index f264092..7628beb 100644 --- a/src/components/like-button.tsx +++ b/src/components/like-button.tsx @@ -56,6 +56,10 @@ export default function LikeButton({ likes, isLiked, miiId, disabled, abbreviate loadIcons(["icon-park-solid:like", "icon-park-outline:like"]); }, []); + useEffect(() => { + setIsLikedState(isLiked); + }, [isLiked]); + return (
-
- {miis.map((mii) => ( -
- `/mii/${mii.id}/image?type=image${index}`), - ]} - /> - -
-
- - {mii.name} - -
- {mii.platform === "SWITCH" ? ( - - ) : ( - - )} -
-
-
- {mii.tags.map((tag) => ( - - {tag} - - ))} -
- -
- - - {!userId && ( - - @{mii.user?.name} - - )} - - {userId && Number(session?.user?.id) == userId && ( -
- - - - -
- )} -
-
-
- ))} -
- + ); diff --git a/src/components/mii/list/mii-grid.tsx b/src/components/mii/list/mii-grid.tsx new file mode 100644 index 0000000..fd346de --- /dev/null +++ b/src/components/mii/list/mii-grid.tsx @@ -0,0 +1,88 @@ +"use client"; + +import Link from "next/link"; +import useSWR from "swr"; +import { Prisma } from "@prisma/client"; +import { useSession } from "next-auth/react"; +import { Icon } from "@iconify/react"; + +import LikeButton from "@/components/like-button"; +import DeleteMiiButton from "../delete-mii-button"; +import Carousel from "@/components/carousel"; + +interface Props { + miis: Prisma.MiiGetPayload<{ include: { user: { select: { id: true; name: true } }; _count: { select: { likedBy: true } } } }>[]; + userId?: number; +} + +const fetcher = (url: string) => fetch(url).then((res) => res.json()); + +export default function MiiGrid({ miis, userId }: Props) { + const session = useSession(); + const ids = miis.map((m) => m.id).join(","); + const { data } = useSWR(session.data?.user && miis.length > 0 ? `/api/mii/has-liked?ids=${ids}` : null, fetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: false, + }); + const likedIds = new Set(data ?? []); + + return ( +
+ {miis.map((mii) => ( +
+ `/mii/${mii.id}/image?type=image${index}`), + ]} + /> + +
+
+ + {mii.name} + +
+ {mii.platform === "SWITCH" ? ( + + ) : ( + + )} +
+
+
+ {mii.tags.map((tag) => ( + + {tag} + + ))} +
+ +
+ + + {!userId && ( + + @{mii.user?.name} + + )} + + {userId && Number(session.data?.user?.id) == userId && ( +
+ + + + +
+ )} +
+
+
+ ))} +
+ ); +}