438 lines
13 KiB
JavaScript
438 lines
13 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 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: '{name} → id' },
|
|
{ value: 'company.list', label: 'list', hint: '→ rows' },
|
|
{ value: 'company.setDefaultLang', label: 'setDefaultLang', hint: 'companyId, langId' },
|
|
{ value: 'company.setRootCategory', label: 'setRootCategory', hint: 'companyId, categoryId' },
|
|
{ value: 'company.delete', label: 'delete', hint: 'companyId' },
|
|
{ value: 'company.getByCompanyUserId', label: 'getByCompanyUserId', hint: 'id → tuple' },
|
|
],
|
|
},
|
|
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' },
|
|
],
|
|
},
|
|
category: {
|
|
label: 'category',
|
|
items: [
|
|
{ value: 'category.create', label: 'create', hint: '{parent_id, sort} → id' },
|
|
{ value: 'category.updateParentId', label: 'updateParentId', hint: 'id, parentId' },
|
|
{ value: 'category.delete', label: 'delete', hint: 'id' },
|
|
{ value: 'category.getTree', label: 'getTree', hint: 'rootId → rows' },
|
|
],
|
|
},
|
|
companyuser: {
|
|
label: 'companyuser',
|
|
items: [
|
|
{ value: 'companyuser.add', label: 'add', hint: 'companyId, authId → 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 → authId' },
|
|
{ value: 'google_auth.getById', label: 'getById', hint: 'id → row' },
|
|
{ value: 'google_auth.getByEmail', label: 'getByEmail', hint: 'email → row' },
|
|
{ value: 'google_auth.list', label: 'list', hint: '→ rows' },
|
|
{ value: 'google_auth.del', label: 'del', hint: 'id' },
|
|
],
|
|
},
|
|
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 langs = await client.lang.list();
|
|
const existingLang = langs.find((row) => row.iso639 === iso639);
|
|
const langId = existingLang
|
|
? existingLang.id
|
|
: await client.lang.create(iso639);
|
|
|
|
const companyId = await client.company.create({ name });
|
|
await client.company.setDefaultLang(companyId, langId);
|
|
|
|
const auths = await client.google_auth.list();
|
|
const existingAuth = auths.find((row) => row.email === email);
|
|
const authId = existingAuth
|
|
? existingAuth.id
|
|
: await client.google_auth.add(email);
|
|
|
|
const companyUserId = await client.companyuser.add(companyId, authId);
|
|
|
|
return [
|
|
'created',
|
|
{
|
|
companyId,
|
|
companyUserId,
|
|
authId,
|
|
langId,
|
|
email,
|
|
name,
|
|
iso639,
|
|
langCreated: !existingLang,
|
|
authCreated: !existingAuth,
|
|
},
|
|
];
|
|
}
|
|
case 'company.create': {
|
|
const name = await askText('Name', {
|
|
placeholder: 'Acme',
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (name == null) return;
|
|
return ['id', await client.company.create({ name })];
|
|
}
|
|
case 'company.list': {
|
|
return ['rows', await client.company.list()];
|
|
}
|
|
case 'company.setDefaultLang': {
|
|
const companyId = await askInt('Company id');
|
|
if (companyId == null) return;
|
|
const languageId = await askInt('Language id');
|
|
if (languageId == null) return;
|
|
await client.company.setDefaultLang(Number(companyId), Number(languageId));
|
|
return ['ok', true];
|
|
}
|
|
case 'company.setRootCategory': {
|
|
const companyId = await askInt('Company id');
|
|
if (companyId == null) return;
|
|
const categoryId = await askInt('Category id');
|
|
if (categoryId == null) return;
|
|
await client.company.setRootCategory(Number(companyId), Number(categoryId));
|
|
return ['ok', true];
|
|
}
|
|
case 'company.delete': {
|
|
const companyId = await askInt('Company id');
|
|
if (companyId == null) return;
|
|
await client.company.delete(Number(companyId));
|
|
return ['ok', true];
|
|
}
|
|
case 'company.getByCompanyUserId': {
|
|
const id = await askInt('Company user id');
|
|
if (id == null) return;
|
|
return [
|
|
'[id, defaultLangId, rootCategoryId, name]',
|
|
await client.company.getByCompanyUserId(Number(id)),
|
|
];
|
|
}
|
|
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.create': {
|
|
const parentRaw = await askText('parent_id (null as empty)', {
|
|
placeholder: '',
|
|
validate: (v) =>
|
|
v?.trim() && !/^\d+$/.test(v.trim()) ? 'Integer or empty' : undefined,
|
|
});
|
|
if (parentRaw == null) return;
|
|
const sortRaw = await askInt('sort', '0');
|
|
if (sortRaw == null) return;
|
|
const parent_id = parentRaw === '' ? null : Number(parentRaw);
|
|
return ['id', await client.category.create({ parent_id, sort: Number(sortRaw) })];
|
|
}
|
|
case 'category.updateParentId': {
|
|
const id = await askInt('Category id');
|
|
if (id == null) return;
|
|
const parentRaw = await askText('parent_id (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.getTree': {
|
|
const rootId = await askInt('Root category id');
|
|
if (rootId == null) return;
|
|
return ['rows', await client.category.getTree(Number(rootId))];
|
|
}
|
|
case 'companyuser.add': {
|
|
const companyId = await askInt('Company id');
|
|
if (companyId == null) return;
|
|
const authId = await askInt('Auth id');
|
|
if (authId == null) return;
|
|
return ['id', await client.companyuser.add(Number(companyId), Number(authId))];
|
|
}
|
|
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 ['authId', await client.google_auth.add(email)];
|
|
}
|
|
case 'google_auth.getById': {
|
|
const id = await askInt('Auth id');
|
|
if (id == null) return;
|
|
return ['row', await client.google_auth.getById(Number(id))];
|
|
}
|
|
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.list': {
|
|
return ['rows', await client.google_auth.list()];
|
|
}
|
|
case 'google_auth.del': {
|
|
const id = await askInt('Auth id');
|
|
if (id == null) return;
|
|
await client.google_auth.del(Number(id));
|
|
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);
|
|
});
|