public
anord
read
Ledger
Why work hard when you can work easier?
Languages
Repository composition by tracked source files.
TypeScript
86%
CSS
10%
SQL
3%
Shell
1%
HTML
0%
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import crypto from "node:crypto";
import { Router } from "express";
import { z } from "zod";
import { requireEditor } from "../middleware/auth.js";
import { env } from "../../config/env.js";
import { pool } from "../../db/pool.js";
import { getPageMetadata } from "../../services/pages.js";
const attachmentSchema = z.object({
pageId: z.string().uuid(),
fileName: z.string().min(1),
contentType: z.string().min(3),
base64Data: z.string().min(1)
});
export const attachmentsRouter = Router();
attachmentsRouter.get("/", async (req, res) => {
const pageId = String(req.query.pageId ?? "");
const page = await getPageMetadata(pageId, req.user ?? null);
if (!page) {
return res.status(404).json({ error: "Page not found" });
}
const result = await pool.query(
`SELECT id, page_id, file_name, content_type, storage_path, size_bytes, created_at
FROM attachments
WHERE page_id = $1
ORDER BY created_at DESC`,
[pageId]
);
return res.json({ attachments: result.rows });
});
attachmentsRouter.post("/", requireEditor, async (req, res) => {
const input = attachmentSchema.parse(req.body);
const page = await getPageMetadata(input.pageId, req.user ?? null);
if (!page) {
return res.status(404).json({ error: "Page not found" });
}
if (env.STORAGE_PROVIDER !== "local") {
return res.status(400).json({ error: "This Ledger deployment is configured for local storage only." });
}
const buffer = Buffer.from(input.base64Data, "base64");
const fileId = crypto.randomUUID();
const ext = path.extname(input.fileName);
const fileName = `${fileId}${ext}`;
await mkdir(env.LOCAL_STORAGE_ROOT, { recursive: true });
const storagePath = path.join(env.LOCAL_STORAGE_ROOT, fileName);
await writeFile(storagePath, buffer);
const created = await pool.query(
`
INSERT INTO attachments (page_id, file_name, content_type, storage_path, size_bytes, created_by_user_id)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, file_name, storage_path, size_bytes
`,
[input.pageId, input.fileName, input.contentType, storagePath, buffer.length, req.user!.id]
);
return res.status(201).json(created.rows[0]);
});