Contract-first API with DTOs, validation, and server-side OpenAPI post-processing
All checks were successful
CI / build (push) Successful in 1m27s
CI / docker (push) Successful in 2m6s
CI / deploy (push) Successful in 30s

Add dedicated request/response DTOs for all controllers, replacing raw
JsonNode parameters with validated types. Move OpenAPI path-prefix stripping
and ProcessorNode children injection into OpenApiCustomizer beans so the
spec served at /api/v1/api-docs is already clean — eliminating the need for
the ui/scripts/process-openapi.mjs post-processing script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-14 15:33:37 +01:00
parent 50bb22d6f6
commit 465f210aee
43 changed files with 1561 additions and 509 deletions

View File

@@ -0,0 +1,27 @@
package com.cameleer3.server.app.dto;
import com.cameleer3.server.core.agent.AgentInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.time.Instant;
import java.util.List;
@Schema(description = "Agent instance summary")
public record AgentInstanceResponse(
@NotNull String id,
@NotNull String name,
@NotNull String group,
@NotNull String status,
@NotNull List<String> routeIds,
@NotNull Instant registeredAt,
@NotNull Instant lastHeartbeat
) {
public static AgentInstanceResponse from(AgentInfo info) {
return new AgentInstanceResponse(
info.id(), info.name(), info.group(),
info.state().name(), info.routeIds(),
info.registeredAt(), info.lastHeartbeat()
);
}
}

View File

@@ -0,0 +1,7 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "Agent token refresh request")
public record AgentRefreshRequest(@NotNull String refreshToken) {}

View File

@@ -0,0 +1,7 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "Refreshed access token")
public record AgentRefreshResponse(@NotNull String accessToken) {}

View File

@@ -0,0 +1,17 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
@Schema(description = "Agent registration payload")
public record AgentRegistrationRequest(
@NotNull String agentId,
@NotNull String name,
@Schema(defaultValue = "default") String group,
String version,
List<String> routeIds,
Map<String, Object> capabilities
) {}

View File

@@ -0,0 +1,14 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "Agent registration result with JWT tokens and SSE endpoint")
public record AgentRegistrationResponse(
@NotNull String agentId,
@NotNull String sseEndpoint,
long heartbeatIntervalMs,
@NotNull String serverPublicKey,
@NotNull String accessToken,
@NotNull String refreshToken
) {}

View File

@@ -0,0 +1,10 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "JWT token pair")
public record AuthTokenResponse(
@NotNull String accessToken,
@NotNull String refreshToken
) {}

View File

@@ -0,0 +1,12 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "Result of broadcasting a command to multiple agents")
public record CommandBroadcastResponse(
@NotNull List<String> commandIds,
int targetCount
) {}

View File

@@ -0,0 +1,12 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "Command to send to agent(s)")
public record CommandRequest(
@NotNull @Schema(description = "Command type: config-update, deep-trace, or replay")
String type,
@Schema(description = "Command payload JSON")
Object payload
) {}

View File

@@ -0,0 +1,10 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "Result of sending a command to a single agent")
public record CommandSingleResponse(
@NotNull String commandId,
@NotNull String status
) {}

View File

@@ -0,0 +1,7 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "Error response")
public record ErrorResponse(@NotNull String message) {}

View File

@@ -0,0 +1,16 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
@Schema(description = "OIDC configuration update request")
public record OidcAdminConfigRequest(
boolean enabled,
String issuerUri,
String clientId,
String clientSecret,
String rolesClaim,
List<String> defaultRoles,
boolean autoSignup
) {}

View File

@@ -0,0 +1,31 @@
package com.cameleer3.server.app.dto;
import com.cameleer3.server.core.security.OidcConfig;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "OIDC configuration for admin management")
public record OidcAdminConfigResponse(
boolean configured,
boolean enabled,
String issuerUri,
String clientId,
boolean clientSecretSet,
String rolesClaim,
List<String> defaultRoles,
boolean autoSignup
) {
public static OidcAdminConfigResponse unconfigured() {
return new OidcAdminConfigResponse(false, false, null, null, false, null, null, false);
}
public static OidcAdminConfigResponse from(OidcConfig config) {
return new OidcAdminConfigResponse(
true, config.enabled(), config.issuerUri(), config.clientId(),
!config.clientSecret().isBlank(), config.rolesClaim(),
config.defaultRoles(), config.autoSignup()
);
}
}

View File

@@ -0,0 +1,13 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "OIDC configuration for SPA login flow")
public record OidcPublicConfigResponse(
@NotNull String issuer,
@NotNull String clientId,
@NotNull String authorizationEndpoint,
@Schema(description = "Present if the provider supports RP-initiated logout")
String endSessionEndpoint
) {}

View File

@@ -0,0 +1,10 @@
package com.cameleer3.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "OIDC provider connectivity test result")
public record OidcTestResult(
@NotNull String status,
@NotNull String authorizationEndpoint
) {}