60 lines
2.9 KiB
JavaScript
60 lines
2.9 KiB
JavaScript
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 };
|
||
}
|