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:
Carsten Rehfeld
2026-05-17 18:04:06 +02:00
parent b85c24d9bf
commit 02420fcbc4
4 changed files with 154 additions and 29 deletions
@@ -88,6 +88,23 @@ public class AdminResource {
"expiresAt", sb.expiresAt.toString())).build();
}
@DELETE
@Path("/sandboxes/{uuid}")
@Operation(summary = "Delete a sandbox and all its services (admin)", description = "Requires X-Admin-Key. DEMO-tier sandboxes are protected.")
public Response deleteSandbox(
@HeaderParam("X-Admin-Key") String adminKey,
@PathParam("uuid") String uuidStr) {
requireAdmin(adminKey);
UUID id;
try { id = UUID.fromString(uuidStr); }
catch (IllegalArgumentException e) {
throw new WebApplicationException(
Response.status(404).entity(Map.of("message", "Sandbox not found")).build());
}
sandboxService.deleteSandbox(id);
return Response.noContent().build();
}
private void requireAdmin(String key) {
if (adminApiKey.isBlank() || !adminApiKey.equals(key)) {
throw new WebApplicationException(
@@ -237,7 +237,7 @@ public class SandboxService {
List<String> properties) {
ServiceStage targetStage = stage != null
? ServiceStage.valueOf(stage.toUpperCase())
: ServiceStage.DEVELOPMENT;
: ServiceStage.PRODUCTION;
List<String[]> props = RegistryService.parsePropertyFilters(properties);
StringBuilder sql = new StringBuilder(
@@ -659,5 +659,19 @@ public class SandboxService {
}
}
/** Deletes a sandbox and all its registered services. DEMO tier sandboxes are protected. */
@Transactional
public void deleteSandbox(UUID id) {
SandboxEntity sb = requireById(id);
if ("DEMO".equals(sb.tier)) {
throw new WebApplicationException(
Response.status(403).entity(Map.of("message", "DEMO-tier sandboxes are permanent and cannot be deleted")).build());
}
em.createQuery("DELETE FROM ServiceEntity s WHERE s.sandboxId = :sid")
.setParameter("sid", id.toString())
.executeUpdate();
em.remove(em.contains(sb) ? sb : em.merge(sb));
}
public record SandboxCreationResult(SandboxEntity sandbox, String plainKey, String plainMaintenanceKey) {}
}