Files
dashfront/src/api/api-tester.mjs
2026-08-03 07:13:42 +02:00

178 lines
5.7 KiB
JavaScript

#!/usr/bin/env node
/**
* API Tester CLI — reuses src/api/index.js directly.
*
* Usage:
* node src/api/api-tester.mjs # interactive REPL
* node src/api/api-tester.mjs <method> [args...] # one-shot call
*
* Auth:
* 1. Copy your session cookie from the browser DevTools (name: "token", value: the JWT)
* 2. Paste it into ~/.dashfront-token (or pass --token-file <path>)
* 3. Or pass --token <jwt-string> on the command line
*
* Examples:
* node src/api/api-tester.mjs posterminal.list
* node src/api/api-tester.mjs category.getTree
* node src/api/api-tester.mjs posterminal.create '{"name":"Kassa 3"}'
* node src/api/api-tester.mjs auth.me
*/
import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { resolve } from 'node:path';
import { createInterface } from 'node:readline';
// ── CLI args ─────────────────────────────────────────────────────────────────
const args = process.argv.slice(2);
function flag(name) {
const idx = args.indexOf(name);
if (idx === -1) return null;
const val = args[idx + 1];
args.splice(idx, 2);
return val;
}
const BASE_URL = flag('--base') || 'http://localhost:4208';
const tokenFileArg = flag('--token-file');
const tokenArg = flag('--token');
function loadToken() {
if (tokenArg) return tokenArg;
const paths = [
tokenFileArg,
resolve(homedir(), '.dashfront-token'),
'./.dashfront-token',
].filter(Boolean);
for (const p of paths) {
try {
const t = readFileSync(p, 'utf-8').trim();
if (t) return t;
} catch { /* ignore */ }
}
console.error('No token found. Options:');
console.error(' 1. Place JWT in ~/.dashfront-token');
console.error(' 2. Pass --token <jwt>');
console.error(' 3. Pass --token-file <path>');
process.exit(1);
}
const TOKEN = loadToken();
// ── Patch globals so src/api/index.js works in Node ──────────────────────────
// Fake browser globals so src/api/index.js works in Node
globalThis.window = {
location: { origin: BASE_URL },
};
globalThis.document = {
get cookie() { return ''; },
set cookie(_v) { /* no-op */ },
};
// Patch fetch to send the session cookie
const originalFetch = globalThis.fetch;
globalThis.fetch = async (input, init) => {
const url = typeof input === 'string' ? input : input?.url ?? String(input);
const fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url}`;
const headers = new Headers(init?.headers);
headers.set('Cookie', `token=${TOKEN}`);
const res = await originalFetch(fullUrl, { ...init, headers });
return res;
};
// ── Load the API module ──────────────────────────────────────────────────────
const api = (await import('./index.js')).default;
// ── Helpers ──────────────────────────────────────────────────────────────────
const flatten = (obj, prefix = '') => {
const entries = [];
for (const [key, val] of Object.entries(obj)) {
const path = prefix ? `${prefix}.${key}` : key;
if (typeof val === 'function') {
entries.push([path, val]);
} else if (typeof val === 'object' && val !== null) {
entries.push(...flatten(val, path));
}
}
return entries;
};
const methods = new Map(flatten(api));
function parseArg(raw) {
const trimmed = raw.trim();
if (trimmed === 'undefined') return undefined;
if (trimmed === 'null') return null;
if (trimmed === 'true') return true;
if (trimmed === 'false') return false;
try { return JSON.parse(trimmed); } catch { return trimmed; }
}
function invoke(methodPath, argStrings = []) {
const fn = methods.get(methodPath);
if (!fn) {
console.error(`Unknown method: ${methodPath}`);
console.error(`Available: ${[...methods.keys()].join(', ')}`);
return;
}
const args = argStrings.map(parseArg);
return fn(...args)
.then((result) => {
console.log(JSON.stringify(result, null, 2));
return result;
})
.catch((err) => {
console.error(`[ERROR] ${err.message}`);
if (err.status) console.error(` status: ${err.status}`);
if (err.data) console.error(` data:`, err.data);
});
}
// ── One-shot mode ────────────────────────────────────────────────────────────
if (args.length > 0) {
const [method, ...rest] = args;
await invoke(method, rest);
process.exit(0);
}
// ── Interactive REPL ─────────────────────────────────────────────────────────
console.log(`API Tester — ${BASE_URL}`);
console.log(`Token: ${TOKEN.slice(0, 20)}…`);
console.log(`Methods: ${[...methods.keys()].join(', ')}`);
console.log('Type a method call, or "quit" to exit.\n');
const rl = createInterface({ input: process.stdin, output: process.stdout });
function prompt() {
rl.question('api> ', async (line) => {
const trimmed = line.trim();
if (!trimmed || trimmed === 'quit' || trimmed === 'exit') {
rl.close();
process.exit(0);
}
// Parse: method arg1 arg2 ...
// Arguments are space-separated; wrap objects in quotes: method '{"key":"val"}'
const parts = trimmed.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
const [method, ...rest] = parts.map((p) => p.replace(/^"|"$/g, ''));
await invoke(method, rest);
prompt();
});
}
prompt();