- Add provider contract (src/providers/provider.js): query() -> {rows,
rowsAffected, insertId}, callback transaction(fn), exec() for stored
procs, initialize/purge lifecycle
- Add MssqlProvider (src/providers/mssql/index.js) wrapping an mssql
ConnectionPool with normalized results, tx commit/rollback and
constraint-error classifiers (unique 2627/2601, FK 547)
- LibDashStore constructor now takes a provider instead of a pool
(breaking); adds LibDashStore.create() async factory; keeps pool as
transitional bridge until store modules are converted (see TODO.md)
- Move MSSQL-specific code into the provider: src/sql/* ->
src/providers/mssql/queries/*, src/schema.js ->
src/providers/mssql/schema.js, purge SQL -> purgeSql.js
- Update scripts, generate-schema-graph.js and package.json exports
(./providers/mssql, ./providers/provider)
- Add TODO.md with the remaining provider/SQLite migration plan
No SQL or behavioral changes.
2079 lines
62 KiB
JavaScript
2079 lines
62 KiB
JavaScript
import { readFile } from 'node:fs/promises';
|
|
import 'dotenv/config';
|
|
import sql from 'mssql';
|
|
import chalk from 'chalk';
|
|
import * as p from '@clack/prompts';
|
|
import LibDashStore from '../src/index.js';
|
|
import { MssqlProvider } from '../src/providers/mssql/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' },
|
|
],
|
|
},
|
|
unit: {
|
|
label: 'unit',
|
|
items: [
|
|
{
|
|
value: 'unit.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId → rows',
|
|
},
|
|
{
|
|
value: 'unit.upsert',
|
|
label: 'upsert',
|
|
hint: 'googleAuthId, JSON unit → row',
|
|
},
|
|
{
|
|
value: 'unit.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, si',
|
|
},
|
|
],
|
|
},
|
|
companysetting: {
|
|
label: 'companysetting',
|
|
items: [
|
|
{
|
|
value: 'companysetting.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId, frontendOnly? → rows',
|
|
},
|
|
{
|
|
value: 'companysetting.get',
|
|
label: 'get',
|
|
hint: 'googleAuthId, key → row',
|
|
},
|
|
{
|
|
value: 'companysetting.set',
|
|
label: 'set',
|
|
hint: 'googleAuthId, JSON setting → row',
|
|
},
|
|
{
|
|
value: 'companysetting.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, key',
|
|
},
|
|
],
|
|
},
|
|
brand: {
|
|
label: 'brand',
|
|
items: [
|
|
{
|
|
value: 'brand.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId → rows',
|
|
},
|
|
{
|
|
value: 'brand.upsert',
|
|
label: 'upsert',
|
|
hint: 'googleAuthId, JSON brand → row',
|
|
},
|
|
{
|
|
value: 'brand.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, id',
|
|
},
|
|
],
|
|
},
|
|
customergroup: {
|
|
label: 'customergroup',
|
|
items: [
|
|
{ value: 'customergroup.list', label: 'list', hint: 'googleAuthId → rows' },
|
|
{ value: 'customergroup.create', label: 'create', hint: 'googleAuthId, name → row' },
|
|
{ value: 'customergroup.update', label: 'update', hint: 'googleAuthId, id, name → row' },
|
|
{ value: 'customergroup.order', label: 'order', hint: 'googleAuthId, JSON customergroupIds → { customergroupIds }' },
|
|
{ value: 'customergroup.delete', label: 'delete', hint: 'googleAuthId, id' },
|
|
],
|
|
},
|
|
entity: {
|
|
label: 'entity',
|
|
items: [
|
|
{ value: 'entity.list', label: 'list', hint: 'googleAuthId, customergroupId?|role=customer → rows' },
|
|
{ value: 'entity.getById', label: 'getById', hint: 'googleAuthId, id → row' },
|
|
{ value: 'entity.create', label: 'create', hint: 'googleAuthId, JSON → row' },
|
|
{ value: 'entity.update', label: 'update', hint: 'googleAuthId, id, JSON → row' },
|
|
{ value: 'entity.delete', label: 'delete', hint: 'googleAuthId, id' },
|
|
{ value: 'entity.peekNextNumber', label: 'peekNextNumber', hint: 'googleAuthId → next number' },
|
|
{ value: 'entity.isNumberFree', label: 'isNumberFree', hint: 'googleAuthId, number, excludeEntityId? → bool' },
|
|
],
|
|
},
|
|
entityaddress: {
|
|
label: 'entityaddress',
|
|
items: [
|
|
{ value: 'entityaddress.list', label: 'list', hint: 'googleAuthId, entityId → rows' },
|
|
{ value: 'entityaddress.create', label: 'create', hint: 'googleAuthId, entityId, JSON → row' },
|
|
{ value: 'entityaddress.update', label: 'update', hint: 'googleAuthId, entityId, id, JSON → row' },
|
|
{ value: 'entityaddress.delete', label: 'delete', hint: 'googleAuthId, entityId, id' },
|
|
],
|
|
},
|
|
entitycontact: {
|
|
label: 'entitycontact',
|
|
items: [
|
|
{ value: 'entitycontact.list', label: 'list', hint: 'googleAuthId, entityId → rows' },
|
|
{ value: 'entitycontact.create', label: 'create', hint: 'googleAuthId, entityId, JSON → row' },
|
|
{ value: 'entitycontact.update', label: 'update', hint: 'googleAuthId, entityId, id, JSON → row' },
|
|
{ value: 'entitycontact.delete', label: 'delete', hint: 'googleAuthId, entityId, id' },
|
|
],
|
|
},
|
|
supplier: {
|
|
label: 'supplier',
|
|
items: [
|
|
{
|
|
value: 'supplier.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId → rows',
|
|
},
|
|
{
|
|
value: 'supplier.create',
|
|
label: 'create',
|
|
hint: 'googleAuthId, name → row',
|
|
},
|
|
{
|
|
value: 'supplier.update',
|
|
label: 'update',
|
|
hint: 'googleAuthId, id, name → row',
|
|
},
|
|
{
|
|
value: 'supplier.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, id',
|
|
},
|
|
],
|
|
},
|
|
delivery: {
|
|
label: 'delivery',
|
|
items: [
|
|
{
|
|
value: 'delivery.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId → summaries',
|
|
},
|
|
{
|
|
value: 'delivery.getById',
|
|
label: 'getById',
|
|
hint: 'googleAuthId, id → delivery',
|
|
},
|
|
{
|
|
value: 'delivery.create',
|
|
label: 'create',
|
|
hint: 'googleAuthId, JSON body → delivery',
|
|
},
|
|
{
|
|
value: 'delivery.update',
|
|
label: 'update',
|
|
hint: 'googleAuthId, id, JSON patch → delivery',
|
|
},
|
|
{
|
|
value: 'delivery.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, id',
|
|
},
|
|
],
|
|
},
|
|
quote: {
|
|
label: 'quote',
|
|
items: [
|
|
{
|
|
value: 'quote.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId → summaries',
|
|
},
|
|
{
|
|
value: 'quote.getById',
|
|
label: 'getById',
|
|
hint: 'googleAuthId, id → quote',
|
|
},
|
|
{
|
|
value: 'quote.create',
|
|
label: 'create',
|
|
hint: 'googleAuthId, JSON body → quote',
|
|
},
|
|
{
|
|
value: 'quote.update',
|
|
label: 'update',
|
|
hint: 'googleAuthId, id, JSON patch → quote',
|
|
},
|
|
{
|
|
value: 'quote.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, id',
|
|
},
|
|
],
|
|
},
|
|
order: {
|
|
label: 'order',
|
|
items: [
|
|
{
|
|
value: 'order.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId → summaries',
|
|
},
|
|
{
|
|
value: 'order.getById',
|
|
label: 'getById',
|
|
hint: 'googleAuthId, id → order',
|
|
},
|
|
{
|
|
value: 'order.create',
|
|
label: 'create',
|
|
hint: 'googleAuthId, JSON body → order',
|
|
},
|
|
{
|
|
value: 'order.update',
|
|
label: 'update',
|
|
hint: 'googleAuthId, id, JSON patch → order',
|
|
},
|
|
{
|
|
value: 'order.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, id',
|
|
},
|
|
],
|
|
},
|
|
category: {
|
|
label: 'category',
|
|
items: [
|
|
{ value: 'category.createUnderParent', label: 'createUnderParent', hint: 'googleAuthId, parentId, name → row' },
|
|
{ value: 'category.rename', label: 'rename', hint: 'googleAuthId, id, name → row' },
|
|
{ value: 'category.move', label: 'move', hint: 'googleAuthId, id, parentId → row' },
|
|
{ value: 'category.order', label: 'order', hint: 'googleAuthId, parentId, JSON categoryIds' },
|
|
{ value: 'category.delete', label: 'delete', hint: 'googleAuthId, id' },
|
|
{ value: 'category.getTreeForCompany', label: 'getTreeForCompany', hint: 'googleAuthId → rows' },
|
|
{ value: 'category.getCategoryArticles', label: 'getCategoryArticles', hint: 'googleAuthId, categoryId → rows' },
|
|
],
|
|
},
|
|
article: {
|
|
label: 'article',
|
|
items: [
|
|
{
|
|
value: 'article.createUnderParent',
|
|
label: 'createUnderParent',
|
|
hint: 'googleAuthId, parentId, name → row',
|
|
},
|
|
{
|
|
value: 'article.getBySku',
|
|
label: 'getBySku',
|
|
hint: 'googleAuthId, sku → row',
|
|
},
|
|
{
|
|
value: 'article.getHistoryBySku',
|
|
label: 'getHistoryBySku',
|
|
hint: 'googleAuthId, sku → history[]',
|
|
},
|
|
{
|
|
value: 'article.updateBySku',
|
|
label: 'updateBySku',
|
|
hint: 'googleAuthId, sku, name/unitamount/unitSi → row',
|
|
},
|
|
{
|
|
value: 'article.setPricesBySku',
|
|
label: 'setPricesBySku',
|
|
hint: 'googleAuthId, sku, JSON prices → row',
|
|
},
|
|
{
|
|
value: 'article.setEansBySku',
|
|
label: 'setEansBySku',
|
|
hint: 'googleAuthId, sku, JSON eans → row',
|
|
},
|
|
{
|
|
value: 'article.setBrandIdBySku',
|
|
label: 'setBrandIdBySku',
|
|
hint: 'googleAuthId, sku, brandId|null → row',
|
|
},
|
|
{
|
|
value: 'article.setWeightBySku',
|
|
label: 'setWeightBySku',
|
|
hint: 'googleAuthId, sku, weight → row',
|
|
},
|
|
{
|
|
value: 'article.setCategoryIdsBySku',
|
|
label: 'setCategoryIdsBySku',
|
|
hint: 'googleAuthId, sku, JSON categoryIds → row',
|
|
},
|
|
{
|
|
value: 'article.addImage',
|
|
label: 'addImage',
|
|
hint: 'googleAuthId, sku, avif file path → sizes',
|
|
},
|
|
{
|
|
value: 'article.deleteImage',
|
|
label: 'deleteImage',
|
|
hint: 'googleAuthId, sku, fullHash',
|
|
},
|
|
{
|
|
value: 'article.orderImages',
|
|
label: 'orderImages',
|
|
hint: 'googleAuthId, sku, comma fullHashes',
|
|
},
|
|
{
|
|
value: 'article.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, sku',
|
|
},
|
|
],
|
|
},
|
|
pic: {
|
|
label: 'pic',
|
|
items: [
|
|
{
|
|
value: 'pic.getImageByHash',
|
|
label: 'getImageByHash',
|
|
hint: 'googleAuthId, fullHash → meta',
|
|
},
|
|
{
|
|
value: 'pic.getThumbByHash',
|
|
label: 'getThumbByHash',
|
|
hint: 'googleAuthId, fullHash → meta',
|
|
},
|
|
],
|
|
},
|
|
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.getById', label: 'getById', hint: 'googleAuthId → 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' },
|
|
{ value: 'google_auth.setProfile', label: 'setProfile', hint: 'googleAuthId, picture, sub' },
|
|
],
|
|
},
|
|
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 'unit.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.unit.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'unit.upsert': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const unitJson = await askText('unit JSON', {
|
|
placeholder:
|
|
'{"si":"ml","basesi":"l","basesifactor":0.001,"symbol":"ml","name":"Milliliter"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (unitJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.unit.upsert(
|
|
Number(googleAuthId),
|
|
JSON.parse(unitJson),
|
|
),
|
|
];
|
|
}
|
|
case 'unit.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const si = await askText('si', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (si == null) return;
|
|
await client.unit.delete(Number(googleAuthId), si);
|
|
return ['ok', true];
|
|
}
|
|
case 'companysetting.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const frontendOnly = await askText('frontendOnly (true/false)', {
|
|
placeholder: 'false',
|
|
});
|
|
if (frontendOnly == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.companysetting.list(Number(googleAuthId), {
|
|
frontendOnly: frontendOnly.trim().toLowerCase() === 'true',
|
|
}),
|
|
];
|
|
}
|
|
case 'companysetting.get': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const key = await askText('key', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (key == null) return;
|
|
const frontendOnly = await askText('frontendOnly (true/false)', {
|
|
placeholder: 'false',
|
|
});
|
|
if (frontendOnly == null) return;
|
|
return [
|
|
'result',
|
|
await client.companysetting.get(
|
|
Number(googleAuthId),
|
|
key,
|
|
{ frontendOnly: frontendOnly.trim().toLowerCase() === 'true' },
|
|
),
|
|
];
|
|
}
|
|
case 'companysetting.set': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const settingJson = await askText('setting JSON', {
|
|
placeholder: '{"key":"theme","value":"dark","frontend":true}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (settingJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.companysetting.set(
|
|
Number(googleAuthId),
|
|
JSON.parse(settingJson),
|
|
),
|
|
];
|
|
}
|
|
case 'companysetting.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const key = await askText('key', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (key == null) return;
|
|
await client.companysetting.delete(
|
|
Number(googleAuthId),
|
|
key,
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'brand.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.brand.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'brand.upsert': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const brandJson = await askText('brand JSON', {
|
|
placeholder: '{"name":"Nike","gsprpinfo":"..."}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (brandJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.brand.upsert(
|
|
Number(googleAuthId),
|
|
JSON.parse(brandJson),
|
|
),
|
|
];
|
|
}
|
|
case 'brand.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('brand id');
|
|
if (id == null) return;
|
|
await client.brand.delete(Number(googleAuthId), id);
|
|
return ['ok', true];
|
|
}
|
|
case 'customergroup.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.customergroup.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'customergroup.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const name = await askText('name', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (name == null) return;
|
|
return [
|
|
'result',
|
|
await client.customergroup.create(
|
|
Number(googleAuthId),
|
|
{ name },
|
|
),
|
|
];
|
|
}
|
|
case 'customergroup.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('customergroup id');
|
|
if (id == null) return;
|
|
const name = await askText('name', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (name == null) return;
|
|
return [
|
|
'result',
|
|
await client.customergroup.update(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
{ name },
|
|
),
|
|
];
|
|
}
|
|
case 'customergroup.order': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const idsJson = await askText('customergroupIds JSON', {
|
|
placeholder: '[3,1,2]',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (idsJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.customergroup.order(
|
|
Number(googleAuthId),
|
|
{ customergroupIds: JSON.parse(idsJson) },
|
|
),
|
|
];
|
|
}
|
|
case 'customergroup.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('customergroup id');
|
|
if (id == null) return;
|
|
await client.customergroup.delete(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'entity.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const roleRaw = await askText('role (customer|empty)', {
|
|
placeholder: '',
|
|
validate: (v) => {
|
|
const t = v?.trim?.() ?? '';
|
|
if (!t || t === 'customer') return undefined;
|
|
return 'customer or empty';
|
|
},
|
|
});
|
|
if (roleRaw == null) return;
|
|
const role = roleRaw.trim() || undefined;
|
|
let customergroupId;
|
|
if (!role) {
|
|
const groupRaw = await askText('customergroup id (empty = all)', {
|
|
placeholder: '',
|
|
validate: (v) =>
|
|
v?.trim() && !/^\d+$/.test(v.trim())
|
|
? 'Integer or empty'
|
|
: undefined,
|
|
});
|
|
if (groupRaw == null) return;
|
|
customergroupId = groupRaw.trim()
|
|
? Number(groupRaw.trim())
|
|
: undefined;
|
|
}
|
|
return [
|
|
'result[]',
|
|
await client.entity.list(Number(googleAuthId), {
|
|
customergroupId,
|
|
role,
|
|
}),
|
|
];
|
|
}
|
|
case 'entity.getById': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('entity id');
|
|
if (id == null) return;
|
|
return [
|
|
'result',
|
|
await client.entity.getById(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
),
|
|
];
|
|
}
|
|
case 'entity.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const bodyJson = await askText('entity JSON', {
|
|
placeholder: '{"customergroupId":1,"name":"Acme","firstName":null,"number":null}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
JSON.parse(v);
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (bodyJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.entity.create(
|
|
Number(googleAuthId),
|
|
JSON.parse(bodyJson),
|
|
),
|
|
];
|
|
}
|
|
case 'entity.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('entity id');
|
|
if (id == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"name":"Acme","firstName":"Ada","number":"C-1"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
JSON.parse(v);
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.entity.update(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'entity.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('entity id');
|
|
if (id == null) return;
|
|
await client.entity.delete(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'entity.peekNextNumber': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result',
|
|
await client.entity.peekNextNumber(
|
|
Number(googleAuthId),
|
|
),
|
|
];
|
|
}
|
|
case 'entity.isNumberFree': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const number = await askText('number', {
|
|
validate: (v) => (v?.trim() ? undefined : 'Required'),
|
|
});
|
|
if (number == null) return;
|
|
const excludeRaw = await askText('exclude entity id (optional)', {
|
|
placeholder: 'leave empty',
|
|
validate: (v) => {
|
|
const t = v?.trim() ?? '';
|
|
if (!t) return undefined;
|
|
return /^\d+$/.test(t) ? undefined : 'Integer or empty';
|
|
},
|
|
});
|
|
if (excludeRaw == null) return;
|
|
const excludeEntityId = excludeRaw.trim()
|
|
? Number(excludeRaw.trim())
|
|
: null;
|
|
return [
|
|
'result',
|
|
await client.entity.isNumberFree(
|
|
Number(googleAuthId),
|
|
number.trim(),
|
|
excludeEntityId == null ? {} : { excludeEntityId },
|
|
),
|
|
];
|
|
}
|
|
case 'entityaddress.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.entityaddress.list(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
),
|
|
];
|
|
}
|
|
case 'entityaddress.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
const bodyJson = await askText('address JSON', {
|
|
placeholder:
|
|
'{"addressType":"billing","street":"Main","streetNumber":"1","zip":"1010","city":"Wien"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
JSON.parse(v);
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (bodyJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.entityaddress.create(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
JSON.parse(bodyJson),
|
|
),
|
|
];
|
|
}
|
|
case 'entityaddress.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
const id = await askInt('address id');
|
|
if (id == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"city":"Graz"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
JSON.parse(v);
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.entityaddress.update(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
Number(id),
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'entityaddress.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
const id = await askInt('address id');
|
|
if (id == null) return;
|
|
await client.entityaddress.delete(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'entitycontact.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.entitycontact.list(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
),
|
|
];
|
|
}
|
|
case 'entitycontact.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
const bodyJson = await askText('contact JSON', {
|
|
placeholder: '{"contactType":"email","contact":"a@b.c"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
JSON.parse(v);
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (bodyJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.entitycontact.create(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
JSON.parse(bodyJson),
|
|
),
|
|
];
|
|
}
|
|
case 'entitycontact.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
const id = await askInt('contact id');
|
|
if (id == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"contact":"b@c.d"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
JSON.parse(v);
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.entitycontact.update(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
Number(id),
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'entitycontact.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const entityId = await askInt('entity id');
|
|
if (entityId == null) return;
|
|
const id = await askInt('contact id');
|
|
if (id == null) return;
|
|
await client.entitycontact.delete(
|
|
Number(googleAuthId),
|
|
Number(entityId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'supplier.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.supplier.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'supplier.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const name = await askText('name', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (name == null) return;
|
|
return [
|
|
'result',
|
|
await client.supplier.create(Number(googleAuthId), {
|
|
name,
|
|
}),
|
|
];
|
|
}
|
|
case 'supplier.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('supplier id');
|
|
if (id == null) return;
|
|
const name = await askText('name', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (name == null) return;
|
|
return [
|
|
'result',
|
|
await client.supplier.update(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
{ name },
|
|
),
|
|
];
|
|
}
|
|
case 'supplier.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('supplier id');
|
|
if (id == null) return;
|
|
await client.supplier.delete(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'delivery.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.delivery.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'delivery.getById': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('delivery id');
|
|
if (id == null) return;
|
|
return [
|
|
'result',
|
|
await client.delivery.getById(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
),
|
|
];
|
|
}
|
|
case 'delivery.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const bodyJson = await askText('delivery JSON', {
|
|
placeholder:
|
|
'{"number":"DHL-123","date":"2026-08-10","supplierId":1,"lines":[{"sku":"1","amount":10,"net":99.5,"tax":19}]}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (bodyJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.delivery.create(
|
|
Number(googleAuthId),
|
|
JSON.parse(bodyJson),
|
|
),
|
|
];
|
|
}
|
|
case 'delivery.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('delivery id');
|
|
if (id == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"number":"DHL-124","lines":[]}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.delivery.update(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'delivery.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('delivery id');
|
|
if (id == null) return;
|
|
await client.delivery.delete(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'quote.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.quote.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'quote.getById': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('quote id');
|
|
if (id == null) return;
|
|
return [
|
|
'result',
|
|
await client.quote.getById(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
),
|
|
];
|
|
}
|
|
case 'quote.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const bodyJson = await askText('quote JSON', {
|
|
placeholder:
|
|
'{"number":"A-2026-001","date":"2026-09-01","entityId":12,"lines":[{"sku":"1","amount":10,"net":99.5,"tax":19}]}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (bodyJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.quote.create(
|
|
Number(googleAuthId),
|
|
JSON.parse(bodyJson),
|
|
),
|
|
];
|
|
}
|
|
case 'quote.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('quote id');
|
|
if (id == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"number":"A-2026-002","lines":[]}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.quote.update(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'quote.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('quote id');
|
|
if (id == null) return;
|
|
await client.quote.delete(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'order.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.order.list(Number(googleAuthId)),
|
|
];
|
|
}
|
|
case 'order.getById': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('order id');
|
|
if (id == null) return;
|
|
return [
|
|
'result',
|
|
await client.order.getById(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
),
|
|
];
|
|
}
|
|
case 'order.create': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const bodyJson = await askText('order JSON', {
|
|
placeholder:
|
|
'{"number":"B-2026-001","date":"2026-09-15","entityId":12,"lines":[{"sku":"1","amount":10,"net":99.5,"tax":19}]}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (bodyJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.order.create(
|
|
Number(googleAuthId),
|
|
JSON.parse(bodyJson),
|
|
),
|
|
];
|
|
}
|
|
case 'order.update': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('order id');
|
|
if (id == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"number":"B-2026-002","lines":[]}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.order.update(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'order.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('order id');
|
|
if (id == null) return;
|
|
await client.order.delete(
|
|
Number(googleAuthId),
|
|
Number(id),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
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.rename': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('Category id');
|
|
if (id == null) return;
|
|
const name = await askText('name', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (name == null) return;
|
|
return [
|
|
'row',
|
|
await client.category.rename({
|
|
googleAuthId: Number(googleAuthId),
|
|
id: Number(id),
|
|
name,
|
|
}),
|
|
];
|
|
}
|
|
case 'category.move': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const id = await askInt('Category id');
|
|
if (id == null) return;
|
|
const parentId = await askInt('new parent id');
|
|
if (parentId == null) return;
|
|
return [
|
|
'row',
|
|
await client.category.move({
|
|
googleAuthId: Number(googleAuthId),
|
|
id: Number(id),
|
|
parentId: Number(parentId),
|
|
}),
|
|
];
|
|
}
|
|
case 'category.order': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const parentId = await askInt('parent id');
|
|
if (parentId == null) return;
|
|
const categoryIdsJson = await askText('categoryIds JSON', {
|
|
placeholder: '[2,4,3,5]',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (categoryIdsJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.category.order({
|
|
googleAuthId: Number(googleAuthId),
|
|
parentId: Number(parentId),
|
|
categoryIds: JSON.parse(categoryIdsJson),
|
|
}),
|
|
];
|
|
}
|
|
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;
|
|
return [
|
|
'rows',
|
|
await client.category.getTreeForCompany(Number(googleAuthId)),
|
|
];
|
|
}
|
|
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 'article.createUnderParent': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const parentId = await askInt('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.article.createUnderParent({
|
|
googleAuthId: Number(googleAuthId),
|
|
parentId: Number(parentId),
|
|
name,
|
|
}),
|
|
];
|
|
}
|
|
case 'article.getBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.getBySku(Number(googleAuthId), sku),
|
|
];
|
|
}
|
|
case 'article.getHistoryBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.getHistoryBySku(Number(googleAuthId), sku),
|
|
];
|
|
}
|
|
case 'article.updateBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const patchJson = await askText('patch JSON', {
|
|
placeholder: '{"name":"Tea","unitamount":0.24,"unitSi":"l"}',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return 'Must be a JSON object';
|
|
}
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (patchJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.updateBySku(
|
|
Number(googleAuthId),
|
|
sku,
|
|
JSON.parse(patchJson),
|
|
),
|
|
];
|
|
}
|
|
case 'article.setPricesBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const pricesJson = await askText('prices JSON', {
|
|
placeholder: '[{"price":9.99,"tax":19}]',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (pricesJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.setPricesBySku(
|
|
Number(googleAuthId),
|
|
sku,
|
|
JSON.parse(pricesJson),
|
|
),
|
|
];
|
|
}
|
|
case 'article.setEansBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const eansJson = await askText('eans JSON', {
|
|
placeholder: '["4006381333931","4012345678901"]',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (eansJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.setEansBySku(
|
|
Number(googleAuthId),
|
|
sku,
|
|
JSON.parse(eansJson),
|
|
),
|
|
];
|
|
}
|
|
case 'article.setBrandIdBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const raw = await askText('brandId (or "null")', {
|
|
validate: (v) => {
|
|
if (v === null || v === undefined || v.trim() === '') return 'Required';
|
|
const t = v.trim();
|
|
if (t === 'null') return undefined;
|
|
if (!Number.isFinite(Number(t))) return 'Must be a number or null';
|
|
},
|
|
});
|
|
if (raw == null) return;
|
|
const brandId = raw.trim() === 'null' ? null : Number(raw.trim());
|
|
return [
|
|
'result',
|
|
await client.article.setBrandIdBySku(
|
|
Number(googleAuthId),
|
|
sku,
|
|
brandId,
|
|
),
|
|
];
|
|
}
|
|
case 'article.setWeightBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const weight = await askInt('weight');
|
|
if (weight == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.setWeightBySku(
|
|
Number(googleAuthId),
|
|
sku,
|
|
weight,
|
|
),
|
|
];
|
|
}
|
|
case 'article.setCategoryIdsBySku': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const categoryIdsJson = await askText('categoryIds JSON', {
|
|
placeholder: '[10,23,42]',
|
|
validate: (v) => {
|
|
if (!v?.trim()) return 'Required';
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
|
} catch {
|
|
return 'Invalid JSON';
|
|
}
|
|
},
|
|
});
|
|
if (categoryIdsJson == null) return;
|
|
return [
|
|
'result',
|
|
await client.article.setCategoryIdsBySku(
|
|
Number(googleAuthId),
|
|
sku,
|
|
JSON.parse(categoryIdsJson),
|
|
),
|
|
];
|
|
}
|
|
case 'article.addImage': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const filePath = await askText('AVIF file path', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (filePath == null) return;
|
|
const image = await readFile(filePath.trim());
|
|
return [
|
|
'result',
|
|
await client.article.addImage(Number(googleAuthId), sku, image),
|
|
];
|
|
}
|
|
case 'article.deleteImage': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const fullHash = await askText('fullHash', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (fullHash == null) return;
|
|
await client.article.deleteImage(
|
|
Number(googleAuthId),
|
|
sku,
|
|
fullHash.trim(),
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'article.orderImages': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
const hashes = await askText('fullHashes (comma-separated)', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (hashes == null) return;
|
|
const fullHashes = hashes
|
|
.split(',')
|
|
.map((h) => h.trim())
|
|
.filter(Boolean);
|
|
return [
|
|
'result',
|
|
await client.article.orderImages(Number(googleAuthId), sku, {
|
|
fullHashes,
|
|
}),
|
|
];
|
|
}
|
|
case 'article.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const sku = await askText('sku', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (sku == null) return;
|
|
await client.article.delete(Number(googleAuthId), sku);
|
|
return ['ok', true];
|
|
}
|
|
case 'pic.getImageByHash': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const fullHash = await askText('fullHash', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (fullHash == null) return;
|
|
const image = await client.pic.getImageByHash(
|
|
Number(googleAuthId),
|
|
fullHash.trim(),
|
|
);
|
|
return [
|
|
'result',
|
|
{
|
|
w: image.w,
|
|
h: image.h,
|
|
size: image.size,
|
|
contentType: image.contentType,
|
|
bufferLength: image.buffer.length,
|
|
},
|
|
];
|
|
}
|
|
case 'pic.getThumbByHash': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const fullHash = await askText('fullHash', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (fullHash == null) return;
|
|
const image = await client.pic.getThumbByHash(
|
|
Number(googleAuthId),
|
|
fullHash.trim(),
|
|
);
|
|
return [
|
|
'result',
|
|
{
|
|
w: image.w,
|
|
h: image.h,
|
|
contentType: image.contentType,
|
|
bufferLength: image.buffer.length,
|
|
},
|
|
];
|
|
}
|
|
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.getById': {
|
|
const id = await askInt('Auth id');
|
|
if (id == null) return;
|
|
return ['row', await client.google_auth.getById(Number(id))];
|
|
}
|
|
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 'google_auth.setProfile': {
|
|
const id = await askInt('Auth id');
|
|
if (id == null) return;
|
|
const picture = await askText('picture URL', { placeholder: '(empty = null)' });
|
|
if (picture == null) return;
|
|
const sub = await askText('Google sub', { placeholder: '(empty = null)' });
|
|
if (sub == null) return;
|
|
await client.google_auth.setProfile(Number(id), {
|
|
picture: picture.trim() || null,
|
|
sub: sub.trim() || null,
|
|
});
|
|
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(new MssqlProvider(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);
|
|
});
|