import fs from "fs/promises"; import path from "path"; import { randomUUID } from "crypto"; import { HISTORY_MAX_ITEMS, type CalcHistoryCreate, type CalcHistoryEntry, } from "@/lib/history/types"; function getDataDir(): string { const configured = process.env.HISTORY_DATA_DIR?.trim(); if (configured) { return configured; } return path.join(process.cwd(), "data", "history"); } export function historyStoreErrorMessage(err: unknown): string { if (!(err instanceof Error)) { return "保存测算历史失败"; } const code = (err as NodeJS.ErrnoException).code; const dir = getDataDir(); if (code === "EACCES" || code === "EPERM") { return `测算历史目录无写入权限(${dir}),请检查 HISTORY_DATA_DIR 或目录权限`; } if (code === "ENOENT") { return `测算历史目录不存在(${dir}),请创建目录或配置 HISTORY_DATA_DIR`; } if (code === "ENOSPC") { return "磁盘空间不足,无法保存测算历史"; } return `保存测算历史失败:${err.message}`; } function sanitizeUsername(username: string): string { const safe = username.replace(/[^a-zA-Z0-9_-]/g, "_"); return safe || "guest"; } function userFile(username: string): string { return path.join(getDataDir(), `${sanitizeUsername(username)}.json`); } async function ensureDir(): Promise { await fs.mkdir(getDataDir(), { recursive: true }); } async function readUserHistory(username: string): Promise { await ensureDir(); try { const raw = await fs.readFile(userFile(username), "utf-8"); const parsed = JSON.parse(raw) as CalcHistoryEntry[]; return Array.isArray(parsed) ? parsed : []; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { return []; } throw error; } } async function writeUserHistory( username: string, entries: CalcHistoryEntry[], ): Promise { await ensureDir(); const file = userFile(username); const tmp = `${file}.tmp`; await fs.writeFile(tmp, JSON.stringify(entries, null, 2), "utf-8"); await fs.rename(tmp, file); } export async function listHistoryEntries( username: string, ): Promise { return readUserHistory(username); } export async function addHistoryEntry( username: string, entry: CalcHistoryCreate, ): Promise { const full: CalcHistoryEntry = { ...entry, id: randomUUID(), createdAt: new Date().toISOString(), }; const list = [full, ...(await readUserHistory(username))].slice( 0, HISTORY_MAX_ITEMS, ); await writeUserHistory(username, list); return full; } export async function deleteHistoryEntry( username: string, id: string, ): Promise { const current = await readUserHistory(username); const next = current.filter((entry) => entry.id !== id); if (next.length === current.length) { return false; } await writeUserHistory(username, next); return true; }