76 lines
3.3 KiB
TypeScript
76 lines
3.3 KiB
TypeScript
/**
|
|
* Tools tracker (/trackers/tools, tools.items) from the command line, for bots and Stan.
|
|
* Items are keyed by name (case-insensitive) or id; importing again only updates the fields
|
|
* given, merges specs (a null spec value removes that key) and never duplicates an item.
|
|
*
|
|
* On stan-ovh (DB_URL is loaded from the app container, nothing printed):
|
|
* scripts/tools-run.sh list [--json]
|
|
* scripts/tools-run.sh import ~/imports/tools.json [--dry-run]
|
|
* scripts/tools-run.sh add '{"name":"AA batteries","category":"batteries","quantity":24}'
|
|
* scripts/tools-run.sh add '{"name":"AAA batteries","add":-4}' # used 4
|
|
* scripts/tools-run.sh delete 12 # by id
|
|
*
|
|
* Entry fields (only name required): name, brand, model, category, quantity | add (delta),
|
|
* location, purchased_on (YYYY-MM-DD), price_eur, specs (object of string|number), notes, id.
|
|
* Categories: batteries, power_tools, hand_tools, measuring, electrical, fasteners, consumables,
|
|
* safety, other (see src/lib/tools/index.ts; "power tools" etc. are understood too).
|
|
*/
|
|
import { readFile } from 'node:fs/promises';
|
|
import postgres from 'postgres';
|
|
import { importItems, listItems, parseImport } from '../src/lib/server/tools/store';
|
|
|
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
const dry = rest.includes('--dry-run');
|
|
const args = rest.filter((a) => !a.startsWith('--'));
|
|
const usage =
|
|
'usage: tools.ts list [--json] | import <file.json> [--dry-run] | add <json> [--dry-run] | delete <id>';
|
|
if (!cmd) throw new Error(usage);
|
|
|
|
let input: unknown;
|
|
if (cmd === 'import') {
|
|
if (!args[0]) throw new Error(usage);
|
|
input = JSON.parse(await readFile(args[0], 'utf8'));
|
|
} else if (cmd === 'add') {
|
|
if (!args[0]) throw new Error(usage);
|
|
input = JSON.parse(args.join(' '));
|
|
} else if (!['list', 'delete'].includes(cmd)) throw new Error(usage);
|
|
|
|
const parsed = input === undefined ? null : parseImport(input);
|
|
if (parsed) {
|
|
console.log(`parsed ${parsed.rows.length} entries`);
|
|
if (parsed.errors.length) {
|
|
for (const e of parsed.errors.slice(0, 50)) console.error(` ${e}`);
|
|
console.error(`${parsed.errors.length} invalid entries; nothing imported`);
|
|
process.exit(1);
|
|
}
|
|
if (dry) process.exit(0);
|
|
}
|
|
|
|
if (!process.env.DB_URL)
|
|
throw new Error('DB_URL is required (use scripts/tools-run.sh on stan-ovh)');
|
|
const sql = postgres(process.env.DB_URL, { max: 1, onnotice: () => {} });
|
|
try {
|
|
if (cmd === 'list') {
|
|
const items = await listItems(sql);
|
|
if (rest.includes('--json')) console.log(JSON.stringify(items, null, 2));
|
|
else
|
|
for (const it of items)
|
|
console.log(
|
|
`${String(it.id).padStart(4)} ${it.category.padEnd(12)} x${String(it.quantity).padEnd(4)} ${it.name}${
|
|
it.brand || it.model ? ` (${[it.brand, it.model].filter(Boolean).join(' ')})` : ''
|
|
}`
|
|
);
|
|
} else if (cmd === 'delete') {
|
|
const id = Number(args[0]);
|
|
if (!Number.isInteger(id) || id <= 0) throw new Error(usage);
|
|
const r = await sql`DELETE FROM tools.items WHERE id = ${id} RETURNING name`;
|
|
console.log(r.length ? `deleted ${id} ${r[0].name}` : `no item ${id}`);
|
|
} else {
|
|
const r = await importItems(sql, parsed!.rows);
|
|
console.log(`imported ${r.total}: ${r.created} new, ${r.updated} updated`);
|
|
if (r.missing.length) console.error(`not found: ${r.missing.join(', ')}`);
|
|
}
|
|
} finally {
|
|
await sql.end();
|
|
}
|