Download server/exportBoard.ts from igorktech/pinned: direct link, hf CLI and curl.
- Browser
- Download file 3.13 kB
-
https://huggingface.co/spaces/igorktech/pinned/resolve/main/server/exportBoard.ts
- Command line
-
hf download hf://spaces/igorktech/pinned/server/exportBoard.ts
-
curl -L -o exportBoard.ts https://huggingface.co/spaces/igorktech/pinned/resolve/main/server/exportBoard.ts
3.13 kB
| // Export a board for the demo fixtures (server/demoBoards): every record a visitor needs, plus the | |
| // board row, from a saved DATA_DIR (e.g. a live run kept with LIVE_KEEP_DATA=1). | |
| // Usage: node server/exportBoard.ts <dataDir> <boardId> <out.json> | |
| import { existsSync } from 'node:fs' | |
| import { writeFileSync } from 'node:fs' | |
| import { join, resolve } from 'node:path' | |
| import { DatabaseSync } from 'node:sqlite' | |
| import { boardDb, closeBoardDb, findBoard, setBoardDbPath } from './boardDb.ts' | |
| import { safeId } from './rooms.ts' | |
| const [dataDir, boardId, out] = process.argv.slice(2) | |
| if (!dataDir || !boardId || !out) { | |
| console.error('Usage: node server/exportBoard.ts <dataDir> <boardId> <out.json>') | |
| process.exit(1) | |
| } | |
| // the room's tldraw records, straight from its SQLite file like the live check's room.py reads them | |
| const roomFile = join(resolve(dataDir), 'rooms', `${safeId(boardId)}.db`) | |
| if (!existsSync(roomFile)) { | |
| console.error(`no room file at ${roomFile}`) | |
| process.exit(1) | |
| } | |
| const room = new DatabaseSync(roomFile, { readOnly: true }) | |
| const records = room | |
| .prepare('SELECT state FROM documents') | |
| .all() | |
| .map((r) => { | |
| const state = (r as { state: string | Uint8Array }).state | |
| return JSON.parse(typeof state === 'string' ? state : new TextDecoder().decode(state)) as { typeName?: string } | |
| }) | |
| // putShapes writes shapes and bindings; pages and assets go straight into the room | |
| .filter((r) => r.typeName === 'shape' || r.typeName === 'binding' || r.typeName === 'asset' || r.typeName === 'page') | |
| room.close() | |
| setBoardDbPath(join(resolve(dataDir), 'board.db')) | |
| const board = findBoard(boardId) | |
| if (!board) { | |
| console.error(`no board row for ${boardId} in ${dataDir}/board.db`) | |
| process.exit(1) | |
| } | |
| const rows = (sql: string) => boardDb().prepare(sql).all(safeId(boardId)) as Record<string, unknown>[] | |
| const exported = { | |
| title: board.title, | |
| question: board.question, | |
| context: board.context, | |
| goals: board.goals, | |
| records, | |
| // per-board state the widgets read back: votes (card faces carry keepBy/discardBy too, but the | |
| // REST answers come from decisions) and links people removed so the agent never redraws them | |
| decisions: rows('SELECT paper_id, member_id, decision FROM decisions WHERE board_id = ? ORDER BY at, rowid').map( | |
| (r) => ({ paperId: String(r.paper_id), memberId: String(r.member_id), decision: String(r.decision) }) | |
| ), | |
| // the people who voted or wrote notes, so their names show on the demo board | |
| members: rows('SELECT member_id, display_name FROM members WHERE board_id = ? ORDER BY joined_at, rowid').map( | |
| (r) => ({ memberId: String(r.member_id), displayName: String(r.display_name) }) | |
| ), | |
| removedLinks: rows( | |
| 'SELECT from_paper, to_paper, kind, removed_by FROM removed_links WHERE board_id = ? ORDER BY at, rowid' | |
| ).map((r) => ({ from: String(r.from_paper), to: String(r.to_paper), kind: String(r.kind), removedBy: String(r.removed_by) })), | |
| } | |
| closeBoardDb() | |
| writeFileSync(out, JSON.stringify(exported, null, '\t') + '\n') | |
| console.log(`${boardId}: ${records.length} records, ${exported.decisions.length} decisions, ${exported.removedLinks.length} removed links → ${out}`) | |