Compare commits
10
Commits
c8c330ea2d
...
main
No files matched your search
@@ -0,0 +1,57 @@
|
|||||||
|
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<string, number> = {}; 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 }); }
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { progress } from "../../../db/schema";
|
|||||||
|
|
||||||
const user = (r: Request) => r.headers.get("oai-authenticated-user-id");
|
const user = (r: Request) => r.headers.get("oai-authenticated-user-id");
|
||||||
const todayInChina = () => new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai" }).format(new Date());
|
const todayInChina = () => new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai" }).format(new Date());
|
||||||
|
const parseTasks = (value: string) => { try { return JSON.parse(value) as string[]; } catch { return []; } };
|
||||||
|
|
||||||
export async function GET(r: Request) {
|
export async function GET(r: Request) {
|
||||||
const id = user(r);
|
const id = user(r);
|
||||||
@@ -21,6 +22,8 @@ export async function PUT(r: Request) {
|
|||||||
if (b.date! > today) return Response.json({ error: "未来日期暂不能打卡。" }, { status: 400 });
|
if (b.date! > today) return Response.json({ error: "未来日期暂不能打卡。" }, { status: 400 });
|
||||||
try {
|
try {
|
||||||
const db = getDb(), now = new Date().toISOString(), checkinType = b.date! < today ? "makeup" : "normal";
|
const db = getDb(), now = new Date().toISOString(), checkinType = b.date! < today ? "makeup" : "normal";
|
||||||
|
const existing = await db.select().from(progress).where(and(eq(progress.userId, id), eq(progress.planDate, b.date!)));
|
||||||
|
if (b.date! < today && existing[0] && parseTasks(existing[0].doneTasks).some(task => !b.doneTasks!.includes(task))) return Response.json({ error: "过去日期已完成的项目不能取消,只能补打未完成项目。" }, { status: 400 });
|
||||||
const values = { userId: id, planDate: b.date!, doneTasks: JSON.stringify(b.doneTasks), mood: (b.mood || "").slice(0, 20), note: (b.note || "").slice(0, 1000), checkinType, checkedAt: now, updatedAt: now };
|
const values = { userId: id, planDate: b.date!, doneTasks: JSON.stringify(b.doneTasks), mood: (b.mood || "").slice(0, 20), note: (b.note || "").slice(0, 1000), checkinType, checkedAt: now, updatedAt: now };
|
||||||
await db.insert(progress).values(values).onConflictDoUpdate({ target: [progress.userId, progress.planDate], set: values });
|
await db.insert(progress).values(values).onConflictDoUpdate({ target: [progress.userId, progress.planDate], set: values });
|
||||||
const row = await db.select().from(progress).where(and(eq(progress.userId, id), eq(progress.planDate, b.date!)));
|
const row = await db.select().from(progress).where(and(eq(progress.userId, id), eq(progress.planDate, b.date!)));
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import { getDb } from "../../../db";
|
||||||
|
import { mockExams, studyDays } from "../../../db/schema";
|
||||||
|
|
||||||
|
const user = (r: Request) => r.headers.get("oai-authenticated-user-id");
|
||||||
|
const validDate = (s?: string) => /^2026-(09|10|11|12)-\d{2}$/.test(s || "");
|
||||||
|
|
||||||
|
export async function GET(r: Request) {
|
||||||
|
const id = user(r); if (!id) return Response.json({ error: "请登录后再使用学习中心。" }, { status: 401 });
|
||||||
|
try { const db = getDb(); return Response.json({ studyDays: await db.select().from(studyDays).where(eq(studyDays.userId, id)), mockExams: await db.select().from(mockExams).where(eq(mockExams.userId, id)) }); }
|
||||||
|
catch { return Response.json({ error: "暂时无法读取学习数据。" }, { status: 500 }); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(r: Request) {
|
||||||
|
const id = user(r); if (!id) return Response.json({ error: "请登录后再保存。" }, { status: 401 });
|
||||||
|
const b = await r.json() as { type?: string; date?: string; durations?: Record<string, number>; weeklyReview?: string; nextFocus?: string; listening?: string; reading?: string; writing?: string; translation?: string; note?: string };
|
||||||
|
if (!validDate(b.date)) return Response.json({ error: "日期格式不正确。" }, { status: 400 });
|
||||||
|
try {
|
||||||
|
const db = getDb(), now = new Date().toISOString();
|
||||||
|
if (b.type === "day") {
|
||||||
|
const durations = Object.fromEntries(Object.entries(b.durations || {}).map(([k, v]) => [k, Math.max(0, Math.min(600, Number(v) || 0))]));
|
||||||
|
const values = { userId: id, planDate: b.date!, durations: JSON.stringify(durations), weeklyReview: (b.weeklyReview || "").slice(0, 1500), nextFocus: (b.nextFocus || "").slice(0, 400), updatedAt: now };
|
||||||
|
await db.insert(studyDays).values(values).onConflictDoUpdate({ target: [studyDays.userId, studyDays.planDate], set: values });
|
||||||
|
return Response.json({ studyDay: (await db.select().from(studyDays).where(and(eq(studyDays.userId, id), eq(studyDays.planDate, b.date!))))[0] });
|
||||||
|
}
|
||||||
|
if (b.type === "mock") {
|
||||||
|
const clean = (v?: string) => String(v || "").replace(/[^\d.]/g, "").slice(0, 6);
|
||||||
|
const values = { userId: id, examDate: b.date!, listening: clean(b.listening), reading: clean(b.reading), writing: clean(b.writing), translation: clean(b.translation), note: (b.note || "").slice(0, 1000), updatedAt: now };
|
||||||
|
await db.insert(mockExams).values(values).onConflictDoUpdate({ target: [mockExams.userId, mockExams.examDate], set: values });
|
||||||
|
return Response.json({ mockExam: (await db.select().from(mockExams).where(and(eq(mockExams.userId, id), eq(mockExams.examDate, b.date!))))[0] });
|
||||||
|
}
|
||||||
|
return Response.json({ error: "请求类型不正确。" }, { status: 400 });
|
||||||
|
} catch { return Response.json({ error: "暂时无法保存,请稍后重试。" }, { status: 500 }); }
|
||||||
|
}
|
||||||
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user