Implementa o MVP do Adaptador de Planilhas no Elo Pro.
O módulo usa a identidade Supabase, isola cada workspace e registra storage, TTL, runner e biblioteca XLSX como decisões ainda abertas. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
6e23f1533b
144 files changed
+19060
No files matched your search
@@ -0,0 +1,64 @@
|
||||
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { AdapterError } from "@/domain/spreadsheet-adapter/errors";
|
||||
import { assertStorageKey } from "@/domain/spreadsheet-adapter/filenames";
|
||||
import type { ObjectStorage } from "./memory";
|
||||
|
||||
export class LocalPrivateStorage implements ObjectStorage {
|
||||
constructor(private readonly root: string) {}
|
||||
|
||||
private resolve(key: string): string {
|
||||
assertStorageKey(key);
|
||||
const full = path.resolve(this.root, ...key.split("/"));
|
||||
const root = path.resolve(this.root);
|
||||
if (full !== root && !full.startsWith(root + path.sep)) {
|
||||
throw new AdapterError("PATH_TRAVERSAL", "Chave de armazenamento rejeitada.");
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
async put(key: string, body: Buffer): Promise<void> {
|
||||
const full = this.resolve(key);
|
||||
await mkdir(path.dirname(full), { recursive: true });
|
||||
await writeFile(full, body);
|
||||
}
|
||||
|
||||
async get(key: string): Promise<Buffer> {
|
||||
try {
|
||||
return await readFile(this.resolve(key));
|
||||
} catch (error) {
|
||||
if (error instanceof AdapterError) throw error;
|
||||
throw new AdapterError("NOT_FOUND", "Arquivo não encontrado.", 404);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
try {
|
||||
await rm(this.resolve(key), { force: true });
|
||||
} catch (error) {
|
||||
if (error instanceof AdapterError) throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async list(prefix: string): Promise<string[]> {
|
||||
const normalized = prefix.replace(/\/$/, "");
|
||||
const start = this.resolve(normalized.includes("/") ? normalized : `${normalized}/noop`.replace("/noop", ""));
|
||||
const root = path.resolve(this.root);
|
||||
const found: string[] = [];
|
||||
const walk = async (dir: string) => {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) await walk(full);
|
||||
else found.push(path.relative(root, full).split(path.sep).join("/"));
|
||||
}
|
||||
};
|
||||
await walk(start);
|
||||
return found.filter((key) => key.startsWith(prefix.replace(/\/$/, "")));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user