Files
apix-mvp/scripts/cleanup-sandboxes.mjs
Carsten Rehfeld 02420fcbc4 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>
2026-05-17 18:04:06 +02:00

93 lines
2.7 KiB
JavaScript

/**
* 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);
});