Files
Void-Homelab/migrate/sources/void1.js
root dbf84559de feat(ui): project details panel + compact/responsive cards; rename Sentinel→Yerin (red); migrate research_notes
- Project card expands to show description + status + dates (was only the research stub)
- Cards compacted + responsive (actions wrap on narrow)
- Sentinel renamed Yerin everywhere (#/yerin, red 'Sage of the Endless Sword' theme + red sidebar dot)
- void1 importer now carries research_notes/last_researched_at (was dropped)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:19:16 +10:00

76 lines
4.2 KiB
JavaScript

import { DatabaseSync } from 'node:sqlite';
import { pool } from '../../lib/db/pool.js';
import * as pages from '../../lib/db/repos/pages.js';
import * as projects from '../../lib/db/repos/projects.js';
import * as tasks from '../../lib/db/repos/tasks.js';
import * as messages from '../../lib/db/repos/messages.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) || 'item';
// conversations.create() has no space_id param; insert directly so migrated chats
// live in the void1 Space.
async function createConversation(title, spaceId) {
const { rows: [r] } = await pool.query(
`INSERT INTO conversations(title, space_id, metadata) VALUES($1,$2,'{}') RETURNING id`, [title || 'Conversation', spaceId]);
return r.id;
}
export async function importVoid1({ sqlitePath, spaceId, dryRun = false }) {
const db = new DatabaseSync(sqlitePath, { readOnly: true });
const n = { pages: 0, projects: 0, tasks: 0, conversations: 0, messages: 0 };
const projIds = new Map(); // void1 project id -> void2 project id
for (const w of db.prepare(`SELECT * FROM wiki_pages`).all()) {
if (await map.seen('void1', `wiki_pages:${w.id}`, 'page')) continue;
n.pages++; if (dryRun) continue;
const p = await pages.create({ space_id: spaceId, slug: slugify(w.slug || w.title), title: w.title || 'Untitled', body_md: w.content || '' }, SYS);
await map.record('void1', `wiki_pages:${w.id}`, 'page', p.id);
}
for (const pr of db.prepare(`SELECT * FROM projects`).all()) {
const existing = await map.seen('void1', `projects:${pr.id}`, 'project');
if (existing) { projIds.set(pr.id, existing); continue; }
n.projects++; if (dryRun) continue;
const created = await projects.create({ space_id: spaceId, slug: slugify(pr.name), name: pr.name || 'Project', description: pr.description || null, status: pr.status === 'archived' ? 'archived' : 'active' }, SYS);
// Carry Void 1's Eithan research over (research_notes were not in the original mapping).
if (pr.research_notes || pr.last_researched_at) {
await pool.query(
`UPDATE projects SET research_notes=$1, last_researched_at=$2, research_status=$3 WHERE id=$4`,
[pr.research_notes || null, pr.last_researched_at ? new Date(pr.last_researched_at) : null, pr.research_notes ? 'done' : 'none', created.id]);
}
projIds.set(pr.id, created.id);
await map.record('void1', `projects:${pr.id}`, 'project', created.id);
}
for (const t of db.prepare(`SELECT * FROM project_tasks`).all()) {
if (await map.seen('void1', `project_tasks:${t.id}`, 'task')) continue;
n.tasks++; if (dryRun) continue;
const created = await tasks.create({ space_id: spaceId, project_id: projIds.get(t.project_id) || null, title: t.text || 'Task', position: t.position ?? 0 }, SYS);
await map.record('void1', `project_tasks:${t.id}`, 'task', created.id);
}
for (const j of db.prepare(`SELECT * FROM project_journal`).all()) {
if (await map.seen('void1', `project_journal:${j.id}`, 'page')) continue;
n.pages++; if (dryRun) continue;
const p = await pages.create({ space_id: spaceId, slug: slugify(`journal-${j.id}`), title: `Journal ${j.id}`, body_md: j.content || '' }, SYS);
await map.record('void1', `project_journal:${j.id}`, 'page', p.id);
}
for (const c of db.prepare(`SELECT * FROM conversations`).all()) {
let convId = await map.seen('void1', `conversations:${c.id}`, 'conversation');
if (!convId) {
n.conversations++;
if (!dryRun) {
convId = await createConversation(c.title, spaceId);
await map.record('void1', `conversations:${c.id}`, 'conversation', convId);
}
}
for (const m of db.prepare(`SELECT * FROM messages WHERE conversation_id=? ORDER BY created_at`).all(c.id)) {
if (await map.seen('void1', `messages:${m.id}`, 'message')) continue;
n.messages++; if (dryRun) continue;
const appended = await messages.append(convId, { role: m.role === 'assistant' ? 'assistant' : 'user', body: m.content || '' });
await map.record('void1', `messages:${m.id}`, 'message', appended.id);
}
}
db.close();
return n;
}