Store calculation history on server with bazi input and chart snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
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");
|
||||
}
|
||||
|
||||
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<void> {
|
||||
await fs.mkdir(getDataDir(), { recursive: true });
|
||||
}
|
||||
|
||||
async function readUserHistory(username: string): Promise<CalcHistoryEntry[]> {
|
||||
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<void> {
|
||||
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<CalcHistoryEntry[]> {
|
||||
return readUserHistory(username);
|
||||
}
|
||||
|
||||
export async function addHistoryEntry(
|
||||
username: string,
|
||||
entry: CalcHistoryCreate,
|
||||
): Promise<CalcHistoryEntry> {
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user