import { randomUUID } from "node:crypto"; import { promises as fs } from "node:fs"; import path from "node:path"; import type { ImportJob } from "@/lib/furni/import-job"; export const importRoot = () => path.join(process.cwd(), "storage", "furniture-imports"); export const validJobId = (value: unknown): value is string => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( value, ); export class ImportJobStore { constructor(readonly root = importRoot()) {} async save(job: ImportJob) { await fs.mkdir(this.root, { recursive: true }); const target = path.join(this.root, `${job.id}.json`), tmp = `${target}.${randomUUID()}.tmp`; job.updatedAt = new Date().toISOString(); await fs.writeFile(tmp, JSON.stringify(job)); await fs.rename(tmp, target); } async create(job: ImportJob) { if (!validJobId(job.id)) throw Error("Invalid import ID"); await fs.mkdir(this.root, { recursive: true }); const tmp = path.join(this.root, `${randomUUID()}.tmp`); await fs.writeFile(tmp, JSON.stringify(job)); try { await fs.link(tmp, path.join(this.root, `${job.id}.json`)); return job; } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; const existing = await this.read(job.id); if (existing.userId !== job.userId) throw Error("Request ID already used"); return existing; } finally { await fs.unlink(tmp); } } async read(id: string): Promise { if (!validJobId(id)) throw Error("Invalid import ID"); return JSON.parse( await fs.readFile(path.join(this.root, `${id}.json`), "utf8"), ); } async list(): Promise { const files = await fs.readdir(this.root).catch((error) => { if (error.code === "ENOENT") return []; throw error; }); const jobs = await Promise.all( files .filter((f) => f.endsWith(".json") && validJobId(f.slice(0, -5))) .map((f) => this.read(f.slice(0, -5))), ); return jobs.sort((a, b) => a.createdAt.localeCompare(b.createdAt)); } }