import { eq } from "drizzle-orm"; import { getDb } from "../../../db"; import { mockExams, progress, studyDays } from "../../../db/schema"; const user = (r: Request) => r.headers.get("oai-authenticated-user-id"); const validDate = (value: unknown) => typeof value === "string" && /^2026-(09|10|11|12)-\d{2}$/.test(value); const safeText = (value: unknown, max: number) => typeof value === "string" ? value.slice(0, max) : ""; const taskKeys = new Set(["vocab", "listening", "reading", "output", "review"]); export async function GET(r: Request) { const id = user(r); if (!id) return Response.json({ error: "请登录后再导出数据。" }, { status: 401 }); try { const db = getDb(); const [allProgress, allStudyDays, allMockExams] = await Promise.all([ db.select().from(progress).where(eq(progress.userId, id)), db.select().from(studyDays).where(eq(studyDays.userId, id)), db.select().from(mockExams).where(eq(mockExams.userId, id)), ]); return Response.json({ format: "cet6-checkin-backup", version: 1, exportedAt: new Date().toISOString(), data: { progress: allProgress.map(({ userId, ...row }) => row), studyDays: allStudyDays.map(({ userId, ...row }) => row), mockExams: allMockExams.map(({ userId, ...row }) => row), } }); } catch { return Response.json({ error: "暂时无法导出云端数据。" }, { status: 500 }); } } export async function POST(r: Request) { const id = user(r); if (!id) return Response.json({ error: "请登录后再导入数据。" }, { status: 401 }); let backup: any; try { backup = await r.json(); } catch { return Response.json({ error: "文件不是有效的 JSON。" }, { status: 400 }); } const data = backup?.data; if (backup?.format !== "cet6-checkin-backup" || backup?.version !== 1 || !data || !Array.isArray(data.progress) || !Array.isArray(data.studyDays) || !Array.isArray(data.mockExams) || data.progress.length > 90 || data.studyDays.length > 90 || data.mockExams.length > 100) return Response.json({ error: "这不是可识别的六级打卡备份文件。" }, { status: 400 }); try { const db = getDb(), now = new Date().toISOString(); for (const item of data.progress) { if (!validDate(item?.planDate) || !Array.isArray(item?.doneTasks) || item.doneTasks.some((key: unknown) => typeof key !== "string" || !taskKeys.has(key))) return Response.json({ error: "打卡记录中含有无效数据。" }, { status: 400 }); const values = { userId: id, planDate: item.planDate, doneTasks: JSON.stringify([...new Set(item.doneTasks)]), mood: safeText(item.mood, 20), note: safeText(item.note, 1000), checkinType: item.checkinType === "makeup" ? "makeup" : "normal", checkedAt: safeText(item.checkedAt, 40) || now, updatedAt: now }; await db.insert(progress).values(values).onConflictDoUpdate({ target: [progress.userId, progress.planDate], set: values }); } for (const item of data.studyDays) { if (!validDate(item?.planDate)) return Response.json({ error: "学习记录中含有无效日期。" }, { status: 400 }); let durations: Record = {}; try { durations = typeof item.durations === "string" ? JSON.parse(item.durations) : item.durations || {}; } catch { return Response.json({ error: "学习用时格式不正确。" }, { status: 400 }); } const cleanDurations = Object.fromEntries(Object.entries(durations).map(([key, value]) => [key, Math.max(0, Math.min(600, Number(value) || 0))])); const values = { userId: id, planDate: item.planDate, durations: JSON.stringify(cleanDurations), weeklyReview: safeText(item.weeklyReview, 1500), nextFocus: safeText(item.nextFocus, 400), updatedAt: now }; await db.insert(studyDays).values(values).onConflictDoUpdate({ target: [studyDays.userId, studyDays.planDate], set: values }); } for (const item of data.mockExams) { if (!validDate(item?.examDate)) return Response.json({ error: "模考记录中含有无效日期。" }, { status: 400 }); const score = (value: unknown) => safeText(value, 16).replace(/[^\d.]/g, "").slice(0, 6); const values = { userId: id, examDate: item.examDate, listening: score(item.listening), reading: score(item.reading), writing: score(item.writing), translation: score(item.translation), note: safeText(item.note, 1000), updatedAt: now }; await db.insert(mockExams).values(values).onConflictDoUpdate({ target: [mockExams.userId, mockExams.examDate], set: values }); } return Response.json({ imported: { progress: data.progress.length, studyDays: data.studyDays.length, mockExams: data.mockExams.length } }); } catch { return Response.json({ error: "导入失败,云端数据未能完整更新。" }, { status: 500 }); } }