一键导入
api-route
Generate a new Next.js API route with project boilerplate (auth, validation, sanitize, Prisma). Use when creating new API endpoints.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate a new Next.js API route with project boilerplate (auth, validation, sanitize, Prisma). Use when creating new API endpoints.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Launch, build, run, start, screenshot, or smoke-test the Librariarr Next.js webapp end-to-end. Use when asked to run librariarr, bring up the dev stack, take a screenshot of the dashboard or any UI page, verify a change in the running app, or check that the app boots cleanly.
Generate an integration test for an API route with all required mocks and boilerplate. Use when creating tests for API endpoints.
Quick reference for all Librariarr project conventions and patterns. Consult when writing or reviewing code to verify correct patterns.
Create a Prisma migration file for schema changes. Required for production — db push only works in dev. Use after modifying schema.prisma.
Scaffold a complete new feature end-to-end (Prisma model, migration, schemas, API routes, tests). Use for new CRUD resources or features that span multiple layers.
Add a new Zod validation schema to src/lib/validation.ts. All schemas MUST live in this file using zod/v4. Use when creating validation for new API endpoints.
| name | api-route |
| description | Generate a new Next.js API route with project boilerplate (auth, validation, sanitize, Prisma). Use when creating new API endpoints. |
| argument-hint | <route-path> [HTTP-methods] [prisma-model] |
Generate a new API route at src/app/api/$ARGUMENTS.
import { NextRequest, NextResponse } from "next/server";
import { getSession } from "@/lib/auth/session";
import { prisma } from "@/lib/db"; // NEVER @/lib/prisma
import { validateRequest, schemaName } from "@/lib/validation"; // schemas ONLY in this file
import { sanitize, sanitizeErrorDetail } from "@/lib/api/sanitize";
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { data, error } = await validateRequest(request, schemaName);
if (error) return error;
The Zod schema MUST be defined in src/lib/validation.ts using zod/v4, NOT inline in the route file.
sanitize()sanitizeErrorDetail()fileSize fields must be serialized: fileSize: item.fileSize?.toString() ?? nullwhere: { userId: session.userId! }where: { library: { mediaServer: { userId: session.userId } } }findFirst with userId filter, NOT bare findUnique({ where: { id } })export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
// ...
}
// For external service failures:
return NextResponse.json(
{ error: "Failed to connect to Service", detail: sanitizeErrorDetail(result.error) },
{ status: 400 }
);
// For not found (always verify ownership):
const existing = await prisma.model.findFirst({
where: { id, userId: session.userId! },
});
if (!existing) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
route.ts — no dynamic segment):export async function GET() {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const items = await prisma.model.findMany({
where: { userId: session.userId! },
orderBy: { createdAt: "desc" },
});
return NextResponse.json({ items: sanitize(items) });
}
export async function POST(request: NextRequest) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { data, error } = await validateRequest(request, createSchema);
if (error) return error;
const item = await prisma.model.create({
data: { userId: session.userId!, ...data },
});
return NextResponse.json({ item: sanitize(item) }, { status: 201 });
}
[id]/route.ts):export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const { data, error } = await validateRequest(request, updateSchema);
if (error) return error;
const existing = await prisma.model.findFirst({
where: { id, userId: session.userId! },
});
if (!existing) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const updated = await prisma.model.update({ where: { id }, data });
return NextResponse.json({ item: sanitize(updated) });
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const existing = await prisma.model.findFirst({
where: { id, userId: session.userId! },
});
if (!existing) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
await prisma.model.delete({ where: { id } });
return NextResponse.json({ success: true });
}
export async function GET(request: NextRequest) {
const session = await getSession();
if (!session.isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") ?? "1");
const rawLimit = parseInt(searchParams.get("limit") ?? "50");
const limit = rawLimit === 0 ? 0 : Math.min(rawLimit, 100);
const where = { userId: session.userId! };
const items = await prisma.model.findMany({
where,
...(limit > 0 ? { skip: (page - 1) * limit, take: limit + 1 } : {}),
orderBy: { createdAt: "desc" },
});
const hasMore = limit > 0 && items.length > limit;
if (hasMore) items.pop();
return NextResponse.json({
items: sanitize(items),
pagination: { page, limit, hasMore },
});
}
import { resolveServerFilter } from "@/lib/dedup/server-filter";
// Inside handler:
const sf = await resolveServerFilter(session.userId!, serverId, "MOVIE");
if (!sf) {
return NextResponse.json({ items: [], pagination: { page, limit, hasMore: false } });
}
const where: Prisma.MediaItemWhereInput = {
library: { mediaServerId: { in: sf.serverIds } },
type: "MOVIE",
};
if (!sf.isSingleServer) where.dedupCanonical = true;
src/lib/validation.ts using zod/v4src/app/api/<route-path>/route.ts using the patterns above[id], use the Next.js 16 async params patternpnpm lint to verify no lint errors