import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' import { auth } from '@/lib/auth' import { compareSortKey, keyAfterLast, keyForPosition } from '@/lib/sort-key' import { v4 as uuidv4 } from 'uuid' // GET /api/levels - Get all levels export async function GET(request: NextRequest) { try { const session = await auth.api.getSession({ headers: request.headers, }) if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } // 注意:不能交给 MySQL 排序。默认 collation 大小写不敏感, // 会把 fractional-indexing 的 Z 系列 key 错排到 z 系列之后。 // 拉全表到应用层按字节序排。 const rows = await prisma.level.findMany() rows.sort((a, b) => { const c = compareSortKey(a.sortKey, b.sortKey) if (c !== 0) return c if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder return a.createdAt.getTime() - b.createdAt.getTime() }) // 响应层回填连续整数 sortOrder,保持前端 / 小程序对这个字段的既有依赖 const levels = rows.map((r, i) => ({ ...r, sortOrder: i })) return NextResponse.json(levels) } catch (error) { console.error('Error fetching levels:', error) return NextResponse.json( { error: 'Failed to fetch levels' }, { status: 500 } ) } } /** * 解析 position: * - undefined / null → 返回 undefined,交给调用方走"默认行为" * - 数字字符串 → 解析成整数 * - 其它 → 抛错 * * 合法范围的校验由调用方根据当前上下文(创建 / 编辑)判断。 */ function parsePosition(raw: unknown): number | undefined { if (raw === undefined || raw === null || raw === '') return undefined const n = typeof raw === 'number' ? raw : Number(raw) if (!Number.isInteger(n) || n < 1) { throw new Error('position 必须是 >= 1 的整数') } return n } // POST /api/levels - Create a new level // body.position 可选:1..N+1;不传则追加末尾 export async function POST(request: NextRequest) { try { const session = await auth.api.getSession({ headers: request.headers, }) if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const body = await request.json() const { image1Url, image1Description, image2Url, image2Description, answer, punchline, hint1, hint2, hint3, } = body if (!image1Url || !image2Url || !answer) { return NextResponse.json( { error: 'image1Url, image2Url and answer are required' }, { status: 400 } ) } let position: number | undefined try { position = parsePosition(body.position) } catch (e) { return NextResponse.json( { error: e instanceof Error ? e.message : 'invalid position' }, { status: 400 } ) } let sortKey: string if (position === undefined) { sortKey = await keyAfterLast() } else { const total = await prisma.level.count() if (position > total + 1) { return NextResponse.json( { error: `position 超出范围,合法区间 [1, ${total + 1}]` }, { status: 400 } ) } sortKey = await keyForPosition(position) } const level = await prisma.level.create({ data: { id: uuidv4(), image1Url, image1Description: image1Description || null, image2Url, image2Description: image2Description || null, answer, punchline: punchline || null, hint1: hint1 || null, hint2: hint2 || null, hint3: hint3 || null, sortKey, }, }) return NextResponse.json(level, { status: 201 }) } catch (error) { console.error('Error creating level:', error) return NextResponse.json( { error: 'Failed to create level' }, { status: 500 } ) } } // PUT /api/levels - Update a level // body.position 可选:1..N(N 含当前行)。不传表示不移动位置。 export async function PUT(request: NextRequest) { try { const session = await auth.api.getSession({ headers: request.headers, }) if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const body = await request.json() const { id, image1Url, image1Description, image2Url, image2Description, answer, punchline, hint1, hint2, hint3, } = body if (!id) { return NextResponse.json({ error: 'id is required' }, { status: 400 }) } let position: number | undefined try { position = parsePosition(body.position) } catch (e) { return NextResponse.json( { error: e instanceof Error ? e.message : 'invalid position' }, { status: 400 } ) } // 只有在 position 确实变化时才重算 sortKey,避免无谓写入 let newSortKey: string | undefined if (position !== undefined) { const total = await prisma.level.count() if (position > total) { return NextResponse.json( { error: `position 超出范围,合法区间 [1, ${total}]` }, { status: 400 } ) } newSortKey = await keyForPosition(position, id) } const level = await prisma.level.update({ where: { id }, data: { image1Url, image1Description: image1Description || null, image2Url, image2Description: image2Description || null, answer, punchline: punchline || null, hint1: hint1 || null, hint2: hint2 || null, hint3: hint3 || null, ...(newSortKey ? { sortKey: newSortKey } : {}), }, }) return NextResponse.json(level) } catch (error) { console.error('Error updating level:', error) return NextResponse.json( { error: 'Failed to update level' }, { status: 500 } ) } } // DELETE /api/levels - Delete a level export async function DELETE(request: NextRequest) { try { const session = await auth.api.getSession({ headers: request.headers, }) if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const { searchParams } = new URL(request.url) const id = searchParams.get('id') if (!id) { return NextResponse.json({ error: 'id is required' }, { status: 400 }) } await prisma.level.delete({ where: { id }, }) return NextResponse.json({ success: true }) } catch (error) { console.error('Error deleting level:', error) return NextResponse.json( { error: 'Failed to delete level' }, { status: 500 } ) } }