import 'dotenv/config'; import sql from 'mssql'; import chalk from 'chalk'; import * as p from '@clack/prompts'; import LibDashStore from '../src/index.js'; const config = { server: process.env.MSSQL_SERVER, database: process.env.MSSQL_DATABASE, user: process.env.MSSQL_USER, password: process.env.MSSQL_PASSWORD, options: { encrypt: false, trustServerCertificate: true, }, }; function printResult(label, value) { p.log.step(chalk.cyan(label)); const text = value === undefined ? chalk.dim('undefined') : typeof value === 'string' ? value : JSON.stringify(value, null, 2); console.log(chalk.white(text)); console.log(); } async function askText(message, { placeholder, validate } = {}) { const value = await p.text({ message, placeholder, validate }); if (p.isCancel(value)) return null; return value.trim(); } async function askInt(message, placeholder = '1') { return askText(message, { placeholder, validate: (v) => (!/^\d+$/.test(v?.trim() ?? '') ? 'Integer required' : undefined), }); } async function askNumber(message, placeholder = '19') { const value = await askText(message, { placeholder, validate: (v) => v?.trim() === '' || Number.isNaN(Number(v)) ? 'Number required' : undefined, }); if (value == null) return null; return Number(value); } async function shutdown(pool, code = 0) { try { await pool.close(); } catch { // ignore } process.exit(code); } const MENU = { setup: { label: chalk.cyan('setup'), hint: 'guided flows', items: [ { value: 'setup.company', label: 'company', hint: 'email + name + lang' }, ], }, company: { label: 'company', items: [ { value: 'company.create', label: 'create', hint: 'googleAuthId, iso639, name, taxes → result' }, { value: 'company.update', label: 'update', hint: 'id + fields → result' }, { value: 'company.list', label: 'list', hint: 'googleAuthId → result[]' }, { value: 'company.delete', label: 'delete', hint: 'companyId' }, ], }, lang: { label: 'lang', items: [ { value: 'lang.create', label: 'create', hint: 'iso639 → id' }, { value: 'lang.list', label: 'list', hint: '→ rows' }, { value: 'lang.delete', label: 'delete', hint: 'id' }, ], }, tax: { label: 'tax', items: [ { value: 'tax.create', label: 'create', hint: 'rate → id' }, { value: 'tax.list', label: 'list', hint: '→ rows' }, { value: 'tax.ensure', label: 'ensure', hint: 'rate → id' }, ], }, category: { label: 'category', items: [ { value: 'category.createUnderParent', label: 'createUnderParent', hint: 'googleAuthId, parentId, name → row' }, { value: 'category.updateParentId', label: 'updateParentId', hint: 'id, parentId' }, { value: 'category.delete', label: 'delete', hint: 'id' }, { value: 'category.getTreeForCompany', label: 'getTreeForCompany', hint: 'googleAuthId, companyId → rows' }, ], }, companyuser: { label: 'companyuser', items: [ { value: 'companyuser.add', label: 'add', hint: 'companyId, googleAuthId → id' }, { value: 'companyuser.delete', label: 'delete', hint: 'id' }, { value: 'companyuser.getByEmail', label: 'getByEmail', hint: 'email → ids' }, ], }, google_auth: { label: 'google_auth', items: [ { value: 'google_auth.add', label: 'add', hint: 'email → id' }, { value: 'google_auth.getByEmail', label: 'getByEmail', hint: 'email → row' }, { value: 'google_auth.listAllowedEmails', label: 'listAllowedEmails', hint: '→ rows' }, { value: 'google_auth.removeAllowedEmail', label: 'removeAllowedEmail', hint: 'email' }, { value: 'google_auth.setSelectedCompany', label: 'setSelectedCompany', hint: 'googleAuthId, companyId' }, ], }, schema: { label: 'schema', items: [ { value: 'initialize', label: 'initialize', hint: 'ensure schema (no purge)' }, { value: 'purge', label: chalk.red('purge'), hint: 'drop all · exit' }, ], }, }; async function pickAction() { const group = await p.select({ message: 'Group', options: [ ...Object.entries(MENU).map(([value, group]) => ({ value, label: group.label, hint: group.hint, })), { value: 'exit', label: 'exit', hint: 'quit without purge' }, ], }); if (p.isCancel(group) || group === 'exit') return 'exit'; const action = await p.select({ message: MENU[group].label, options: [ ...MENU[group].items, { value: 'back', label: chalk.dim('← back') }, ], }); if (p.isCancel(action) || action === 'back') return null; return action; } async function runMethod(client, action) { switch (action) { case 'setup.company': { const email = await askText('Email', { placeholder: 'user@example.com', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (email == null) return; const name = await askText('Company name', { placeholder: 'Acme', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (name == null) return; const iso639 = await askText('Default language (iso639)', { placeholder: 'de', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (iso639 == null) return; const taxrate = await askNumber('Tax rate', '19'); if (taxrate == null) return; const reducedtaxrate = await askNumber('Reduced tax rate', '7'); if (reducedtaxrate == null) return; const existing = await client.google_auth.getByEmail(email); const googleAuthId = existing ? existing.id : await client.google_auth.add(email); const created = await client.company.create({ iso639, name, taxrate, reducedtaxrate, googleAuthId, }); const companyId = created.id; await client.google_auth.setSelectedCompany(googleAuthId, companyId); return [ 'created', { ...created, googleAuthId, email, name, iso639, taxrate, reducedtaxrate, }, ]; } case 'company.create': { const googleAuthId = await askInt('google_auth id'); if (googleAuthId == null) return; const name = await askText('Name', { placeholder: 'Acme', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (name == null) return; const iso639 = await askText('iso639', { placeholder: 'de', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (iso639 == null) return; const taxrate = await askNumber('taxrate', '19'); if (taxrate == null) return; const reducedtaxrate = await askNumber('reducedtaxrate', '7'); if (reducedtaxrate == null) return; return [ 'result', await client.company.create({ iso639, name, taxrate, reducedtaxrate, googleAuthId: Number(googleAuthId), }), ]; } case 'company.update': { const id = await askInt('Company id'); if (id == null) return; const name = await askText('Name', { placeholder: 'Acme', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (name == null) return; const iso639 = await askText('iso639', { placeholder: 'de', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (iso639 == null) return; const taxrate = await askNumber('taxrate', '19'); if (taxrate == null) return; const reducedtaxrate = await askNumber('reducedtaxrate', '7'); if (reducedtaxrate == null) return; return [ 'result', await client.company.update(Number(id), { iso639, name, taxrate, reducedtaxrate, }), ]; } case 'company.list': { const googleAuthId = await askInt('google_auth id'); if (googleAuthId == null) return; return ['result[]', await client.company.list(Number(googleAuthId))]; } case 'company.delete': { const companyId = await askInt('Company id'); if (companyId == null) return; await client.company.delete(Number(companyId)); return ['ok', true]; } case 'tax.create': { const rate = await askNumber('rate', '19'); if (rate == null) return; return ['id', await client.tax.create(rate)]; } case 'tax.list': { return ['rows', await client.tax.list()]; } case 'tax.ensure': { const rate = await askNumber('rate', '19'); if (rate == null) return; return ['id', await client.tax.ensure(rate)]; } case 'lang.create': { const iso639 = await askText('ISO 639', { placeholder: 'de', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (iso639 == null) return; return ['id', await client.lang.create(iso639)]; } case 'lang.list': { return ['rows', await client.lang.list()]; } case 'lang.delete': { const id = await askInt('Language id'); if (id == null) return; await client.lang.delete(Number(id)); return ['ok', true]; } case 'category.createUnderParent': { const googleAuthId = await askInt('google_auth id'); if (googleAuthId == null) return; const parentId = await askInt('parent category id'); if (parentId == null) return; const name = await askText('name', { validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (name == null) return; return [ 'result', await client.category.createUnderParent({ googleAuthId: Number(googleAuthId), parentId: Number(parentId), name, }), ]; } case 'category.updateParentId': { const id = await askInt('Category id'); if (id == null) return; const parentRaw = await askText('parentId (null as empty)', { placeholder: '', validate: (v) => v?.trim() && !/^\d+$/.test(v.trim()) ? 'Integer or empty' : undefined, }); if (parentRaw == null) return; const parentId = parentRaw === '' ? null : Number(parentRaw); await client.category.updateParentId(Number(id), parentId); return ['ok', true]; } case 'category.delete': { const id = await askInt('Category id'); if (id == null) return; await client.category.delete(Number(id)); return ['ok', true]; } case 'category.getTreeForCompany': { const googleAuthId = await askInt('google_auth id'); if (googleAuthId == null) return; const companyId = await askInt('company id'); if (companyId == null) return; return [ 'rows', await client.category.getTreeForCompany(Number(googleAuthId), Number(companyId)), ]; } case 'companyuser.add': { const companyId = await askInt('Company id'); if (companyId == null) return; const googleAuthId = await askInt('Auth id'); if (googleAuthId == null) return; return ['id', await client.companyuser.add(Number(companyId), Number(googleAuthId))]; } case 'companyuser.delete': { const id = await askInt('Company user id'); if (id == null) return; await client.companyuser.delete(Number(id)); return ['ok', true]; } case 'companyuser.getByEmail': { const email = await askText('Email', { placeholder: 'user@example.com', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (email == null) return; return ['ids', await client.companyuser.getByEmail(email)]; } case 'google_auth.add': { const email = await askText('Email', { placeholder: 'user@example.com', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (email == null) return; return ['id', await client.google_auth.add(email)]; } case 'google_auth.getByEmail': { const email = await askText('Email', { placeholder: 'user@example.com', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (email == null) return; return ['row', await client.google_auth.getByEmail(email)]; } case 'google_auth.listAllowedEmails': { return ['rows', await client.google_auth.listAllowedEmails()]; } case 'google_auth.removeAllowedEmail': { const email = await askText('Email', { placeholder: 'user@example.com', validate: (v) => (!v?.trim() ? 'Required' : undefined), }); if (email == null) return; await client.google_auth.removeAllowedEmail(email); return ['ok', true]; } case 'google_auth.setSelectedCompany': { const id = await askInt('Auth id'); if (id == null) return; const companyId = await askInt('Company id'); if (companyId == null) return; await client.google_auth.setSelectedCompany(Number(id), Number(companyId)); return ['ok', true]; } case 'initialize': { await client.initialize(); return ['ok', true]; } default: return null; } } async function main() { console.clear(); p.intro(chalk.bgMagenta.black(' libdashstore ') + chalk.dim(' api tester')); const spin = p.spinner(); spin.start('Connecting to MSSQL…'); let pool; try { pool = await sql.connect(config); } catch (err) { spin.stop(chalk.red('Connection failed')); p.log.error(err.message); p.outro(chalk.red('Aborted')); process.exit(1); } spin.message('Initializing schema…'); let client; try { client = await new LibDashStore(pool); spin.stop(chalk.green('Ready')); } catch (err) { spin.stop(chalk.red('Initialize failed')); p.log.error(err.message); await shutdown(pool, 1); } p.note( [ chalk.dim(`${config.server} / ${config.database}`), chalk.dim('initialize = ensure schema · purge ends session'), ].join('\n'), 'Connected', ); while (true) { const action = await pickAction(); if (action === 'exit') { p.outro(chalk.dim('Bye')); await shutdown(pool, 0); } if (action == null) continue; if (action === 'purge') { const confirm1 = await p.confirm({ message: chalk.red('Drop ALL tables?'), initialValue: false, }); if (p.isCancel(confirm1) || !confirm1) { p.log.warn('Purge cancelled'); continue; } const confirm2 = await p.confirm({ message: chalk.red('Really purge? This cannot be undone.'), initialValue: false, }); if (p.isCancel(confirm2) || !confirm2) { p.log.warn('Purge cancelled'); continue; } const run = p.spinner(); run.start('Purging…'); await client.purge(); run.stop(chalk.green('purge')); p.outro(chalk.red('Database purged — exiting')); await shutdown(pool, 0); } try { const result = await runMethod(client, action); if (result) { p.log.success(action); printResult(result[0], result[1]); } } catch (err) { p.log.error(err.message); console.log(chalk.dim(err.stack?.split('\n').slice(1, 4).join('\n') ?? '')); console.log(); } } } main().catch(async (err) => { console.error(chalk.red(err.stack ?? err.message)); process.exit(1); });