feat(apix-demo): working Node.js discovery demo + sandbox cleanup
- apix-demo.mjs: all 6 steps run end-to-end — HATEOAS root, sandbox
creation, BSM registration (3 carriers), capability search, parallel
quote calls, best-price selection; all field names aligned to BsmPayload
- SandboxService: add deleteSandbox() — removes services then sandbox,
DEMO tier protected
- AdminResource: add DELETE /admin/sandboxes/{uuid} backed by deleteSandbox
- cleanup-sandboxes.mjs: admin script to purge test sandboxes while
preserving DEMO-tier and named exceptions (apix-demo-ecosystem)
Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Sandbox cleanup — deletes all non-protected sandboxes from the APIX registry.
|
||||
*
|
||||
* Protected sandboxes (never deleted):
|
||||
* - tier = DEMO (server-enforced, returns 403)
|
||||
* - name matching KEEP_NAMES below
|
||||
*
|
||||
* Usage:
|
||||
* APIX_ADMIN_KEY=<key> node scripts/cleanup-sandboxes.mjs [--dry-run]
|
||||
*
|
||||
* The admin key is in /opt/apix/.env on the VPS (APIX_API_KEY=...).
|
||||
*/
|
||||
|
||||
const REGISTRY_ROOT = 'https://api-index.org';
|
||||
const KEEP_NAMES = new Set(['apix-demo-ecosystem']);
|
||||
const DRY_RUN = process.argv.includes('--dry-run');
|
||||
const ADMIN_KEY = process.env.APIX_ADMIN_KEY;
|
||||
|
||||
if (!ADMIN_KEY) {
|
||||
console.error('Error: set APIX_ADMIN_KEY environment variable');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function adminGet(path) {
|
||||
const res = await fetch(`${REGISTRY_ROOT}${path}`, {
|
||||
headers: { Accept: 'application/json', 'X-Admin-Key': ADMIN_KEY },
|
||||
});
|
||||
if (!res.ok) throw new Error(`GET ${path} → ${res.status}: ${await res.text()}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function adminDelete(path) {
|
||||
const res = await fetch(`${REGISTRY_ROOT}${path}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-Admin-Key': ADMIN_KEY },
|
||||
});
|
||||
if (res.status === 204) return;
|
||||
throw new Error(`DELETE ${path} → ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`\n APIX Sandbox Cleanup${DRY_RUN ? ' [DRY RUN — nothing will be deleted]' : ''}\n`);
|
||||
|
||||
let page = 0;
|
||||
const toDelete = [];
|
||||
const keep = [];
|
||||
|
||||
while (true) {
|
||||
const data = await adminGet(`/admin/sandboxes?page=${page}&size=50`);
|
||||
const sandboxes = data.sandboxes ?? data.items ?? data;
|
||||
if (!Array.isArray(sandboxes) || sandboxes.length === 0) break;
|
||||
|
||||
for (const sb of sandboxes) {
|
||||
const name = sb.name ?? sb.sandboxName ?? '—';
|
||||
const tier = sb.tier ?? '?';
|
||||
const id = sb.sandboxId ?? sb.id;
|
||||
if (KEEP_NAMES.has(name) || tier === 'DEMO') {
|
||||
keep.push({ id, name, tier });
|
||||
} else {
|
||||
toDelete.push({ id, name, tier });
|
||||
}
|
||||
}
|
||||
|
||||
if (sandboxes.length < 50) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
console.log(` Keeping (${keep.length}):`);
|
||||
for (const sb of keep) console.log(` ✓ ${sb.name.padEnd(36)} ${sb.tier} ${sb.id}`);
|
||||
|
||||
console.log(`\n Deleting (${toDelete.length}):`);
|
||||
for (const sb of toDelete) {
|
||||
const label = `${sb.name.padEnd(36)} ${sb.tier} ${sb.id}`;
|
||||
if (DRY_RUN) {
|
||||
console.log(` ○ ${label} [skipped — dry run]`);
|
||||
} else {
|
||||
try {
|
||||
await adminDelete(`/admin/sandboxes/${sb.id}`);
|
||||
console.log(` ✗ ${label} deleted`);
|
||||
} catch (err) {
|
||||
console.log(` ! ${label} ERROR: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n Done.\n');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('\n Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user