feat: 批量导入支持记录三方 id,实现增量更新,存量覆盖

This commit is contained in:
richarjiang
2026-05-12 10:26:08 +08:00
parent e2fd091b4e
commit e0b88e68e9
5 changed files with 127 additions and 53 deletions

View File

@@ -56,6 +56,38 @@ function parsePosition(raw: unknown): number | undefined {
return n
}
function parseRiddleId(raw: unknown): string | null {
if (raw === undefined || raw === null || raw === '') return null
if (typeof raw !== 'string') {
throw new Error('riddleId 必须是字符串')
}
const riddleId = raw.trim()
if (!riddleId) return null
if (riddleId.length > 128) {
throw new Error('riddleId 长度不能超过 128')
}
return riddleId
}
function optionalString(raw: unknown): string | null {
return raw ? String(raw) : null
}
function buildLevelData(body: Record<string, unknown>) {
return {
image1Url: body.image1Url as string,
image1Description: optionalString(body.image1Description),
image2Url: body.image2Url as string,
image2Description: optionalString(body.image2Description),
answer: body.answer as string,
punchline: optionalString(body.punchline),
hint1: optionalString(body.hint1),
hint2: optionalString(body.hint2),
hint3: optionalString(body.hint3),
}
}
// POST /api/levels - Create a new level
// body.position 可选1..N+1不传则追加末尾
export async function POST(request: NextRequest) {
@@ -69,17 +101,7 @@ export async function POST(request: NextRequest) {
}
const body = await request.json()
const {
image1Url,
image1Description,
image2Url,
image2Description,
answer,
punchline,
hint1,
hint2,
hint3,
} = body
const { image1Url, image2Url, answer } = body
if (!image1Url || !image2Url || !answer) {
return NextResponse.json(
@@ -89,15 +111,49 @@ export async function POST(request: NextRequest) {
}
let position: number | undefined
let riddleId: string | null
try {
position = parsePosition(body.position)
riddleId = parseRiddleId(body.riddleId)
} catch (e) {
return NextResponse.json(
{ error: e instanceof Error ? e.message : 'invalid position' },
{ error: e instanceof Error ? e.message : 'invalid level payload' },
{ status: 400 }
)
}
const data = buildLevelData(body)
if (riddleId) {
const existing = await prisma.level.findUnique({
where: { riddleId },
})
if (existing) {
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, existing.id)
}
const level = await prisma.level.update({
where: { id: existing.id },
data: {
...data,
...(newSortKey ? { sortKey: newSortKey } : {}),
},
})
return NextResponse.json(level)
}
}
let sortKey: string
if (position === undefined) {
sortKey = await keyAfterLast()
@@ -115,15 +171,8 @@ export async function POST(request: NextRequest) {
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,
riddleId,
...data,
sortKey,
},
})
@@ -151,18 +200,7 @@ export async function PUT(request: NextRequest) {
}
const body = await request.json()
const {
id,
image1Url,
image1Description,
image2Url,
image2Description,
answer,
punchline,
hint1,
hint2,
hint3,
} = body
const { id } = body
if (!id) {
return NextResponse.json({ error: 'id is required' }, { status: 400 })
@@ -191,18 +229,12 @@ export async function PUT(request: NextRequest) {
newSortKey = await keyForPosition(position, id)
}
const data = buildLevelData(body)
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,
...data,
...(newSortKey ? { sortKey: newSortKey } : {}),
},
})