Files
Void-Homelab/migrate/sources/bookstack.js

60 lines
2.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as pages from '../../lib/db/repos/pages.js';
import * as map from '../../lib/db/repos/migration_map.js';
const SYS = { kind: 'system', id: null };
const slugify = (s) => (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 60) || 'page';
// BookStack REST. Auth header: "Token <id>:<secret>". Builds the hierarchy as
// parent pages: books → chapters (parent=book) → pages (parent=chapter or book),
// so Void breadcrumbs read Wiki Book Chapter Page.
export async function importBookstack({ apiUrl = process.env.BOOKSTACK_URL, tokenId = process.env.BOOKSTACK_TOKEN_ID, tokenSecret = process.env.BOOKSTACK_TOKEN_SECRET, spaceId, dryRun = false, fetchImpl = fetch }) {
const headers = { Authorization: `Token ${tokenId}:${tokenSecret}` };
const getJson = async (path) => {
const r = await fetchImpl(`${apiUrl}${path}`, { headers });
if (!r.ok) throw new Error(`bookstack ${path} ${r.status}`);
return r.json();
};
let created = 0;
const bookPage = new Map(); // book id → void page id
const chapterPage = new Map(); // chapter id → void page id
// 1. Books → top-level parent pages.
for (const b of ((await getJson('/api/books')).data || [])) {
let pid = await map.seen('bookstack', `book:${b.id}`, 'page');
if (!pid) {
created++;
if (!dryRun) {
const p = await pages.create({ space_id: spaceId, slug: slugify(b.name), title: b.name || 'Book', body_md: b.description || '' }, SYS);
pid = p.id; await map.record('bookstack', `book:${b.id}`, 'page', pid);
}
}
if (pid) bookPage.set(b.id, pid);
}
// 2. Chapters → pages parented to their book.
for (const c of ((await getJson('/api/chapters')).data || [])) {
let pid = await map.seen('bookstack', `chapter:${c.id}`, 'page');
if (!pid) {
created++;
if (!dryRun) {
const p = await pages.create({ space_id: spaceId, slug: slugify(c.name), title: c.name || 'Chapter', body_md: c.description || '', parent_id: bookPage.get(c.book_id) || null }, SYS);
pid = p.id; await map.record('bookstack', `chapter:${c.id}`, 'page', pid);
}
}
if (pid) chapterPage.set(c.id, pid);
}
// 3. Pages → leaf pages parented to their chapter (or book if chapter-less).
for (const item of ((await getJson('/api/pages')).data || [])) {
if (await map.seen('bookstack', `page:${item.id}`, 'page')) continue;
created++; if (dryRun) continue;
const d = await getJson(`/api/pages/${item.id}`);
const body = d.markdown && d.markdown.trim() ? d.markdown : (d.html || '');
const parent = chapterPage.get(d.chapter_id) || bookPage.get(d.book_id) || null;
const p = await pages.create({ space_id: spaceId, slug: slugify(d.name), title: d.name || 'Untitled', body_md: body, parent_id: parent }, SYS);
await map.record('bookstack', `page:${item.id}`, 'page', p.id);
}
return { created };
}