Sets each direct child's sort to its index in the given categoryIds, scoped by parentId. Validates the set matches the current children.
1194 lines
36 KiB
JavaScript
1194 lines
36 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';
|
|
|
|
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, companyId → rows',
|
|
},
|
|
{
|
|
value: 'unit.upsert',
|
|
label: 'upsert',
|
|
hint: 'googleAuthId, companyId, JSON unit → row',
|
|
},
|
|
{
|
|
value: 'unit.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, companyId, si',
|
|
},
|
|
],
|
|
},
|
|
companysetting: {
|
|
label: 'companysetting',
|
|
items: [
|
|
{
|
|
value: 'companysetting.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId, companyId, frontendOnly? → rows',
|
|
},
|
|
{
|
|
value: 'companysetting.get',
|
|
label: 'get',
|
|
hint: 'googleAuthId, companyId, key → row',
|
|
},
|
|
{
|
|
value: 'companysetting.set',
|
|
label: 'set',
|
|
hint: 'googleAuthId, companyId, JSON setting → row',
|
|
},
|
|
{
|
|
value: 'companysetting.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, companyId, key',
|
|
},
|
|
],
|
|
},
|
|
brand: {
|
|
label: 'brand',
|
|
items: [
|
|
{
|
|
value: 'brand.list',
|
|
label: 'list',
|
|
hint: 'googleAuthId, companyId → rows',
|
|
},
|
|
{
|
|
value: 'brand.upsert',
|
|
label: 'upsert',
|
|
hint: 'googleAuthId, companyId, JSON brand → row',
|
|
},
|
|
{
|
|
value: 'brand.delete',
|
|
label: 'delete',
|
|
hint: 'googleAuthId, companyId, 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.order', label: 'order', hint: 'googleAuthId, parentId, JSON categoryIds' },
|
|
{ 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' },
|
|
],
|
|
},
|
|
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.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.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 'unit.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.unit.list(Number(googleAuthId), Number(companyId)),
|
|
];
|
|
}
|
|
case 'unit.upsert': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == 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),
|
|
Number(companyId),
|
|
JSON.parse(unitJson),
|
|
),
|
|
];
|
|
}
|
|
case 'unit.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == null) return;
|
|
const si = await askText('si', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (si == null) return;
|
|
await client.unit.delete(Number(googleAuthId), Number(companyId), si);
|
|
return ['ok', true];
|
|
}
|
|
case 'companysetting.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == null) return;
|
|
const frontendOnly = await askText('frontendOnly (true/false)', {
|
|
placeholder: 'false',
|
|
});
|
|
if (frontendOnly == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.companysetting.list(Number(googleAuthId), Number(companyId), {
|
|
frontendOnly: frontendOnly.trim().toLowerCase() === 'true',
|
|
}),
|
|
];
|
|
}
|
|
case 'companysetting.get': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == 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),
|
|
Number(companyId),
|
|
key,
|
|
{ frontendOnly: frontendOnly.trim().toLowerCase() === 'true' },
|
|
),
|
|
];
|
|
}
|
|
case 'companysetting.set': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == 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),
|
|
Number(companyId),
|
|
JSON.parse(settingJson),
|
|
),
|
|
];
|
|
}
|
|
case 'companysetting.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == null) return;
|
|
const key = await askText('key', {
|
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
|
});
|
|
if (key == null) return;
|
|
await client.companysetting.delete(
|
|
Number(googleAuthId),
|
|
Number(companyId),
|
|
key,
|
|
);
|
|
return ['ok', true];
|
|
}
|
|
case 'brand.list': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == null) return;
|
|
return [
|
|
'result[]',
|
|
await client.brand.list(Number(googleAuthId), Number(companyId)),
|
|
];
|
|
}
|
|
case 'brand.upsert': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == 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),
|
|
Number(companyId),
|
|
JSON.parse(brandJson),
|
|
),
|
|
];
|
|
}
|
|
case 'brand.delete': {
|
|
const googleAuthId = await askInt('google_auth id');
|
|
if (googleAuthId == null) return;
|
|
const companyId = await askInt('company id');
|
|
if (companyId == null) return;
|
|
const id = await askInt('brand id');
|
|
if (id == null) return;
|
|
await client.brand.delete(Number(googleAuthId), Number(companyId), 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.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;
|
|
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 '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.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.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);
|
|
});
|