soc2-cockpit/server/lib/store.js

78 lines
2.7 KiB
JavaScript

import fs from "node:fs/promises";
import path from "node:path";
import { buildRegistryManifests, normalizeDb, validateRegistry } from "../../src/lib/validation.js";
import { INITIAL_DB } from "../../src/model/seed.js";
import { evidenceDir, manifestsDir, registryPath } from "../config.js";
async function ensureDirs() {
await fs.mkdir(manifestsDir, { recursive: true });
await fs.mkdir(evidenceDir, { recursive: true });
}
export async function loadRegistry() {
await ensureDirs();
try {
const raw = await fs.readFile(registryPath, "utf-8");
return normalizeDb(JSON.parse(raw));
} catch {
return normalizeDb(INITIAL_DB);
}
}
export async function writeRegistry(input) {
await ensureDirs();
const normalized = normalizeDb(input);
const validation = validateRegistry(normalized);
if (!validation.isValid) {
const error = new Error("Registry validation failed.");
error.validation = validation;
throw error;
}
await fs.writeFile(registryPath, JSON.stringify(normalized, null, 2), "utf-8");
const manifests = buildRegistryManifests(normalized);
await fs.writeFile(path.join(manifestsDir, "evidence-manifest.json"), JSON.stringify(manifests.evidenceManifest, null, 2), "utf-8");
await fs.writeFile(path.join(manifestsDir, "auditor-pack-index.json"), JSON.stringify(manifests.auditorPackIndex, null, 2), "utf-8");
return { registry: normalized, validation, manifests };
}
export async function resetRegistry() {
return writeRegistry(INITIAL_DB);
}
export async function listEvidenceArtifacts() {
await ensureDirs();
const names = await fs.readdir(evidenceDir).catch(() => []);
const stats = await Promise.all(
names
.filter((name) => !name.startsWith("."))
.map(async (name) => {
const fullPath = path.join(evidenceDir, name);
const stat = await fs.stat(fullPath);
return {
filename: name,
size: stat.size,
modifiedAt: stat.mtime.toISOString(),
path: fullPath,
};
})
);
return stats.sort((a, b) => a.filename.localeCompare(b.filename));
}
export async function writeEvidenceArtifact({ filename, contentBase64 }) {
await ensureDirs();
if (!filename || typeof filename !== "string") throw new Error("filename is required.");
if (!contentBase64 || typeof contentBase64 !== "string") throw new Error("contentBase64 is required.");
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_");
const fullPath = path.join(evidenceDir, safeName);
await fs.writeFile(fullPath, Buffer.from(contentBase64, "base64"));
const stat = await fs.stat(fullPath);
return {
filename: safeName,
size: stat.size,
modifiedAt: stat.mtime.toISOString(),
path: fullPath,
};
}