From bb0aef92cf9737bc2ec8b3b732b5d00388b865d8 Mon Sep 17 00:00:00 2001 From: RaymondVerhoef Date: Thu, 14 May 2026 22:08:23 +0200 Subject: [PATCH] feat: add script to automate initialization of PocketBase collections --- scripts/setup-pb-collections.mjs | 181 +++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 scripts/setup-pb-collections.mjs diff --git a/scripts/setup-pb-collections.mjs b/scripts/setup-pb-collections.mjs new file mode 100644 index 0000000..8b07c36 --- /dev/null +++ b/scripts/setup-pb-collections.mjs @@ -0,0 +1,181 @@ +/** + * Creates all PocketBase collections for the learning platform. + * Usage: node scripts/setup-pb-collections.mjs [PB_URL] [ADMIN_EMAIL] [ADMIN_PASSWORD] + * Defaults: http://localhost:8090, prompted if not provided + */ + +const PB_URL = process.argv[2] || 'http://localhost:8090'; +const ADMIN_EMAIL = process.argv[3]; +const ADMIN_PASSWORD = process.argv[4]; + +if (!ADMIN_EMAIL || !ADMIN_PASSWORD) { + console.error('Usage: node scripts/setup-pb-collections.mjs '); + process.exit(1); +} + +const OPEN_RULES = { listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' }; + +const COLLECTIONS = [ + { + name: 'topics', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'label', type: 'text', required: true }, + { name: 'type', type: 'text', required: false }, + { name: 'description', type: 'text', required: false }, + ], + }, + { + name: 'relations', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'source', type: 'text', required: true }, + { name: 'target', type: 'text', required: true }, + { name: 'type', type: 'text', required: false }, + ], + }, + { + name: 'content', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'topic_id', type: 'text', required: true }, + { name: 'data', type: 'json', required: false }, + ], + }, + { + name: 'quiz_banks', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'topic_id', type: 'text', required: true }, + { name: 'questions', type: 'json', required: false }, + ], + }, + { + name: 'sources', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'name', type: 'text', required: true }, + { name: 'status', type: 'text', required: false }, + { name: 'error', type: 'text', required: false }, + ], + }, + { + name: 'team_members', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'name', type: 'text', required: true }, + { name: 'pin', type: 'text', required: false }, + { name: 'role', type: 'text', required: false }, + ], + }, + { + name: 'quiz_results', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'user_id', type: 'text', required: true }, + { name: 'week_number', type: 'number', required: true }, + { name: 'score', type: 'number', required: false }, + { name: 'total', type: 'number', required: false }, + { name: 'percentage', type: 'number', required: false }, + { name: 'time_used', type: 'number', required: false }, + { name: 'completed_at', type: 'text', required: false }, + { name: 'breakdown', type: 'json', required: false }, + { name: 'points_earned',type: 'number', required: false }, + ], + }, + { + name: 'quiz_cache', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'user_id', type: 'text', required: true }, + { name: 'week_number', type: 'number', required: true }, + { name: 'questions', type: 'json', required: false }, + { name: 'meta', type: 'json', required: false }, + ], + }, + { + name: 'learn_progress', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'user_id', type: 'text', required: true }, + { name: 'week_number', type: 'number', required: true }, + { name: 'done', type: 'bool', required: false }, + ], + }, + { + name: 'leaderboard', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'user_id', type: 'text', required: true }, + { name: 'name', type: 'text', required: false }, + { name: 'points', type: 'number', required: false }, + { name: 'tests_completed', type: 'number', required: false }, + { name: 'learnings_completed', type: 'number', required: false }, + ], + }, + { + name: 'settings', + type: 'base', + ...OPEN_RULES, + schema: [ + { name: 'key', type: 'text', required: true }, + { name: 'value', type: 'text', required: false }, + ], + }, +]; + +async function post(path, body, token) { + const res = await fetch(`${PB_URL}${path}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: token } : {}), + }, + body: JSON.stringify(body), + }); + const json = await res.json(); + if (!res.ok) throw Object.assign(new Error(json.message || 'Request failed'), { status: res.status, data: json }); + return json; +} + +async function main() { + console.log(`Connecting to PocketBase at ${PB_URL}...`); + + // Authenticate as superuser (PocketBase v0.23+) + const auth = await post('/api/collections/_superusers/auth-with-password', { + identity: ADMIN_EMAIL, + password: ADMIN_PASSWORD, + }); + const token = auth.token; + console.log('Authenticated as admin.\n'); + + for (const col of COLLECTIONS) { + try { + await post('/api/collections', col, token); + console.log(` ✓ Created: ${col.name}`); + } catch (err) { + if (err.status === 400 && err.data?.data?.name?.code === 'validation_not_unique') { + console.log(` ~ Skipped: ${col.name} (already exists)`); + } else { + console.error(` ✗ Failed: ${col.name} — ${err.message}`); + } + } + } + + console.log('\nDone. All collections are set up with open API rules.'); +} + +main().catch(err => { + console.error('\nFatal error:', err.message); + process.exit(1); +});