feat(catalog): queue furniture imports with history and matching file attachments
This commit is contained in:
1 parent
775d14f861
commit
2619bec165
16 files changed
+1016
-232
No files matched your search
@@ -0,0 +1,58 @@
|
||||
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<ImportJob> {
|
||||
if (!validJobId(id)) throw Error("Invalid import ID");
|
||||
return JSON.parse(
|
||||
await fs.readFile(path.join(this.root, `${id}.json`), "utf8"),
|
||||
);
|
||||
}
|
||||
async list(): Promise<ImportJob[]> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user