Compare commits

..
2 Commits
Author SHA1 Message Date
venus 417ec30bfd updated lorem ipsum in some spots 2026-09-14 01:17:42 -05:00
venus f30d636bba fix(ingestion): file deletion of old articles now syncs correctly 2026-09-14 01:17:19 -05:00
5 changed files with 89 additions and 7 deletions
+33 -1
View File
@@ -1,6 +1,7 @@
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
import { exec } from "node:child_process";
import { fileURLToPath } from "node:url";
const MIME_TYPES = {
@@ -205,12 +206,43 @@ export function createServer(customSiteDir, customPostsDir, customAssetsDir) {
}
const cleanSlug = querySlug.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
const targetFilePath = path.join(postsDir, `${cleanSlug}.md`);
if (!cleanSlug) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Invalid slug parameter" }));
return;
}
// 1. Remove source markdown post
const targetFilePath = path.join(postsDir, `${cleanSlug}.md`);
if (fs.existsSync(targetFilePath)) {
fs.unlinkSync(targetFilePath);
}
// 2. Remove compiled static site directory and file
const compiledDir = path.join(siteDir, "posts", cleanSlug);
if (fs.existsSync(compiledDir)) {
fs.rmSync(compiledDir, { recursive: true, force: true });
}
const compiledHtml = path.join(siteDir, "posts", `${cleanSlug}.html`);
if (fs.existsSync(compiledHtml)) {
fs.unlinkSync(compiledHtml);
}
// 3. Rebuild static collections (index.html, dispatches, tags) if in full Eleventy environment
try {
if (fs.existsSync(path.join(process.cwd(), ".eleventy.js"))) {
exec("npx @11ty/eleventy", (err) => {
if (err) {
console.error("[Ingestion API] Error rebuilding site after delete:", err);
} else {
console.log(`[Ingestion API] Site rebuild complete after unpublishing '${cleanSlug}'.`);
}
});
}
} catch (rebuildErr) {
console.error("[Ingestion API] Failed to trigger Eleventy rebuild:", rebuildErr);
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "deleted", slug: cleanSlug }));
return;
+3 -3
View File
@@ -105,19 +105,19 @@ export default {
{
title: "Systems Engineering",
slug: "systems",
desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
desc: "Notes from my Journey builing out interconnected systems.",
badge: "TRACK: SYSTEMS"
},
{
title: "Security Research",
slug: "security",
desc: "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
desc: "Notes from my CTF training and security research.",
badge: "TRACK: SECURITY"
},
{
title: "Homelab Infrastructure",
slug: "homelab",
desc: "Ut enim ad minim veniam, quis nostrud exercitation ullamco.",
desc: "Notes from builing out my homelab.",
badge: "TRACK: INFRA"
}
]
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="hero-pretitle">TECHNICAL PUBLICATION REPOSITORY &bull; ~/NOTES/INDEX.MD</div>
<h1 class="hero-main-title">{{ heroTitle or site.description }}</h1>
<p class="hero-landing-text">
{{ heroText or "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation." }}
{{ heroText or "Notes from my Journey builing out interconnected systems." }}
</p>
<div class="filter-pills-row" aria-label="Topic Filters">
+1 -1
View File
@@ -49,7 +49,7 @@ title: "River Rooks | Homelab Gateway"
<a href="/dispatches/" class="blog-feature-title">&gt; Technical Notes &amp; Engineering Dispatches</a>
<p class="blog-feature-desc">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
Notes from my Journey builing out interconnected systems.
</p>
<div class="blog-sublinks">
+51 -1
View File
@@ -173,6 +173,17 @@ Here is an image: ![[camera-board.png]]
const assetPath = path.join(tmpAssets, "camera-board.png");
assert.ok(fs.existsSync(assetPath), "Asset file must be saved");
// Create simulated compiled static post files in siteDir
const compiledPostDir = path.join(tmpDir, "posts", "flock-firmware");
fs.mkdirSync(compiledPostDir, { recursive: true });
const compiledPostHtml = path.join(compiledPostDir, "index.html");
fs.writeFileSync(compiledPostHtml, "<html><body>Simulated compiled post</body></html>");
const standalonePostHtml = path.join(tmpDir, "posts", "flock-firmware.html");
fs.writeFileSync(standalonePostHtml, "<html><body>Simulated standalone post</body></html>");
assert.ok(fs.existsSync(compiledPostHtml), "Simulated compiled post must exist before delete");
assert.ok(fs.existsSync(standalonePostHtml), "Simulated standalone post must exist before delete");
// Delete post
const resDelete = await fetch(`http://127.0.0.1:${port}/api/posts?slug=flock-firmware`, {
method: "DELETE",
@@ -182,6 +193,8 @@ Here is an image: ![[camera-board.png]]
});
assert.strictEqual(resDelete.status, 200, "Delete must return 200");
assert.ok(!fs.existsSync(writtenPath), "Post markdown file must be deleted");
assert.ok(!fs.existsSync(compiledPostDir), "Compiled post directory must be deleted from _site");
assert.ok(!fs.existsSync(standalonePostHtml), "Compiled standalone HTML must be deleted from _site");
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -190,7 +203,7 @@ Here is an image: ![[camera-board.png]]
}
});
test("Ingestion API: adversarial protection against path traversal in slug", async () => {
test("Ingestion API: adversarial protection against path traversal in slug and DELETE validation", async () => {
const tmpDir = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-site-"));
const tmpPosts = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-posts-"));
process.env.API_SECRET = TEST_SECRET;
@@ -200,6 +213,7 @@ test("Ingestion API: adversarial protection against path traversal in slug", asy
const port = server.address().port;
try {
// 1. Path traversal in POST
const resTraversal = await fetch(`http://127.0.0.1:${port}/api/posts`, {
method: "POST",
headers: {
@@ -218,6 +232,42 @@ test("Ingestion API: adversarial protection against path traversal in slug", asy
} else {
assert.strictEqual(resTraversal.status, 400, "Invalid slug must return 400");
}
// 2. DELETE without auth token
const resDeleteNoAuth = await fetch(`http://127.0.0.1:${port}/api/posts?slug=any-post`, {
method: "DELETE"
});
assert.strictEqual(resDeleteNoAuth.status, 401, "DELETE without Bearer token must return 401");
// 3. DELETE with missing slug parameter
const resDeleteNoSlug = await fetch(`http://127.0.0.1:${port}/api/posts`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${TEST_SECRET}`
}
});
assert.strictEqual(resDeleteNoSlug.status, 400, "DELETE without slug parameter must return 400");
// 4. DELETE with invalid/empty slug
const resDeleteBadSlug = await fetch(`http://127.0.0.1:${port}/api/posts?slug=///`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${TEST_SECRET}`
}
});
assert.strictEqual(resDeleteBadSlug.status, 400, "DELETE with invalid slug must return 400");
// 5. DELETE non-existent post succeeds gracefully (idempotent)
const resDeleteNonExistent = await fetch(`http://127.0.0.1:${port}/api/posts?slug=non-existent-post`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${TEST_SECRET}`
}
});
assert.strictEqual(resDeleteNonExistent.status, 200, "DELETE non-existent post must return 200 idempotently");
const jsonNonExistent = await resDeleteNonExistent.json();
assert.strictEqual(jsonNonExistent.status, "deleted");
assert.strictEqual(jsonNonExistent.slug, "non-existent-post");
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(tmpDir, { recursive: true, force: true });