Initial commit: add FastAPI MVP (모프) and existing web app
Includes FastAPI+Jinja2+HTMX+SQLite implementation with seed categories, plus deployment templates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
80
apps/web/src/app/api/prompts/route.ts
Normal file
80
apps/web/src/app/api/prompts/route.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
const QuerySchema = z.object({
|
||||
query: z.string().trim().min(1).max(200).optional(),
|
||||
entity: z.string().trim().min(1).max(120).optional(), // entity slug
|
||||
tag: z.string().trim().min(1).max(120).optional(), // tag slug
|
||||
model: z.string().trim().min(1).max(120).optional(), // model slug
|
||||
sort: z.enum(["new", "popular"]).optional().default("new"),
|
||||
limit: z.coerce.number().int().min(1).max(50).optional().default(20),
|
||||
offset: z.coerce.number().int().min(0).max(5000).optional().default(0),
|
||||
});
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const url = new URL(req.url);
|
||||
const parsed = QuerySchema.safeParse({
|
||||
query: url.searchParams.get("query") ?? undefined,
|
||||
entity: url.searchParams.get("entity") ?? undefined,
|
||||
tag: url.searchParams.get("tag") ?? undefined,
|
||||
model: url.searchParams.get("model") ?? undefined,
|
||||
sort: url.searchParams.get("sort") ?? undefined,
|
||||
limit: url.searchParams.get("limit") ?? undefined,
|
||||
offset: url.searchParams.get("offset") ?? undefined,
|
||||
});
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ ok: false, error: "INVALID_QUERY", detail: parsed.error.flatten() },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const { query, entity, tag, model, sort, limit, offset } = parsed.data;
|
||||
|
||||
const where = {
|
||||
isPublished: true,
|
||||
...(entity ? { entity: { slug: entity } } : {}),
|
||||
...(model ? { model: { slug: model } } : {}),
|
||||
...(tag
|
||||
? { tags: { some: { tag: { slug: tag } } } }
|
||||
: {}),
|
||||
...(query
|
||||
? {
|
||||
OR: [
|
||||
{ title: { contains: query, mode: "insensitive" as const } },
|
||||
{
|
||||
descriptionMd: { contains: query, mode: "insensitive" as const },
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const orderBy =
|
||||
sort === "popular"
|
||||
? ({ likes: { _count: "desc" } } as const)
|
||||
: ({ createdAt: "desc" } as const);
|
||||
|
||||
const prompts = await prisma.prompt.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
take: limit,
|
||||
skip: offset,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
descriptionMd: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
entity: { select: { slug: true, name: true } },
|
||||
model: { select: { slug: true, name: true, provider: true } },
|
||||
tags: { select: { tag: { select: { slug: true, name: true } } } },
|
||||
_count: { select: { likes: true, comments: true, bookmarks: true } },
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ ok: true, items: prompts });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user