feat: Enhance ServerTemplatesUpdateEndpoint with additional request fields and validation rules
Build Plugin / build (push) Has been cancelled
Build Plugin / release (push) Has been cancelled

feat: Expand PlayersApi with new endpoints for better player management

feat: Update ServerApi to include new instance-related endpoints

feat: Add new endpoints to ServiceAccountsApi for improved service account management

fix: Update TokensRotateEndpoint and TokensStoreEndpoint to include new validation rules

feat: Introduce ControlPlaneHttpClient for handling HTTP requests to the control plane

feat: Implement PathBuilder for dynamic URI resolution

feat: Add PlayerEntitlement and PlayerSessionProfile models for player data handling

feat: Create server-related models including HeartbeatPayload, PresenceState, and ReadinessState

feat: Develop RegistrationPayload and ServerInstance models for server management

feat: Add ServerProfile model to encapsulate server instance details

refactor: Update network class to include builder method for ControlPlaneClient

chore: Update manifest.json for plugin metadata and dependencies
This commit is contained in:
2026-05-30 23:25:22 -07:00
parent 7ab54bb8d3
commit 7a4a0bd4bd
37 changed files with 1047 additions and 34 deletions
+2 -1
View File
@@ -61,4 +61,5 @@ desktop.ini
# Template specific
libs/HytaleServer.jar
buildSrc/
.github/
.github/
src/main/java/net/kewwbec/network/generated/
+9 -5
View File
@@ -47,14 +47,18 @@ javadoc {
// Adds the Hytale server as a build dependency, allowing you to reference and
// compile against their code without bundling it. When a local install is
// present, we still use its jar for launching the server in IDE run configs.
// present we compile and run against that jar directly so there is no dependency
// on the Hytale Maven repositories. In CI (no local install) the Maven artifact
// is used together with an explicit Gson dep (bundled in the server at runtime).
dependencies {
compileOnly("com.hypixel.hytale:Server:$hytale_build")
if (hasHytaleHome) {
runtimeOnly(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar"))
def serverJar = files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar")
compileOnly(serverJar)
runtimeOnly(serverJar)
} else {
compileOnly("com.hypixel.hytale:Server:$hytale_build")
compileOnly("com.google.gson:gson:2.11.0")
}
// Your dependencies here
}
repositories {
+1 -1
View File
@@ -3,7 +3,7 @@ version=0.0.2
# The group ID used for maven publishing. Usually the same as your package name
# but not the same as your plugin group!
maven_group=org.example
maven_group=net.kewwbec
# The version of Java used by your plugin. The game is built on Java 21 but
# actually runs on Java 25.
@@ -0,0 +1,69 @@
package net.kewwbec.network;
import net.kewwbec.network.auth.ApiToken;
import net.kewwbec.network.client.PlayersClient;
import net.kewwbec.network.client.PresenceClient;
import net.kewwbec.network.client.ServerClient;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import java.time.Duration;
public final class ControlPlaneClient {
private final ControlPlaneHttpClient http;
private final PresenceClient presence;
private final ServerClient server;
private final PlayersClient players;
private ControlPlaneClient(Builder builder) {
this.http = new ControlPlaneHttpClient(builder.baseUrl, builder.token, builder.connectTimeout);
this.presence = new PresenceClient(http);
this.server = new ServerClient(http);
this.players = new PlayersClient(http);
}
public static Builder builder() {
return new Builder();
}
public PresenceClient presence() { return presence; }
public ServerClient server() { return server; }
public PlayersClient players() { return players; }
public static final class Builder {
private String baseUrl;
private ApiToken token;
private Duration connectTimeout = Duration.ofSeconds(5);
private Builder() {}
public Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Builder token(String token) {
this.token = ApiToken.of(token);
return this;
}
public Builder token(ApiToken token) {
this.token = token;
return this;
}
public Builder connectTimeout(Duration timeout) {
this.connectTimeout = timeout;
return this;
}
public ControlPlaneClient build() {
if (baseUrl == null || baseUrl.isBlank()) {
throw new IllegalStateException("baseUrl must be set");
}
if (token == null) {
throw new IllegalStateException("token must be set");
}
return new ControlPlaneClient(this);
}
}
}
@@ -0,0 +1,63 @@
package net.kewwbec.network;
/**
* Plugin entry point for core-network.
*
* Call {@link #configure(String, String)} once during server startup (e.g. from
* your plugin's onLoad/onEnable equivalent) before any other plugin calls
* {@link #client()}. The base URL and token come from your server's environment
* or plugin config.
*
* Dependent plugins declare this plugin in their manifest Dependencies block:
* "Dependencies": { "core-network": "*" }
*/
public final class NetworkPlugin {
private static volatile ControlPlaneClient instance;
private NetworkPlugin() {}
/**
* Initialise the shared {@link ControlPlaneClient}. Must be called once
* before any plugin calls {@link #client()}.
*
* @param baseUrl control-plane base URL, e.g. {@code https://cp.internal}
* @param token Sanctum bearer token issued to this server instance
*/
public static void configure(String baseUrl, String token) {
instance = ControlPlaneClient.builder()
.baseUrl(baseUrl)
.token(token)
.build();
}
/**
* Initialise with a pre-built client (useful for testing or custom config).
*/
public static void configure(ControlPlaneClient client) {
if (client == null) throw new IllegalArgumentException("client must not be null");
instance = client;
}
/**
* Returns the shared {@link ControlPlaneClient}.
*
* @throws IllegalStateException if {@link #configure} has not been called yet
*/
public static ControlPlaneClient client() {
ControlPlaneClient c = instance;
if (c == null) {
throw new IllegalStateException(
"NetworkPlugin has not been configured. " +
"Call NetworkPlugin.configure(baseUrl, token) during plugin startup."
);
}
return c;
}
/**
* Returns {@code true} if the client has been configured.
*/
public static boolean isConfigured() {
return instance != null;
}
}
@@ -0,0 +1,25 @@
package net.kewwbec.network.auth;
public final class ApiToken {
private final String value;
private ApiToken(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("API token must not be blank");
}
this.value = value;
}
public static ApiToken of(String value) {
return new ApiToken(value);
}
public String value() {
return value;
}
@Override
public String toString() {
return "ApiToken[***]";
}
}
@@ -0,0 +1,29 @@
package net.kewwbec.network.client;
import net.kewwbec.network.errors.ControlPlaneException;
import net.kewwbec.network.generated.api.v1.players.EntitlementsIndexEndpoint;
import net.kewwbec.network.generated.api.v1.players.SessionProfileEndpoint;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import net.kewwbec.network.http.PathBuilder;
import net.kewwbec.network.models.players.PlayerEntitlement;
import net.kewwbec.network.models.players.PlayerSessionProfile;
import java.util.List;
public final class PlayersClient {
private final ControlPlaneHttpClient http;
public PlayersClient(ControlPlaneHttpClient http) {
this.http = http;
}
public PlayerSessionProfile getSessionProfile(String playerId) throws ControlPlaneException {
String uri = PathBuilder.resolve(SessionProfileEndpoint.VALUE.uri(), "player", playerId);
return http.get(uri, PlayerSessionProfile.class);
}
public List<PlayerEntitlement> getEntitlements(String playerId) throws ControlPlaneException {
String uri = PathBuilder.resolve(EntitlementsIndexEndpoint.VALUE.uri(), "player", playerId);
return http.getList(uri, PlayerEntitlement.class);
}
}
@@ -0,0 +1,40 @@
package net.kewwbec.network.client;
import net.kewwbec.network.errors.ControlPlaneException;
import net.kewwbec.network.generated.api.v1.server.InstancesHeartbeatEndpoint;
import net.kewwbec.network.generated.api.v1.server.InstancesRegisterEndpoint;
import net.kewwbec.network.generated.api.v1.server.InstancesShutdownEndpoint;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import net.kewwbec.network.http.PathBuilder;
import net.kewwbec.network.models.server.HeartbeatPayload;
import net.kewwbec.network.models.server.RegistrationPayload;
public final class PresenceClient {
private final ControlPlaneHttpClient http;
public PresenceClient(ControlPlaneHttpClient http) {
this.http = http;
}
public void register(String instanceId, RegistrationPayload payload) throws ControlPlaneException {
String uri = PathBuilder.resolve(InstancesRegisterEndpoint.VALUE.uri(), "instance", instanceId);
http.post(uri, payload);
}
public void heartbeat(String instanceId, HeartbeatPayload payload) throws ControlPlaneException {
String uri = PathBuilder.resolve(InstancesHeartbeatEndpoint.VALUE.uri(), "instance", instanceId);
http.post(uri, payload);
}
public void shutdown(String instanceId) throws ControlPlaneException {
shutdown(instanceId, null);
}
public void shutdown(String instanceId, String reason) throws ControlPlaneException {
String uri = PathBuilder.resolve(InstancesShutdownEndpoint.VALUE.uri(), "instance", instanceId);
ShutdownBody body = reason != null ? new ShutdownBody(reason) : null;
http.post(uri, body);
}
private record ShutdownBody(String reason) {}
}
@@ -0,0 +1,18 @@
package net.kewwbec.network.client;
import net.kewwbec.network.errors.ControlPlaneException;
import net.kewwbec.network.generated.api.v1.server.ProfileEndpoint;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import net.kewwbec.network.models.server.ServerProfile;
public final class ServerClient {
private final ControlPlaneHttpClient http;
public ServerClient(ControlPlaneHttpClient http) {
this.http = http;
}
public ServerProfile currentProfile() throws ControlPlaneException {
return http.get(ProfileEndpoint.VALUE.uri(), ServerProfile.class);
}
}
@@ -0,0 +1,14 @@
package net.kewwbec.network.errors;
public final class AuthException extends ControlPlaneException {
private final int statusCode;
public AuthException(int statusCode, String message) {
super("Auth failure (" + statusCode + "): " + message);
this.statusCode = statusCode;
}
public int statusCode() {
return statusCode;
}
}
@@ -0,0 +1,11 @@
package net.kewwbec.network.errors;
public class ControlPlaneException extends Exception {
public ControlPlaneException(String message) {
super(message);
}
public ControlPlaneException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,7 @@
package net.kewwbec.network.errors;
public final class NotFoundException extends ControlPlaneException {
public NotFoundException(String message) {
super("Not found: " + message);
}
}
@@ -0,0 +1,14 @@
package net.kewwbec.network.errors;
public final class RemoteException extends ControlPlaneException {
private final int statusCode;
public RemoteException(int statusCode, String message) {
super("Remote error (" + statusCode + "): " + message);
this.statusCode = statusCode;
}
public int statusCode() {
return statusCode;
}
}
@@ -0,0 +1,17 @@
package net.kewwbec.network.errors;
import java.util.Collections;
import java.util.Map;
public final class ValidationException extends ControlPlaneException {
private final Map<String, String[]> errors;
public ValidationException(String message, Map<String, String[]> errors) {
super("Validation failed: " + message);
this.errors = Collections.unmodifiableMap(errors);
}
public Map<String, String[]> errors() {
return errors;
}
}
@@ -1,26 +1,34 @@
package net.kewwbec.network.generated.api.v1;
import net.kewwbec.network.generated.api.v1.announcements.AnnouncementsApi;
import net.kewwbec.network.generated.api.v1.assets.AssetsApi;
import net.kewwbec.network.generated.api.v1.audit.AuditApi;
import net.kewwbec.network.generated.api.v1.entitlements.EntitlementsApi;
import net.kewwbec.network.generated.api.v1.gamepermissions.GamePermissionsApi;
import net.kewwbec.network.generated.api.v1.gameplay.GameplayApi;
import net.kewwbec.network.generated.api.v1.health.HealthApi;
import net.kewwbec.network.generated.api.v1.hub.HubApi;
import net.kewwbec.network.generated.api.v1.infrastructure.InfrastructureApi;
import net.kewwbec.network.generated.api.v1.integrations.IntegrationsApi;
import net.kewwbec.network.generated.api.v1.moderation.ModerationApi;
import net.kewwbec.network.generated.api.v1.players.PlayersApi;
import net.kewwbec.network.generated.api.v1.plugins.PluginsApi;
import net.kewwbec.network.generated.api.v1.progression.ProgressionApi;
import net.kewwbec.network.generated.api.v1.rewards.RewardsApi;
import net.kewwbec.network.generated.api.v1.server.ServerApi;
import net.kewwbec.network.generated.api.v1.serviceaccounts.ServiceAccountsApi;
import net.kewwbec.network.generated.api.v1.social.SocialApi;
import net.kewwbec.network.generated.api.v1.support.SupportApi;
import net.kewwbec.network.generated.api.v1.worlds.WorldsApi;
public final class ControlPlaneApiV1 {
public static final ControlPlaneApiV1 INSTANCE = new ControlPlaneApiV1();
private static final String API_VERSION = "v1";
private static final String BASE_PATH = "/api/v1";
private static final String FINGERPRINT = "4dfbb2630cab5693fcc6f310408442201b6a4881f60a582d04fc2d6d05c45420";
private static final int DOMAIN_COUNT = 12;
private static final int ENDPOINT_COUNT = 69;
private static final String FINGERPRINT = "4627923de5907196aabfc8de361f6e168f139a0476a206a5367fb5550b9dedbc";
private static final int DOMAIN_COUNT = 20;
private static final int ENDPOINT_COUNT = 136;
private ControlPlaneApiV1() {
}
@@ -49,14 +57,34 @@ public final class ControlPlaneApiV1 {
return AnnouncementsApi.INSTANCE;
}
public AssetsApi assets() {
return AssetsApi.INSTANCE;
}
public AuditApi audit() {
return AuditApi.INSTANCE;
}
public EntitlementsApi entitlements() {
return EntitlementsApi.INSTANCE;
}
public GamePermissionsApi gamePermissions() {
return GamePermissionsApi.INSTANCE;
}
public GameplayApi gameplay() {
return GameplayApi.INSTANCE;
}
public HealthApi health() {
return HealthApi.INSTANCE;
}
public HubApi hub() {
return HubApi.INSTANCE;
}
public InfrastructureApi infrastructure() {
return InfrastructureApi.INSTANCE;
}
@@ -77,6 +105,14 @@ public final class ControlPlaneApiV1 {
return PluginsApi.INSTANCE;
}
public ProgressionApi progression() {
return ProgressionApi.INSTANCE;
}
public RewardsApi rewards() {
return RewardsApi.INSTANCE;
}
public ServerApi server() {
return ServerApi.INSTANCE;
}
@@ -92,4 +128,8 @@ public final class ControlPlaneApiV1 {
public SupportApi support() {
return SupportApi.INSTANCE;
}
public WorldsApi worlds() {
return WorldsApi.INSTANCE;
}
}
@@ -7,6 +7,7 @@ public final class InfrastructureApi {
public static final InfrastructureApi INSTANCE = new InfrastructureApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
ServerInstancesDestinationValidationEndpoint.VALUE,
ServerInstancesPresenceIndexEndpoint.VALUE,
ServerTemplatesIndexEndpoint.VALUE,
ServerTemplatesShowEndpoint.VALUE,
@@ -39,6 +40,10 @@ public final class InfrastructureApi {
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint serverInstancesDestinationValidation() {
return ServerInstancesDestinationValidationEndpoint.VALUE;
}
public ApiEndpoint serverInstancesPresenceIndex() {
return ServerInstancesPresenceIndexEndpoint.VALUE;
}
@@ -18,7 +18,7 @@ public final class ServerInstancesPresenceIndexEndpoint {
List.of("presence.read"),
List.of("api", "auth:sanctum", "service.abilities:presence.read"),
List.of(),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\ListServerPresenceRequest", "query", List.of(new RequestField("region", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("template", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"))), new RequestField("category", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"provisioning\",\"starting\",\"running\",\"draining\",\"stopped\",\"unhealthy\""))), new RequestField("presence_state", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"online\",\"missing\",\"any\""))), new RequestField("has_capacity", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("limit", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:100"))))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\ListServerPresenceRequest", "query", List.of(new RequestField("region", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("template", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"))), new RequestField("category", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"provisioning\",\"starting\",\"running\",\"draining\",\"stopped\",\"unhealthy\""))), new RequestField("presence_state", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"online\",\"missing\",\"any\""))), new RequestField("has_capacity", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("accepting_players", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("limit", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:100"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("Illuminate\\Http\\Resources\\Json\\AnonymousResourceCollection", false, false))
)
;
@@ -18,7 +18,7 @@ public final class ServerTemplatesStoreEndpoint {
List.of("server-templates.write"),
List.of("api", "auth:sanctum", "service.abilities:server-templates.write"),
List.of(),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\StoreServerTemplateRequest", "body", List.of(new RequestField("region_key", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:regions,key"))), new RequestField("key", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"), new RuleDescriptor("string", null, "regex:/^[a-z0-9._-]+$/"), new RuleDescriptor("string", null, "unique:server_templates,key"))), new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:120"))), new RequestField("category", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("lifecycle_strategy", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"manual\",\"static\",\"elastic\""))), new RequestField("version", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:80"))), new RequestField("max_players", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("is_active", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\StoreServerTemplateRequest", "body", List.of(new RequestField("region_key", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:regions,key"))), new RequestField("key", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"), new RuleDescriptor("string", null, "regex:/^[a-z0-9._-]+$/"), new RuleDescriptor("string", null, "unique:server_templates,key"))), new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:120"))), new RequestField("category", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("lifecycle_strategy", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"manual\",\"static\",\"elastic\""))), new RequestField("version", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:80"))), new RequestField("max_players", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("is_active", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("startup_world_key", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"), new RuleDescriptor("string", null, "exists:worlds,key"))), new RequestField("asset_pack_keys", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "array"))), new RequestField("asset_pack_keys.*", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "distinct"), new RuleDescriptor("string", null, "exists:asset_packs,key"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
@@ -18,7 +18,7 @@ public final class ServerTemplatesUpdateEndpoint {
List.of("server-templates.write"),
List.of("api", "auth:sanctum", "service.abilities:server-templates.write"),
List.of(new RouteParameter("serverTemplate", new TypeDescriptor("App\\Domains\\Infrastructure\\Models\\ServerTemplate", false, false))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\UpdateServerTemplateRequest", "body", List.of(new RequestField("region_key", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:regions,key"))), new RequestField("key", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"), new RuleDescriptor("string", null, "regex:/^[a-z0-9._-]+$/"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\Unique", "unique:server_templates,key,NULL,id"))), new RequestField("name", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:120"))), new RequestField("category", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("lifecycle_strategy", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"manual\",\"static\",\"elastic\""))), new RequestField("version", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:80"))), new RequestField("max_players", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("is_active", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\UpdateServerTemplateRequest", "body", List.of(new RequestField("region_key", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:regions,key"))), new RequestField("key", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"), new RuleDescriptor("string", null, "regex:/^[a-z0-9._-]+$/"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\Unique", "unique:server_templates,key,NULL,id"))), new RequestField("name", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:120"))), new RequestField("category", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("lifecycle_strategy", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"manual\",\"static\",\"elastic\""))), new RequestField("version", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:80"))), new RequestField("max_players", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("is_active", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))), new RequestField("startup_world_key", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:150"), new RuleDescriptor("string", null, "exists:worlds,key"))), new RequestField("asset_pack_keys", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "array"))), new RequestField("asset_pack_keys.*", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "distinct"), new RuleDescriptor("string", null, "exists:asset_packs,key"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
@@ -7,12 +7,30 @@ public final class PlayersApi {
public static final PlayersApi INSTANCE = new PlayersApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
AuthorizationPermissionSnapshotEndpoint.VALUE,
BalanceTransactionsIndexEndpoint.VALUE,
BalanceTransactionsStoreEndpoint.VALUE,
BalancesIndexEndpoint.VALUE,
CatalogEmotesIndexEndpoint.VALUE,
EntitlementsIndexEndpoint.VALUE,
EntitlementsRevokeEndpoint.VALUE,
EntitlementsStoreEndpoint.VALUE,
GamePermissionsShowEndpoint.VALUE,
GameplayAttemptsIndexEndpoint.VALUE,
GameplayStatsShowEndpoint.VALUE,
ModerationSanctionSnapshotEndpoint.VALUE,
NotificationsIndexEndpoint.VALUE,
NotificationsReadEndpoint.VALUE,
NotificationsStoreEndpoint.VALUE,
PresenceEndpoint.VALUE,
SessionProfileEndpoint.VALUE,
SocialGraphEndpoint.VALUE,
WebsiteProfileEndpoint.VALUE
SocialPrivacyUpdateEndpoint.VALUE,
VisibilityUpdateEndpoint.VALUE,
WebsiteProfileEndpoint.VALUE,
XpLedgerIndexEndpoint.VALUE,
XpLedgerStoreEndpoint.VALUE,
XpProgressIndexEndpoint.VALUE
);
private PlayersApi() {
@@ -40,6 +58,54 @@ public final class PlayersApi {
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint authorizationPermissionSnapshot() {
return AuthorizationPermissionSnapshotEndpoint.VALUE;
}
public ApiEndpoint balanceTransactionsIndex() {
return BalanceTransactionsIndexEndpoint.VALUE;
}
public ApiEndpoint balanceTransactionsStore() {
return BalanceTransactionsStoreEndpoint.VALUE;
}
public ApiEndpoint balancesIndex() {
return BalancesIndexEndpoint.VALUE;
}
public ApiEndpoint catalogEmotesIndex() {
return CatalogEmotesIndexEndpoint.VALUE;
}
public ApiEndpoint entitlementsIndex() {
return EntitlementsIndexEndpoint.VALUE;
}
public ApiEndpoint entitlementsRevoke() {
return EntitlementsRevokeEndpoint.VALUE;
}
public ApiEndpoint entitlementsStore() {
return EntitlementsStoreEndpoint.VALUE;
}
public ApiEndpoint gamePermissionsShow() {
return GamePermissionsShowEndpoint.VALUE;
}
public ApiEndpoint gameplayAttemptsIndex() {
return GameplayAttemptsIndexEndpoint.VALUE;
}
public ApiEndpoint gameplayStatsShow() {
return GameplayStatsShowEndpoint.VALUE;
}
public ApiEndpoint moderationSanctionSnapshot() {
return ModerationSanctionSnapshotEndpoint.VALUE;
}
public ApiEndpoint notificationsIndex() {
return NotificationsIndexEndpoint.VALUE;
}
@@ -52,6 +118,10 @@ public final class PlayersApi {
return NotificationsStoreEndpoint.VALUE;
}
public ApiEndpoint presence() {
return PresenceEndpoint.VALUE;
}
public ApiEndpoint sessionProfile() {
return SessionProfileEndpoint.VALUE;
}
@@ -60,7 +130,27 @@ public final class PlayersApi {
return SocialGraphEndpoint.VALUE;
}
public ApiEndpoint socialPrivacyUpdate() {
return SocialPrivacyUpdateEndpoint.VALUE;
}
public ApiEndpoint visibilityUpdate() {
return VisibilityUpdateEndpoint.VALUE;
}
public ApiEndpoint websiteProfile() {
return WebsiteProfileEndpoint.VALUE;
}
public ApiEndpoint xpLedgerIndex() {
return XpLedgerIndexEndpoint.VALUE;
}
public ApiEndpoint xpLedgerStore() {
return XpLedgerStoreEndpoint.VALUE;
}
public ApiEndpoint xpProgressIndex() {
return XpProgressIndexEndpoint.VALUE;
}
}
@@ -7,8 +7,11 @@ public final class ServerApi {
public static final ServerApi INSTANCE = new ServerApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
InstancesAssetPacksVersionsAcknowledgeEndpoint.VALUE,
InstancesGameplayAttemptsStoreEndpoint.VALUE,
InstancesHeartbeatEndpoint.VALUE,
InstancesMatchesResultsStoreEndpoint.VALUE,
InstancesPlayersPresenceStoreEndpoint.VALUE,
InstancesRegisterEndpoint.VALUE,
InstancesShutdownEndpoint.VALUE,
ProfileEndpoint.VALUE
@@ -39,6 +42,14 @@ public final class ServerApi {
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint instancesAssetPacksVersionsAcknowledge() {
return InstancesAssetPacksVersionsAcknowledgeEndpoint.VALUE;
}
public ApiEndpoint instancesGameplayAttemptsStore() {
return InstancesGameplayAttemptsStoreEndpoint.VALUE;
}
public ApiEndpoint instancesHeartbeat() {
return InstancesHeartbeatEndpoint.VALUE;
}
@@ -47,6 +58,10 @@ public final class ServerApi {
return InstancesMatchesResultsStoreEndpoint.VALUE;
}
public ApiEndpoint instancesPlayersPresenceStore() {
return InstancesPlayersPresenceStoreEndpoint.VALUE;
}
public ApiEndpoint instancesRegister() {
return InstancesRegisterEndpoint.VALUE;
}
@@ -7,10 +7,14 @@ public final class ServiceAccountsApi {
public static final ServiceAccountsApi INSTANCE = new ServiceAccountsApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
IndexEndpoint.VALUE,
ShowEndpoint.VALUE,
StoreEndpoint.VALUE,
TokensDestroyEndpoint.VALUE,
TokensIndexEndpoint.VALUE,
TokensRotateEndpoint.VALUE,
TokensStoreEndpoint.VALUE
TokensStoreEndpoint.VALUE,
UpdateEndpoint.VALUE
);
private ServiceAccountsApi() {
@@ -38,6 +42,18 @@ public final class ServiceAccountsApi {
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint index() {
return IndexEndpoint.VALUE;
}
public ApiEndpoint show() {
return ShowEndpoint.VALUE;
}
public ApiEndpoint store() {
return StoreEndpoint.VALUE;
}
public ApiEndpoint tokensDestroy() {
return TokensDestroyEndpoint.VALUE;
}
@@ -53,4 +69,8 @@ public final class ServiceAccountsApi {
public ApiEndpoint tokensStore() {
return TokensStoreEndpoint.VALUE;
}
public ApiEndpoint update() {
return UpdateEndpoint.VALUE;
}
}
@@ -18,7 +18,7 @@ public final class TokensRotateEndpoint {
List.of("service-accounts.tokens.manage"),
List.of("api", "auth:sanctum", "service.abilities:service-accounts.tokens.manage"),
List.of(new RouteParameter("serviceAccount", new TypeDescriptor("App\\Domains\\Authorization\\Models\\ServiceAccount", false, false))),
new RequestDescriptor("App\\Domains\\Authorization\\Http\\Requests\\IssueServiceAccountTokenRequest", "body", List.of(new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("abilities", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "array"), new RuleDescriptor("string", null, "min:1"))), new RequestField("abilities.*", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"announcements.read\",\"audit-events.read\",\"configs.read\",\"integrations.read\",\"integrations.write\",\"matches.result.write\",\"moderation.cases.read\",\"moderation.cases.write\",\"notifications.read\",\"notifications.write\",\"plugins.deploy.write\",\"plugins.read\",\"presence.read\",\"players.website-profile.read\",\"players.session-profile.read\",\"presence.write\",\"rewards.request\",\"social.read\",\"social.write\",\"service-accounts.tokens.manage\",\"server-instance.heartbeat.write\",\"server.profile.read\",\"server-templates.read\",\"server-templates.write\",\"support.issues.read\",\"support.issues.write\""))), new RequestField("expires_in_minutes", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:10080"))))),
new RequestDescriptor("App\\Domains\\Authorization\\Http\\Requests\\IssueServiceAccountTokenRequest", "body", List.of(new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("abilities", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "array"), new RuleDescriptor("string", null, "min:1"))), new RequestField("abilities.*", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"allocation.read\",\"announcements.read\",\"asset-packs.read\",\"asset-packs.write\",\"audit-events.read\",\"catalog.read\",\"catalog.write\",\"hub-live-ops.read\",\"hub-live-ops.write\",\"authorization.runtime.read\",\"configs.read\",\"entitlements.read\",\"entitlements.write\",\"game-permissions.read\",\"game-permissions.write\",\"gameplay.results.read\",\"gameplay.stats.read\",\"gameplay.results.write\",\"integrations.read\",\"integrations.write\",\"matches.result.write\",\"moderation.cases.read\",\"moderation.cases.write\",\"moderation.runtime.read\",\"notifications.read\",\"notifications.write\",\"plugins.deploy.write\",\"plugins.read\",\"presence.read\",\"players.visibility.write\",\"players.balances.read\",\"players.balances.write\",\"players.website-profile.read\",\"players.session-profile.read\",\"players.xp-ledger.read\",\"players.xp-ledger.write\",\"presence.write\",\"rewards.read\",\"rewards.write\",\"rewards.request\",\"social.read\",\"social.write\",\"service-accounts.manage\",\"service-accounts.tokens.manage\",\"server-instance.heartbeat.write\",\"server.profile.read\",\"server-templates.read\",\"server-templates.write\",\"support.issues.read\",\"support.issues.write\",\"worlds.read\",\"worlds.write\""))), new RequestField("expires_in_minutes", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:10080"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
@@ -18,7 +18,7 @@ public final class TokensStoreEndpoint {
List.of("service-accounts.tokens.manage"),
List.of("api", "auth:sanctum", "service.abilities:service-accounts.tokens.manage"),
List.of(new RouteParameter("serviceAccount", new TypeDescriptor("App\\Domains\\Authorization\\Models\\ServiceAccount", false, false))),
new RequestDescriptor("App\\Domains\\Authorization\\Http\\Requests\\IssueServiceAccountTokenRequest", "body", List.of(new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("abilities", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "array"), new RuleDescriptor("string", null, "min:1"))), new RequestField("abilities.*", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"announcements.read\",\"audit-events.read\",\"configs.read\",\"integrations.read\",\"integrations.write\",\"matches.result.write\",\"moderation.cases.read\",\"moderation.cases.write\",\"notifications.read\",\"notifications.write\",\"plugins.deploy.write\",\"plugins.read\",\"presence.read\",\"players.website-profile.read\",\"players.session-profile.read\",\"presence.write\",\"rewards.request\",\"social.read\",\"social.write\",\"service-accounts.tokens.manage\",\"server-instance.heartbeat.write\",\"server.profile.read\",\"server-templates.read\",\"server-templates.write\",\"support.issues.read\",\"support.issues.write\""))), new RequestField("expires_in_minutes", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:10080"))))),
new RequestDescriptor("App\\Domains\\Authorization\\Http\\Requests\\IssueServiceAccountTokenRequest", "body", List.of(new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("abilities", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "array"), new RuleDescriptor("string", null, "min:1"))), new RequestField("abilities.*", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"allocation.read\",\"announcements.read\",\"asset-packs.read\",\"asset-packs.write\",\"audit-events.read\",\"catalog.read\",\"catalog.write\",\"hub-live-ops.read\",\"hub-live-ops.write\",\"authorization.runtime.read\",\"configs.read\",\"entitlements.read\",\"entitlements.write\",\"game-permissions.read\",\"game-permissions.write\",\"gameplay.results.read\",\"gameplay.stats.read\",\"gameplay.results.write\",\"integrations.read\",\"integrations.write\",\"matches.result.write\",\"moderation.cases.read\",\"moderation.cases.write\",\"moderation.runtime.read\",\"notifications.read\",\"notifications.write\",\"plugins.deploy.write\",\"plugins.read\",\"presence.read\",\"players.visibility.write\",\"players.balances.read\",\"players.balances.write\",\"players.website-profile.read\",\"players.session-profile.read\",\"players.xp-ledger.read\",\"players.xp-ledger.write\",\"presence.write\",\"rewards.read\",\"rewards.write\",\"rewards.request\",\"social.read\",\"social.write\",\"service-accounts.manage\",\"service-accounts.tokens.manage\",\"server-instance.heartbeat.write\",\"server.profile.read\",\"server-templates.read\",\"server-templates.write\",\"support.issues.read\",\"support.issues.write\",\"worlds.read\",\"worlds.write\""))), new RequestField("expires_in_minutes", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:10080"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
@@ -7,6 +7,8 @@ public final class SocialApi {
public static final SocialApi INSTANCE = new SocialApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
BlocksRemoveEndpoint.VALUE,
BlocksStoreEndpoint.VALUE,
FriendshipsAcceptEndpoint.VALUE,
FriendshipsStoreEndpoint.VALUE,
GuildInvitesAcceptEndpoint.VALUE,
@@ -54,6 +56,14 @@ public final class SocialApi {
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint blocksRemove() {
return BlocksRemoveEndpoint.VALUE;
}
public ApiEndpoint blocksStore() {
return BlocksStoreEndpoint.VALUE;
}
public ApiEndpoint friendshipsAccept() {
return FriendshipsAcceptEndpoint.VALUE;
}
@@ -0,0 +1,155 @@
package net.kewwbec.network.http;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import net.kewwbec.network.auth.ApiToken;
import net.kewwbec.network.errors.AuthException;
import net.kewwbec.network.errors.ControlPlaneException;
import net.kewwbec.network.errors.NotFoundException;
import net.kewwbec.network.errors.RemoteException;
import net.kewwbec.network.errors.ValidationException;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
public final class ControlPlaneHttpClient {
private static final Gson GSON = new GsonBuilder().create();
private final HttpClient httpClient;
private final String baseUrl;
private final ApiToken token;
public ControlPlaneHttpClient(String baseUrl, ApiToken token, Duration timeout) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
this.token = token;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(timeout)
.build();
}
public JsonObject get(String uri) throws ControlPlaneException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + uri))
.header("Authorization", "Bearer " + token.value())
.header("Accept", "application/json")
.GET()
.timeout(Duration.ofSeconds(10))
.build();
return send(request);
}
public JsonObject post(String uri, Object body) throws ControlPlaneException {
String json = body != null ? GSON.toJson(body) : "{}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + uri))
.header("Authorization", "Bearer " + token.value())
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.timeout(Duration.ofSeconds(10))
.build();
return send(request);
}
public <T> T get(String uri, Class<T> responseType) throws ControlPlaneException {
JsonObject body = get(uri);
return GSON.fromJson(extractData(body), responseType);
}
public <T> java.util.List<T> getList(String uri, Class<T> elementType) throws ControlPlaneException {
JsonObject body = get(uri);
Type listType = TypeToken.getParameterized(java.util.List.class, elementType).getType();
return GSON.fromJson(extractData(body), listType);
}
public <T> T post(String uri, Object requestBody, Class<T> responseType) throws ControlPlaneException {
JsonObject body = post(uri, requestBody);
return GSON.fromJson(extractData(body), responseType);
}
private JsonObject send(HttpRequest request) throws ControlPlaneException {
HttpResponse<String> response;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
throw new ControlPlaneException("Network error communicating with control plane", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ControlPlaneException("Request interrupted", e);
}
int status = response.statusCode();
String responseBody = response.body();
if (status >= 200 && status < 300) {
if (responseBody == null || responseBody.isBlank()) {
return new JsonObject();
}
JsonElement element = JsonParser.parseString(responseBody);
return element.isJsonObject() ? element.getAsJsonObject() : new JsonObject();
}
if (status == 401 || status == 403) {
throw new AuthException(status, parseMessage(responseBody));
}
if (status == 404) {
throw new NotFoundException(parseMessage(responseBody));
}
if (status == 422) {
throw new ValidationException(parseMessage(responseBody), parseErrors(responseBody));
}
if (status >= 500) {
throw new RemoteException(status, parseMessage(responseBody));
}
throw new ControlPlaneException("Unexpected response status " + status + ": " + responseBody);
}
private JsonElement extractData(JsonObject envelope) {
if (envelope.has("data")) {
return envelope.get("data");
}
return envelope;
}
private String parseMessage(String body) {
try {
JsonObject obj = JsonParser.parseString(body).getAsJsonObject();
if (obj.has("message")) {
return obj.get("message").getAsString();
}
} catch (Exception ignored) {}
return body;
}
private Map<String, String[]> parseErrors(String body) {
Map<String, String[]> errors = new HashMap<>();
try {
JsonObject obj = JsonParser.parseString(body).getAsJsonObject();
if (obj.has("errors")) {
JsonObject errObj = obj.getAsJsonObject("errors");
for (String key : errObj.keySet()) {
com.google.gson.JsonArray arr = errObj.getAsJsonArray(key);
String[] messages = new String[arr.size()];
for (int i = 0; i < arr.size(); i++) {
messages[i] = arr.get(i).getAsString();
}
errors.put(key, messages);
}
}
} catch (Exception ignored) {}
return errors;
}
}
@@ -0,0 +1,19 @@
package net.kewwbec.network.http;
import java.util.Map;
public final class PathBuilder {
private PathBuilder() {}
public static String resolve(String uriTemplate, Map<String, String> params) {
String result = uriTemplate;
for (Map.Entry<String, String> entry : params.entrySet()) {
result = result.replace("{" + entry.getKey() + "}", entry.getValue());
}
return result;
}
public static String resolve(String uriTemplate, String paramName, String paramValue) {
return uriTemplate.replace("{" + paramName + "}", paramValue);
}
}
@@ -0,0 +1,49 @@
package net.kewwbec.network.models.players;
import com.google.gson.annotations.SerializedName;
public final class PlayerEntitlement {
private String id;
private String status;
@SerializedName("source_type")
private String sourceType;
@SerializedName("granted_at")
private String grantedAt;
@SerializedName("expires_at")
private String expiresAt;
@SerializedName("revoked_at")
private String revokedAt;
@SerializedName("revoked_reason")
private String revokedReason;
private EntitlementDefinition definition;
public String id() { return id; }
public String status() { return status; }
public String sourceType() { return sourceType; }
public String grantedAt() { return grantedAt; }
public String expiresAt() { return expiresAt; }
public String revokedAt() { return revokedAt; }
public String revokedReason() { return revokedReason; }
public EntitlementDefinition definition() { return definition; }
public boolean isActive() { return "active".equals(status); }
public boolean isRevoked() { return "revoked".equals(status); }
public static final class EntitlementDefinition {
private String id;
private String key;
private String type;
private String name;
public String id() { return id; }
public String key() { return key; }
public String type() { return type; }
public String name() { return name; }
}
}
@@ -0,0 +1,97 @@
package net.kewwbec.network.models.players;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public final class PlayerSessionProfile {
private String id;
@SerializedName("current_name")
private String currentName;
@SerializedName("display_name")
private String displayName;
private String status;
@SerializedName("game_identity")
private GameIdentity gameIdentity;
private List<PlayerEntitlement> entitlements;
@SerializedName("last_seen_at")
private String lastSeenAt;
private SocialSummary social;
public String id() { return id; }
public String currentName() { return currentName; }
public String displayName() { return displayName; }
public String status() { return status; }
public GameIdentity gameIdentity() { return gameIdentity; }
public List<PlayerEntitlement> entitlements() { return entitlements != null ? entitlements : List.of(); }
public String lastSeenAt() { return lastSeenAt; }
public SocialSummary social() { return social; }
public static final class GameIdentity {
private String provider;
@SerializedName("player_id")
private String playerId;
public String provider() { return provider; }
public String playerId() { return playerId; }
}
public static final class SocialSummary {
@SerializedName("friends_count")
private int friendsCount;
private GuildSummary guild;
private PartySummary party;
public int friendsCount() { return friendsCount; }
public GuildSummary guild() { return guild; }
public PartySummary party() { return party; }
public boolean isInGuild() { return guild != null; }
public boolean isInParty() { return party != null; }
}
public static final class GuildSummary {
private String id;
private String slug;
private String name;
private String tag;
private String role;
@SerializedName("member_count")
private int memberCount;
public String id() { return id; }
public String slug() { return slug; }
public String name() { return name; }
public String tag() { return tag; }
public String role() { return role; }
public int memberCount() { return memberCount; }
}
public static final class PartySummary {
private String id;
@SerializedName("member_count")
private int memberCount;
@SerializedName("max_members")
private int maxMembers;
@SerializedName("is_leader")
private boolean isLeader;
public String id() { return id; }
public int memberCount() { return memberCount; }
public int maxMembers() { return maxMembers; }
public boolean isLeader() { return isLeader; }
}
}
@@ -0,0 +1,31 @@
package net.kewwbec.network.models.server;
import com.google.gson.annotations.SerializedName;
public final class HeartbeatPayload {
private final String status;
@SerializedName("current_players")
private final int currentPlayers;
@SerializedName("max_players")
private final int maxPlayers;
private HeartbeatPayload(String status, int currentPlayers, int maxPlayers) {
this.status = status;
this.currentPlayers = currentPlayers;
this.maxPlayers = maxPlayers;
}
public static HeartbeatPayload running(int currentPlayers, int maxPlayers) {
return new HeartbeatPayload("running", currentPlayers, maxPlayers);
}
public static HeartbeatPayload draining(int currentPlayers, int maxPlayers) {
return new HeartbeatPayload("draining", currentPlayers, maxPlayers);
}
public String status() { return status; }
public int currentPlayers() { return currentPlayers; }
public int maxPlayers() { return maxPlayers; }
}
@@ -0,0 +1,16 @@
package net.kewwbec.network.models.server;
public enum PresenceState {
LIVE,
STALE,
UNKNOWN;
public static PresenceState fromString(String value) {
if (value == null) return UNKNOWN;
return switch (value.toLowerCase()) {
case "live" -> LIVE;
case "stale" -> STALE;
default -> UNKNOWN;
};
}
}
@@ -0,0 +1,18 @@
package net.kewwbec.network.models.server;
public enum ReadinessState {
READY,
DRAINING,
STALE,
UNKNOWN;
public static ReadinessState fromString(String value) {
if (value == null) return UNKNOWN;
return switch (value.toLowerCase()) {
case "ready" -> READY;
case "draining" -> DRAINING;
case "stale" -> STALE;
default -> UNKNOWN;
};
}
}
@@ -0,0 +1,37 @@
package net.kewwbec.network.models.server;
import com.google.gson.annotations.SerializedName;
public final class RegistrationPayload {
private final String status;
private final String host;
private final int port;
@SerializedName("current_players")
private final int currentPlayers;
@SerializedName("max_players")
private final int maxPlayers;
private RegistrationPayload(String status, String host, int port, int currentPlayers, int maxPlayers) {
this.status = status;
this.host = host;
this.port = port;
this.currentPlayers = currentPlayers;
this.maxPlayers = maxPlayers;
}
public static RegistrationPayload starting(String host, int port, int maxPlayers) {
return new RegistrationPayload("starting", host, port, 0, maxPlayers);
}
public static RegistrationPayload running(String host, int port, int currentPlayers, int maxPlayers) {
return new RegistrationPayload("running", host, port, currentPlayers, maxPlayers);
}
public String status() { return status; }
public String host() { return host; }
public int port() { return port; }
public int currentPlayers() { return currentPlayers; }
public int maxPlayers() { return maxPlayers; }
}
@@ -0,0 +1,67 @@
package net.kewwbec.network.models.server;
import com.google.gson.annotations.SerializedName;
public final class ServerInstance {
private String id;
private String slug;
private String status;
private String host;
private int port;
@SerializedName("current_players")
private int currentPlayers;
@SerializedName("max_players")
private int maxPlayers;
@SerializedName("available_slots")
private int availableSlots;
@SerializedName("has_capacity")
private boolean hasCapacity;
@SerializedName("presence_state")
private String presenceStateRaw;
@SerializedName("readiness_state")
private String readinessStateRaw;
@SerializedName("accepting_players")
private boolean acceptingPlayers;
@SerializedName("last_heartbeat_at")
private String lastHeartbeatAt;
public String id() { return id; }
public String slug() { return slug; }
public String status() { return status; }
public String host() { return host; }
public int port() { return port; }
public int currentPlayers() { return currentPlayers; }
public int maxPlayers() { return maxPlayers; }
public int availableSlots() { return availableSlots; }
public boolean hasCapacity() { return hasCapacity; }
public boolean acceptingPlayers() { return acceptingPlayers; }
public String lastHeartbeatAt() { return lastHeartbeatAt; }
public PresenceState presenceState() {
return PresenceState.fromString(presenceStateRaw);
}
public ReadinessState readinessState() {
return ReadinessState.fromString(readinessStateRaw);
}
public boolean isReady() {
return readinessState() == ReadinessState.READY;
}
public boolean isDraining() {
return readinessState() == ReadinessState.DRAINING;
}
public boolean isStale() {
return readinessState() == ReadinessState.STALE || presenceState() == PresenceState.STALE;
}
}
@@ -0,0 +1,32 @@
package net.kewwbec.network.models.server;
import com.google.gson.annotations.SerializedName;
public final class ServerProfile {
@SerializedName("service_account")
private ServiceAccountSummary serviceAccount;
@SerializedName("server_instance")
private ServerInstance serverInstance;
public ServiceAccountSummary serviceAccount() { return serviceAccount; }
public ServerInstance serverInstance() { return serverInstance; }
public boolean hasInstance() { return serverInstance != null; }
public static final class ServiceAccountSummary {
private String id;
private String key;
private String name;
private String type;
@SerializedName("last_used_at")
private String lastUsedAt;
public String id() { return id; }
public String key() { return key; }
public String name() { return name; }
public String type() { return type; }
public String lastUsedAt() { return lastUsedAt; }
}
}
@@ -3,7 +3,11 @@ package net.kewwbec.network;
import net.kewwbec.network.generated.api.v1.ControlPlaneApiV1;
public class network {
public ControlPlaneApiV1 controlPlane() {
return ControlPlaneApiV1.INSTANCE;
}
public ControlPlaneApiV1 controlPlane() {
return ControlPlaneApiV1.INSTANCE;
}
public static ControlPlaneClient.Builder builder() {
return ControlPlaneClient.builder();
}
}
+10 -14
View File
@@ -1,22 +1,18 @@
{
"Group": "Example",
"Name": "ExamplePlugin",
"Group": "net.kewwbec",
"Name": "core-network",
"Version": "0.0.2",
"Description": "An example plugin for HyTale!",
"Description": "KweebecNetwork control-plane SDK for Hytale server plugins",
"Authors": [
{
"Name": "It's you!"
"Name": "KweebecNetwork"
}
],
"Website": "example.org",
"Website": "https://kewwbec.net",
"ServerVersion": "2026.02.17-255364b8e",
"Dependencies": {
},
"OptionalDependencies": {
},
"Dependencies": {},
"OptionalDependencies": {},
"DisabledByDefault": false,
"Main": "org.example.plugin.ExamplePlugin",
"IncludesAssetPack": true
}
"Main": "net.kewwbec.network.NetworkPlugin",
"IncludesAssetPack": false
}