From 626016d68908307d6e14e1e2b8d4a6a96ccd60fc Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Sat, 12 Apr 2025 17:00:42 +0100 Subject: [PATCH] feat: api mii list route --- src/app/api/mii/list/route.ts | 93 +++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/app/api/mii/list/route.ts diff --git a/src/app/api/mii/list/route.ts b/src/app/api/mii/list/route.ts new file mode 100644 index 0000000..7ed91df --- /dev/null +++ b/src/app/api/mii/list/route.ts @@ -0,0 +1,93 @@ +import { NextRequest, NextResponse } from "next/server"; +import { Prisma } from "@prisma/client"; +import { z } from "zod"; + +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { nameSchema, tagsSchema } from "@/lib/schemas"; + +const searchSchema = z.object({ + query: nameSchema.optional(), + sort: z.enum(["newest", "likes"], { message: "Sort must be either 'newest' or 'likes'" }).default("newest"), + tags: z + .string() + .optional() + .transform((value) => + value + ?.split(",") + .map((tag) => tag.trim()) + .filter((tag) => tag.length > 0) + ), + userId: z.coerce + .number({ message: "User ID must be valid" }) + .int({ message: "User ID must be an integer" }) + .positive({ message: "User ID must be valid" }) + .optional(), +}); + +export async function GET(request: NextRequest) { + const session = await auth(); + + const parsed = searchSchema.safeParse(Object.fromEntries(request.nextUrl.searchParams)); + if (!parsed.success) return NextResponse.json({ error: parsed.error.errors[0].message }, { status: 400 }); + + const { query, sort, tags, userId } = parsed.data; + + const where: Prisma.MiiWhereInput = { + // Searching + ...(query && { + OR: [{ name: { contains: query, mode: "insensitive" } }, { tags: { has: query } }], + }), + // Tag filtering + ...(tags && tags.length > 0 && { tags: { hasEvery: tags } }), + // Profiles + ...(userId && { userId }), + }; + + // Sorting by likes or newest + const orderBy: Prisma.MiiOrderByWithRelationInput = sort === "likes" ? { likedBy: { _count: "desc" } } : { createdAt: "desc" }; + + const select: Prisma.MiiSelect = { + id: true, + // Don't show when userId is specified + ...(!userId && { + user: { + select: { + id: true, + username: true, + }, + }, + }), + name: true, + imageCount: true, + tags: true, + createdAt: true, + // Mii liked check + likedBy: { + where: session && session.user?.id ? { userId: Number(session.user.id) } : {}, + select: { + userId: true, + }, + }, + // Like count + _count: { + select: { likedBy: true }, + }, + }; + + const [totalCount, filteredCount, list] = await Promise.all([ + prisma.mii.count({ where: userId ? { userId } : {} }), + prisma.mii.count({ where }), + prisma.mii.findMany({ where, orderBy, select }), + ]); + + return NextResponse.json({ + total: totalCount, + filtered: filteredCount, + miis: list.map(({ _count, likedBy, ...rest }) => ({ + ...rest, + likes: _count.likedBy, + isLiked: likedBy.length > 0, + })), + }); +}