Files
dashlibstorage/scripts/test-api.js
2026-07-31 15:36:48 +02:00

514 lines
15 KiB
JavaScript

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.delete', label: 'delete', hint: 'googleAuthId, id' },
{ value: 'category.getTreeForCompany', label: 'getTreeForCompany', hint: 'googleAuthId, companyId → rows' },
{ value: 'category.getCategoryArticles', label: 'getCategoryArticles', hint: 'googleAuthId, categoryId → 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.delete': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const id = await askInt('Category id');
if (id == null) return;
await client.category.delete({
googleAuthId: Number(googleAuthId),
id: 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 'category.getCategoryArticles': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const categoryId = await askInt('category id');
if (categoryId == null) return;
return [
'rows',
await client.category.getCategoryArticles(
Number(googleAuthId),
Number(categoryId),
),
];
}
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);
});