02420fcbc4
- 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>
93 lines
2.7 KiB
JavaScript
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);
|
|
});
|