Compare commits

..

13 Commits

Author SHA1 Message Date
HeruEdhel be35d92b52 Refactor API response descriptors to use resource classes
Build Plugin / build (push) Has been cancelled
Build Plugin / release (push) Has been cancelled
- Updated response descriptors in various endpoints to utilize specific resource classes instead of generic JsonResponse.
- Changed response types for friendship, guild, party, and support issue endpoints to their respective resource classes.
- Incremented version number in manifest.json to 0.5.3 for the new changes.
2026-06-05 09:23:01 -07:00
HeruEdhel 59330bdf84 feat: Implement runtime API cache store and related configurations
- Added RuntimeApiCacheStore for managing API cache entries in memory.
- Introduced StorageTarget enum to define various storage targets.
- Created StoredCacheEntry record to encapsulate cache entries.
- Defined SyncState enum to represent the synchronization state of cache entries.
- Added ControlPlaneConfigurationSource enum for different configuration sources.
- Developed ControlPlaneRuntimeConfig class for runtime configuration management.
- Implemented ControlPlaneStartupConfigResolver for resolving configuration from various sources.
- Created ControlPlaneStartupSettings to hold startup configuration values.
- Added ResolvedControlPlaneStartupConfig record to encapsulate resolved configuration.
- Implemented tests for batch client and cache client functionalities.
- Added tests for ControlPlaneStartupConfigResolver to validate configuration resolution logic.
2026-06-05 07:00:14 -07:00
HeruEdhel 7a4a0bd4bd 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
2026-05-30 23:25:22 -07:00
HeruEdhel 7ab54bb8d3 core network first time gen
Build Plugin / build (push) Has been cancelled
Build Plugin / release (push) Has been cancelled
2026-05-27 20:34:13 -07:00
Britakee b6a2a9d515 updated the template so it finds Hytale even when it isn’t in the default path 2026-02-18 08:57:15 +01:00
Britakee be9968c345 Switch to Groovy DSL; add runServer task
Migrate the project from Gradle Kotlin DSL to Groovy DSL: update README to reference build.gradle/settings.gradle and change code blocks to Groovy. Remove build.gradle.kts. Add runServerJar and runServer tasks in build.gradle that depend on shadowJar and launch the Hytale server (uses hytaleHome paths, assets, and optional user mods). Bump plugin manifest version to 0.0.3 and set a concrete ServerVersion string. Fixed some issues.
2026-02-17 19:21:10 +01:00
Britakee a1f96f58e6 Merge pull request #22 from hytailor/dev/vscode
Fix gitignore for VS Code
2026-02-17 18:23:34 +01:00
Britakee 839df5f059 Merge pull request #19 from FedericoLeiva12/main
Use official Hytale mavel repository and fix Github Actions workflow
2026-02-17 18:23:15 +01:00
DocW 647fe81537 Track VS Code Java formatter config in repo
Add negated pattern to `.gitignore` for the VS Code Java formatter
configuration file (`.vscode/java-formatter.xml`).
2026-02-01 14:20:58 -05:00
DocW 772dabc708 Track VS Code settings in repository
Add negated pattern to `.gitignore` for the VS Code settings file
(`.vscode/settings.json`).
2026-02-01 14:19:36 -05:00
DocW bbd0265f2e Allow files in .vscode/ to be included in Git repo
Allow the exclusion of files in the `.vscode/` directory to be
overridden via negated patterns (gitignore patterns beginning with `!`)
by changing the pattern in `.gitignore` from `.vscode/` to `.vscode/*`
(i.e., by ignoring the files in `.vscode/` rather than the `.vscode/`
directory itself).
2026-02-01 14:13:52 -05:00
InvBoy b0d6afbd4c Remove unused database driver dependencies
Removed unused database driver dependencies.
2026-01-27 23:00:05 -03:00
Federico Leiva b2045653ef Enhance build configuration and dependencies for Hytale plugin to use HytaleServer.jar from official maven repository. Added permissions in workflows to allow github action to create new releases. 2026-01-27 17:46:48 -03:00
145 changed files with 7573 additions and 557 deletions
+57 -53
View File
@@ -1,8 +1,12 @@
name: Build Plugin
permissions:
contents: write
on:
push:
branches: [ main, develop ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]
@@ -11,62 +15,62 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
- name: Cache Gradle Dependencies
uses: actions/cache@v4
with:
path: ~/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant Execute Permission for Gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew shadowJar
- name: Run Tests
run: ./gradlew test
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: plugin-jar
path: build/libs/*.jar
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
- name: Cache Gradle Dependencies
uses: actions/cache@v4
with:
path: ~/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant Execute Permission for Gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew shadowJar
- name: Run Tests
run: ./gradlew test
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: plugin-jar
path: build/libs/*.jar
release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
- name: Grant Execute Permission for Gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew shadowJar
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: build/libs/*.jar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
- name: Grant Execute Permission for Gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew shadowJar
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: build/libs/*.jar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6 -2
View File
@@ -38,7 +38,9 @@ bin/
/.nb-gradle/
# VS Code
.vscode/
.vscode/*
!.vscode/java-formatter.xml
!.vscode/settings.json
# Mac OS
.DS_Store
@@ -58,4 +60,6 @@ desktop.ini
# Template specific
libs/HytaleServer.jar
buildSrc/
buildSrc/
.github/
src/main/java/net/kewwbec/network/generated/
+115 -346
View File
@@ -1,381 +1,150 @@
# Hytale Plugin Template
# Kweebec Network Core
A minimal, ready-to-use template for creating Hytale plugins with modern build tools and automated testing.
This repository contains the Hytale plugin-side code for the Kweebec network along with the generated Java view of the control-plane API.
> **✨ Builds immediately without any changes!** Clone and run `./gradlew shadowJar` to get a working plugin JAR.
It is not just a generic plugin template anymore. The repo now has two important responsibilities:
## Features
- handwritten Hytale plugin and runtime code
- generator-owned Java API packages synced from the sibling `core-control-plane` repository
**Modern Build System** - Gradle with Kotlin DSL
**Automated Testing** - Custom Gradle plugin for one-command server testing
**Java 25** - Latest Java features
**ShadowJar** - Automatic dependency bundling
**CI/CD Ready** - GitHub Actions workflow included
**Minimal Structure** - Only essential files, write your own code
## Repository purpose
---
`core-network` is where game-side integrations for the network live.
## Quick Start
That includes:
### Prerequisites
- plugin entry points and handwritten Java glue
- future gameplay-facing integrations that consume the control-plane API
- the generated versioned API tree under `net.kewwbec.network.generated.api.v1`
- **Java 25 JDK** - [Download here](https://www.oracle.com/java/technologies/downloads/)
- **IntelliJ IDEA** - [Download here](https://www.jetbrains.com/idea/download/) (Community Edition is fine)
- **Git** - [Download here](https://git-scm.com/)
The generated files are meant to make the control plane feel like a stable Java dependency instead of a pile of raw route strings.
### 1. Clone or Download
## Generated control-plane SDK tree
```bash
git clone https://github.com/yourusername/hytale-plugin-template.git
cd hytale-plugin-template
The generated Java API output lives under:
```text
src/main/java/net/kewwbec/network/generated/api/v1/
ControlPlaneApiV1.java
shared/
announcements/
assets/
audit/
batch/
configs/
entitlements/
gamepermissions/
gameplay/
health/
hub/
infrastructure/
integrations/
moderation/
players/
plugins/
server/
serviceaccounts/
social/
support/
worlds/
```
**The template builds immediately without any changes!**
You can customize it later when you're ready to develop your plugin.
What that means:
### 2. Build Immediately (No Changes Needed!)
- each top-level API domain has its own package
- each domain has a `<Domain>Api` entry point
- each route has its own generated `*Endpoint` file
- shared descriptor records live in `shared/`
The template works out-of-the-box:
Example imports look like:
```java
import net.kewwbec.network.generated.api.v1.ControlPlaneApiV1;
import net.kewwbec.network.generated.api.v1.plugins.PluginsApi;
import net.kewwbec.network.generated.api.v1.moderation.CasesShowEndpoint;
```
The handwritten bridge is still:
```text
src/main/java/net/kewwbec/network/network.java
```
Everything under `src/main/java/net/kewwbec/network/generated` is generator-owned and should **not** be edited manually. If they need edits check the generator in the control panel repo
## How generation works
The source of truth for the generated API tree is the `core-control-plane` repository.
Run the sync from that repository:
```bash
# Windows
php artisan control-plane:sync-network-sdk --dry-run
php artisan control-plane:sync-network-sdk
```
That command exports the Laravel API contract and rewrites the generated Java tree in this repository.
## Common commands
```bash
# Compile Java sources
gradlew.bat compileJava
# Build the plugin jar
gradlew.bat shadowJar
# Linux/Mac
./gradlew shadowJar
```
# Build with the standard lifecycle
gradlew.bat build
Your plugin JAR will be in: `build/libs/TemplatePlugin-1.0.0.jar`
### 3. Customize Your Plugin (Optional)
When ready to customize, edit these files:
**`settings.gradle.kts`:**
```kotlin
rootProject.name = "your-plugin-name"
```
**`gradle.properties`:**
```properties
pluginGroup=com.yourname
pluginVersion=1.0.0
pluginDescription=Your plugin description
```
**`src/main/resources/manifest.json`:**
```json
{
"Group": "YourName",
"Name": "YourPluginName",
"Main": "com.yourname.yourplugin.YourPlugin"
}
```
**Rename the main plugin class:**
- Rename `src/main/java/com/example/templateplugin/TemplatePlugin.java`
- Update package name to match your `pluginGroup`
### 4. Build Your Plugin
```bash
# Windows
gradlew.bat shadowJar
# Linux/Mac
./gradlew shadowJar
```
Your plugin JAR will be in: `build/libs/YourPluginName-1.0.0.jar`
### 5. Implement Your Plugin
Write your plugin code in `src/main/java/`:
- Commands
- Event listeners
- Services
- Storage
- Utilities
See our [documentation](../Documentation/) for examples and patterns.
### 6. Test Your Plugin (Automated!)
```bash
# Windows
# Run the Hytale server workflow when local server prerequisites are available
gradlew.bat runServer
# Linux/Mac
./gradlew runServer
```
This will:
1. Download the Hytale server (cached for future runs)
2. Build your plugin
3. Copy it to the server's mods folder
4. Start the server with interactive console
## Wiki docs
---
Developer-facing wiki pages live under:
## Project Structure
```
TemplatePlugin/
├── .github/workflows/
│ └── build.yml # CI/CD workflow
├── buildSrc/
│ ├── build.gradle.kts # Custom plugin configuration
│ └── src/main/kotlin/
│ └── RunHytalePlugin.kt # Automated server testing
├── src/main/
│ ├── java/com/example/templateplugin/
│ │ └── TemplatePlugin.java # Minimal main class (example)
│ └── resources/
│ └── manifest.json # Plugin metadata
├── .gitignore # Git ignore rules
├── build.gradle.kts # Build configuration
├── gradle.properties # Project properties
├── settings.gradle.kts # Project settings
├── LICENSE # MIT License
└── README.md # This file
```text
docs/wiki/
```
**Note:** This is a minimal template. Create your own folder structure:
Start with:
- `commands/` - For command implementations
- `listeners/` - For event listeners
- `services/` - For business logic
- `storage/` - For data persistence
- `utils/` - For utility classes
- `config/` - For configuration management
- [Core Network Wiki](docs/wiki/Home.md)
- [Getting Started](docs/wiki/Getting-Started.md)
- [Batched API Calls](docs/wiki/Batched-API-Calls.md)
- [API Response Storage](docs/wiki/API-Response-Storage.md)
- [Control Plane Client](docs/wiki/Control-Plane-Client.md)
- [Generated SDK](docs/wiki/Generated-SDK.md)
---
## Project structure
## Development Workflow
### Building
```bash
# Compile only
./gradlew compileJava
# Build plugin JAR
./gradlew shadowJar
# Clean and rebuild
./gradlew clean shadowJar
```text
core-network/
docs/wiki/
src/main/java/
net/kewwbec/network/
network.java
batch/
cache/
generated/api/v1/...
src/main/resources/
manifest.json
build.gradle
gradle.properties
settings.gradle
```
### Testing
## Working rules
```bash
# Run server with your plugin
./gradlew runServer
- Do not patch generated files by hand.
- Fix the control-plane routes or generator, then re-run the sync command.
- Keep handwritten plugin code outside the generated tree.
- Treat the control plane as the API owner; the game-side repo is the consumer.
# Run unit tests
./gradlew test
## Related repositories
# Clean test server
rm -rf run/
```
### Debugging
```bash
# Run server in debug mode
./gradlew runServer -Pdebug
# Then connect your IDE debugger to localhost:5005
```
---
## Customization
### Adding Dependencies
Edit `build.gradle.kts`:
```kotlin
dependencies {
// Hytale API (provided by server)
compileOnly(files("./HytaleServer.jar"))
// Your dependencies (will be bundled)
implementation("com.google.code.gson:gson:2.10.1")
// Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
```
### Configuring Server Testing
**Run Hytale Server** - A Gradle plugin to download and run a Hytale server for development and testing purposes. The server files will be located in the `run/` directory of the project. Before starting the server it will compile (shadowJar task) and copy the plugin jar to the server's `mods/` folder.
**Usage:**
Edit `build.gradle.kts`:
```kotlin
runHytale {
jarUrl = "url to hytale server jar"
}
```
Run the server with:
```bash
# Windows
gradlew.bat runServer
# Linux/Mac
./gradlew runServer
```
**Features:**
- ✅ Automatic server JAR download and caching
- ✅ Compiles and deploys your plugin automatically
- ✅ Starts server with interactive console
- ✅ One-command workflow: `./gradlew runServer`
- ✅ Server files in `run/` directory (gitignored)
### Implementing Your Plugin
**Recommended folder structure:**
```
src/main/java/com/yourname/yourplugin/
├── YourPlugin.java # Main class
├── commands/ # Commands
├── listeners/ # Event listeners
├── services/ # Business logic
├── storage/ # Data persistence
├── config/ # Configuration
└── utils/ # Utilities
```
**See our documentation for examples:**
- [Getting Started with Plugins](https://britakee-studios.gitbook.io/hytale-modding-documentation/plugins-java-development/07-getting-started-with-plugins)
- [Advanced Plugin Patterns](https://britakee-studios.gitbook.io/hytale-modding-documentation/plugins-java-development/12-advanced-plugin-patterns)
- [Common Plugin Features](https://britakee-studios.gitbook.io/hytale-modding-documentation/plugins-java-development/14-common-plugin-features)
---
## CI/CD
This template includes a GitHub Actions workflow that:
1. ✅ Builds your plugin on every push
2. ✅ Runs tests
3. ✅ Uploads artifacts
4. ✅ Creates releases (when you tag)
### Creating a Release
```bash
git tag v1.0.0
git push origin v1.0.0
```
GitHub Actions will automatically build and create a release with your plugin JAR.
---
## Best Practices
### ✅ DO:
- Use the Service-Storage pattern for data management
- Write unit tests for your business logic
- Use structured logging (not `System.out.println`)
- Handle errors gracefully
- Document your public API
- Version your releases semantically (1.0.0, 1.1.0, etc.)
### ❌ DON'T:
- Hardcode configuration values
- Block the main thread with heavy operations
- Ignore exceptions
- Use deprecated APIs
- Commit sensitive data (API keys, passwords)
---
## Troubleshooting
### Build Fails
```bash
# Clean and rebuild
./gradlew clean build --refresh-dependencies
```
### Server Won't Start
1. Check that `jarUrl` in `build.gradle.kts` is correct
2. Verify Java 25 is installed: `java -version`
3. Check logs in `run/logs/`
### Plugin Not Loading
1. Verify `manifest.json` has correct `Main` class
2. Check server logs for errors
3. Ensure all dependencies are bundled in JAR
---
## Documentation
For detailed guides on plugin development, see:
- [Hytale Modding Documentation](https://github.com/yourusername/hytale-modding/tree/main/Documentation)
- [Getting Started with Plugins](../Documentation/07-getting-started-with-plugins.md)
- [Advanced Plugin Patterns](../Documentation/12-advanced-plugin-patterns.md)
- [Common Plugin Features](../Documentation/14-common-plugin-features.md)
---
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
---
## License
This template is released under the MIT License. You are free to use it for any purpose.
---
## Support
- **Issues:** [GitHub Issues](https://github.com/yourusername/hytale-plugin-template/issues)
- **Documentation:** [Hytale Modding Docs](https://github.com/yourusername/hytale-modding)
- **Community:** Join the Hytale modding community
---
## Credits
Created by the Hytale modding community.
Based on best practices from production Hytale plugins.
---
**Happy Modding! 🎮**
- `../core-control-plane` owns the Laravel API and the SDK sync commands.
- `core-network` consumes the generated Java tree and will host the gameplay-facing wrappers around it.
+120 -57
View File
@@ -1,6 +1,7 @@
plugins {
id 'java'
id 'org.jetbrains.gradle.plugin.idea-ext' version '1.3'
id 'com.gradleup.shadow' version '9.3.1'
}
import org.gradle.internal.os.OperatingSystem
@@ -8,8 +9,9 @@ import org.gradle.internal.os.OperatingSystem
ext {
if (project.hasProperty('hytale_home')) {
hytaleHome = project.findProperty('hytale_home')
}
else {
} else if (System.getenv('HYTALE_HOME')) {
hytaleHome = System.getenv('HYTALE_HOME')
} else {
def os = OperatingSystem.current()
if (os.isWindows()) {
hytaleHome = "${System.getProperty("user.home")}/AppData/Roaming/Hytale"
@@ -26,11 +28,10 @@ ext {
}
}
if (!project.hasProperty('hytaleHome')) {
throw new GradleException('Your Hytale install could not be detected automatically. If you are on an unsupported platform or using a custom install location, please define the install location using the hytale_home property.');
}
else if (!file(project.findProperty('hytaleHome')).exists()) {
throw new GradleException("Failed to find Hytale at the expected location. Please make sure you have installed the game. The expected location can be changed using the hytale_home property. Currently looking in ${project.findProperty('hytaleHome')}")
def hytaleHomePath = ext.has('hytaleHome') ? hytaleHome : null
def hasHytaleHome = (hytaleHomePath != null) && file(hytaleHomePath).exists()
if (!hasHytaleHome) {
logger.lifecycle("Hytale install not detected; run configs that launch the server will be skipped. Set -Phytale_home=/path/to/Hytale to enable them.")
}
java {
@@ -45,16 +46,27 @@ javadoc {
}
// Adds the Hytale server as a build dependency, allowing you to reference and
// compile against their code. This requires you to have Hytale installed using
// the official launcher for now.
// 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.
dependencies {
implementation(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar"))
compileOnly("com.hypixel.hytale:Server:$hytale_build")
if (hasHytaleHome) {
runtimeOnly(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar"))
}
// Your dependencies here
}
// Create the working directory to run the server if it does not already exist.
def serverRunDir = file("$projectDir/run")
if (!serverRunDir.exists()) {
serverRunDir.mkdirs()
repositories {
mavenCentral()
maven {
name = "hytale-release"
url = uri("https://maven.hytale.com/release")
}
maven {
name = "hytale-pre-release"
url = uri("https://maven.hytale.com/pre-release")
}
}
// Updates the manifest.json file with the latest properties defined in the
@@ -78,54 +90,105 @@ tasks.named('processResources') {
dependsOn 'updatePluginManifest'
}
def createServerRunArguments(String srcDir) {
def programParameters = '--allow-op --disable-sentry --assets="' + "${hytaleHome}/install/$patchline/package/game/latest/Assets.zip" + '"'
def modPaths = []
if (includes_pack.toBoolean()) {
modPaths << srcDir
}
if (load_user_mods.toBoolean()) {
modPaths << "${hytaleHome}/UserData/Mods"
}
if (!modPaths.isEmpty()) {
programParameters += ' --mods="' + modPaths.join(',') + '"'
}
return programParameters
tasks.named('shadowJar') {
archiveClassifier.set('')
mergeServiceFiles()
}
// Creates a run configuration in IDEA that will run the Hytale server with
// your plugin and the default assets.
idea.project.settings.runConfigurations {
'HytaleServer'(org.jetbrains.gradle.ext.Application) {
mainClass = 'com.hypixel.hytale.Main'
moduleName = project.idea.module.name + '.main'
programParameters = createServerRunArguments(sourceSets.main.java.srcDirs.first().parentFile.absolutePath)
workingDirectory = serverRunDir.absolutePath
}
// Ensure the shaded jar is produced during a normal build.
tasks.named('build') {
dependsOn 'shadowJar'
}
// Creates a launch.json file for VSCode with the same configuration
tasks.register('generateVSCodeLaunch') {
def vscodeDir = file("$projectDir/.vscode")
def launchFile = file("$vscodeDir/launch.json")
doLast {
if (!vscodeDir.exists()) {
vscodeDir.mkdirs()
if (hasHytaleHome) {
// Create the working directory to run the server if it does not already exist.
def serverRunDir = file("$projectDir/run")
if (!serverRunDir.exists()) {
serverRunDir.mkdirs()
}
def createServerRunArguments = { String srcDir ->
def programParameters = '--allow-op --disable-sentry --assets="' + "${hytaleHome}/install/$patchline/package/game/latest/Assets.zip" + '"'
def modPaths = []
if (includes_pack.toBoolean()) {
modPaths << srcDir
}
def programParams = createServerRunArguments("\${workspaceFolder}")
def launchConfig = [
version: "0.2.0",
configurations: [
[
type: "java",
name: "HytaleServer",
request: "launch",
mainClass: "com.hypixel.hytale.Main",
args: programParams,
cwd: "\${workspaceFolder}/run"
]
if (load_user_mods.toBoolean()) {
modPaths << "${hytaleHome}/UserData/Mods"
}
if (!modPaths.isEmpty()) {
programParameters += ' --mods="' + modPaths.join(',') + '"'
}
return programParameters
}
def serverJar = file("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar")
def assetsZip = file("$hytaleHome/install/$patchline/package/game/latest/Assets.zip")
def shadowJarTask = tasks.named('shadowJar')
tasks.register('runServerJar', JavaExec) {
dependsOn shadowJarTask
mainClass = 'com.hypixel.hytale.Main'
classpath = files(serverJar)
workingDir = serverRunDir.absolutePath
doFirst {
def modPaths = [shadowJarTask.get().archiveFile.get().asFile.absolutePath]
if (load_user_mods.toBoolean()) {
modPaths << "${hytaleHome}/UserData/Mods"
}
setArgs([
'--allow-op',
'--disable-sentry',
"--assets=${assetsZip}",
"--mods=${modPaths.join(',')}"
])
}
}
tasks.register('runServer') {
dependsOn 'runServerJar'
}
// Creates a run configuration in IDEA that will run the Hytale server with
// your plugin and the default assets.
idea.project.settings.runConfigurations {
'HytaleServer'(org.jetbrains.gradle.ext.Application) {
mainClass = 'com.hypixel.hytale.Main'
moduleName = project.idea.module.name + '.main'
programParameters = createServerRunArguments(sourceSets.main.java.srcDirs.first().parentFile.absolutePath)
workingDirectory = serverRunDir.absolutePath
}
}
// Creates a launch.json file for VSCode with the same configuration
tasks.register('generateVSCodeLaunch') {
def vscodeDir = file("$projectDir/.vscode")
def launchFile = file("$vscodeDir/launch.json")
doLast {
if (!vscodeDir.exists()) {
vscodeDir.mkdirs()
}
def programParams = createServerRunArguments("\${workspaceFolder}")
def launchConfig = [
version: "0.2.0",
configurations: [
[
type: "java",
name: "HytaleServer",
request: "launch",
mainClass: "com.hypixel.hytale.Main",
args: programParams,
cwd: "\${workspaceFolder}/run"
]
]
]
]
launchFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(launchConfig))
launchFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(launchConfig))
}
}
} else {
tasks.register('generateVSCodeLaunch') {
doLast {
logger.lifecycle("Skipped VSCode launch configuration because hytale_home is not set or the install path is missing.")
}
}
}
-87
View File
@@ -1,87 +0,0 @@
plugins {
id("java-library")
id("com.gradleup.shadow") version "9.3.1"
id("run-hytale")
}
group = findProperty("pluginGroup") as String? ?: "com.example"
version = findProperty("pluginVersion") as String? ?: "1.0.0"
description = findProperty("pluginDescription") as String? ?: "A Hytale plugin template"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// Hytale Server API (provided by server at runtime)
compileOnly(files("./libs/HytaleServer.jar"))
// Common dependencies (will be bundled in JAR)
implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains:annotations:24.1.0")
// Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// Configure server testing
runHytale {
jarUrl = "./libs/HytaleServer.jar"
assetsPath = "./libs/Assets.zip"
}
tasks {
// Configure Java compilation
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release = 25
}
// Configure resource processing
processResources {
filteringCharset = Charsets.UTF_8.name()
// Replace placeholders in manifest.json
val props = mapOf(
"group" to project.group,
"version" to project.version,
"description" to project.description
)
inputs.properties(props)
filesMatching("manifest.json") {
expand(props)
}
}
// Configure ShadowJar (bundle dependencies)
shadowJar {
archiveBaseName.set(rootProject.name)
archiveClassifier.set("")
// Relocate dependencies to avoid conflicts
relocate("com.google.gson", "com.yourplugin.libs.gson")
// Minimize JAR size (removes unused classes)
minimize()
}
// Configure tests
test {
useJUnitPlatform()
}
// Make build depend on shadowJar
build {
dependsOn(shadowJar)
}
}
// Configure Java toolchain
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
+269
View File
@@ -0,0 +1,269 @@
# API Response Storage
The cache layer stores API results and controls when data should be pulled from or pushed to the control plane.
Use it when multiple plugins need shared ownership of:
- API response freshness
- stale fallback behavior
- player-persistent API data
- dirty local data that should be pushed later
Entry point:
```java
ApiDataCacheClient cache = NetworkPlugin.client().cache();
```
## Storage Targets
Every cached call explicitly chooses a target.
```java
StorageTarget.RUNTIME_SHARED
StorageTarget.PLAYER
StorageTarget.ASSET_STORE
```
`RUNTIME_SHARED`
- process-local in-memory cache
- best for server profile, config, catalog reads, active announcements
- does not survive restart
`PLAYER`
- stored on `NetworkApiDataComponent`
- requires a `PlayerCacheContext`
- persists with Hytale player component data
`ASSET_STORE`
- requires an `AssetStoreCacheAdapter`
- intended for plugins that can map API data into concrete Hytale asset types
- core-network owns freshness metadata, the adapter owns asset conversion
## Register A Cached Call
```java
CachedApiCall<ServerProfile> serverProfileCall = CachedApiCall
.builder(ServerProfile.class)
.id("server-profile")
.method("GET")
.uri("/api/v1/server/profile")
.storageTarget(StorageTarget.RUNTIME_SHARED)
.maxAge(Duration.ofSeconds(30))
.build();
cache.register(serverProfileCall);
```
Read cached or pull if stale:
```java
CompletableFuture<ServerProfile> profile =
cache.getOrPull(serverProfileCall, CacheContext.shared());
```
Force refresh:
```java
CompletableFuture<ServerProfile> profile =
cache.forcePull(serverProfileCall, CacheContext.shared());
```
Invalidate a cached value:
```java
cache.invalidate(serverProfileCall, CacheContext.shared());
```
Prewarm after an event-driven invalidation:
```java
CompletableFuture<ServerProfile> profile =
cache.prewarm(serverProfileCall, CacheContext.shared());
```
## Player Storage
Player storage requires explicit context. Core-network does not infer player identity from the API URI.
```java
PlayerCacheContext context = new PlayerCacheContext(playerId, playerRef, entityStore);
CachedApiCall<PlayerSessionProfile> sessionProfile = CachedApiCall
.builder(PlayerSessionProfile.class)
.id("player-session-profile")
.method("GET")
.uri(() -> "/api/v1/players/" + playerId + "/session-profile")
.storageTarget(StorageTarget.PLAYER)
.maxAge(Duration.ofMinutes(5))
.build();
CompletableFuture<PlayerSessionProfile> profile =
cache.getOrPull(sessionProfile, context);
```
`NetworkPlugin.setup()` registers `NetworkApiDataComponent` with the entity store registry.
## Reactive Player Hooks
`ReactivePlayerCacheHooks` gives ECS/event systems a small public surface for cache invalidation and prewarming when control-plane state changes.
Entry point:
```java
ReactivePlayerCacheHooks hooks =
NetworkPlugin.client().reactivePlayerCache();
```
Use it from player lifecycle or control-plane event handlers:
```java
PlayerCacheContext context = new PlayerCacheContext(playerId, playerRef, entityStore);
hooks.onPlayerJoin(context);
hooks.onPermissionChanged(context);
hooks.onSanctionChanged(context);
hooks.onNotificationCreated(context);
```
The hook currently covers:
- `players/{player}/moderation/sanction-snapshot`
- `players/{player}/authorization/permission-snapshot`
- `players/{player}/notifications`
`onPlayerJoin(...)` force-refreshes sanction and permission snapshots and prewarms notifications. The change-specific hooks invalidate the stale player entry and immediately pull the new value.
## Freshness Policies
Auto-refresh when older than max age:
```java
.maxAge(Duration.ofMinutes(5))
```
Never auto-refresh:
```java
.neverRefresh()
```
`forcePull(...)` always bypasses freshness checks.
## Dirty Push Policies
Update local data and mark it dirty:
```java
cache.updateLocal(call, context, value);
```
Push immediately:
```java
cache.forcePush(call, context);
```
Push after dirty TTL:
```java
CachedApiCall<MyModel> call = CachedApiCall
.builder(MyModel.class)
.id("my-writeback")
.method("GET")
.uri("/api/v1/some/read/path")
.dirtyMaxAge(Duration.ofSeconds(10))
.push(
"POST",
(context, value) -> "/api/v1/some/write/path",
(context, value) -> value
)
.build();
```
The plugin scheduler calls:
```java
cache.flushDirty();
```
Manual-only push:
```java
.manualPushOnly()
```
## Failed Sync Metadata
If refresh fails and an old value exists:
- stale value is returned
- `failedSyncCount` increments
- `lastError` is stored
- `syncState` becomes `FAILED`
If refresh fails and no value exists:
- the future completes exceptionally
Read metadata:
```java
Optional<ApiCacheMetadata> metadata =
cache.metadata(call, context);
```
Successful pull or push resets `failedSyncCount` to `0`.
## Asset Store Adapter
Asset-store-backed calls need a plugin adapter.
```java
public final class MyAssetCacheAdapter implements AssetStoreCacheAdapter {
@Override
public Optional<ApiCacheEntry> read(ApiCacheKey key, CacheContext context) {
// Read concrete Hytale asset and convert to ApiCacheEntry.
}
@Override
public void write(ApiCacheKey key, ApiCacheEntry entry, CacheContext context) {
// Convert entry payload into concrete asset and load/write through AssetStore.
}
@Override
public void remove(ApiCacheKey key, CacheContext context) {
// Remove or mark the mapped asset entry stale when invalidated.
}
}
```
Use it like:
```java
CachedApiCall<JsonObject> call = CachedApiCall
.builder(JsonObject.class)
.id("asset-backed-config")
.method("GET")
.uri("/api/v1/configs/active")
.storageTarget(StorageTarget.ASSET_STORE)
.assetStoreAdapter(new MyAssetCacheAdapter())
.maxAge(Duration.ofMinutes(10))
.build();
```
The assets domain now exposes typed generated methods that can feed asset-store-backed cache calls, including:
```java
NetworkPlugin.client().assets().getActivePack(packKey);
NetworkPlugin.client().assets().getPackVersionFiles(versionId);
```
## Batching Behavior
When `ControlPlaneClient.batchingEnabled()` is true, cache pulls and pushes use `BatchClient`.
When batching is disabled, cache operations call the HTTP transport directly.
+102
View File
@@ -0,0 +1,102 @@
# Batched API Calls
Batching lets multiple plugins enqueue control-plane calls that are sent as one HTTP request to `/api/v1/batch`.
Use batching when:
- many plugins read data at the same time
- looped tasks report state periodically
- call latency can be handled asynchronously
Do not use batching when:
- the caller needs a blocking result immediately
- the endpoint should bypass queueing for operational safety
## One-Off Calls
```java
CompletableFuture<ServerProfile> future = NetworkPlugin.client()
.batch()
.get(ProfileEndpoint.VALUE, ProfileEndpoint.VALUE.uri(), ServerProfile.class);
```
```java
CompletableFuture<JsonObject> future = NetworkPlugin.client()
.batch()
.enqueueJson("GET", "/api/v1/server/profile", null);
```
Queued calls complete when the batch loop flushes, or when code calls:
```java
NetworkPlugin.client().batch().flush();
```
## Looped Calls
```java
LoopHandle handle = NetworkPlugin.client().batch().registerLoopedCall(
LoopedApiCall.builder(ServerProfile.class)
.id("server-profile-loop")
.interval(Duration.ofSeconds(5))
.method("GET")
.uri("/api/v1/server/profile")
.onSuccess(profile -> {
// update local state
})
.onFailure(error -> {
// log or mark degraded
})
.build()
);
```
Cancel when no longer needed:
```java
handle.cancel();
```
## Batch Endpoint Contract
Request:
```json
{
"requests": [
{
"id": "generated-call-id",
"method": "GET",
"uri": "/api/v1/server/profile",
"body": null
}
]
}
```
Response:
```json
{
"responses": [
{
"id": "generated-call-id",
"status": 200,
"body": { "data": {} },
"error": null
}
]
}
```
Each item completes independently. A failed item does not fail unrelated futures.
## Defaults
- endpoint: `/api/v1/batch`
- flush interval: `250ms`
- max batch size: `100`
- batching enabled: `true`
Configure with `ControlPlaneClient.builder()`.
+105
View File
@@ -0,0 +1,105 @@
# Control Plane Client
`ControlPlaneClient` is the main public entry point for plugins.
```java
ControlPlaneClient client = NetworkPlugin.client();
```
## Public Clients
```java
client.server(); // ServerClient
client.presence(); // PresenceClient
client.players(); // PlayersClient
client.batch(); // BatchClient
client.cache(); // ApiDataCacheClient
client.reactivePlayerCache(); // ReactivePlayerCacheHooks
```
## Handwritten Domain Clients
`ServerClient`
```java
ServerProfile profile = client.server().currentProfile();
```
`PresenceClient`
```java
client.presence().register(instanceId, registrationPayload);
client.presence().heartbeat(instanceId, HeartbeatPayload.running(12, 150));
client.presence().shutdown(instanceId, "restart");
```
`PlayersClient`
```java
PlayerSessionProfile profile = client.players().sessionProfile(playerId);
List<PlayerEntitlement> entitlements = client.players().entitlementsIndex(playerId);
```
`AssetsClient`
```java
AssetPackVersion active = client.assets().getActivePack(packKey);
List<AssetPackVersionFile> files = client.assets().getPackVersionFiles(active.id());
```
## Reactive Player Cache
`reactivePlayerCache()` is intended for Hytale ECS/event handlers that know player state changed and need cache freshness now, not after a TTL expires.
```java
PlayerCacheContext context = new PlayerCacheContext(playerId, playerRef, entityStore);
client.reactivePlayerCache().onPlayerJoin(context);
client.reactivePlayerCache().onPermissionChanged(context);
client.reactivePlayerCache().onSanctionChanged(context);
client.reactivePlayerCache().onNotificationCreated(context);
```
These hooks invalidate and prewarm the player-scoped cache entries for permission snapshots, moderation sanction snapshots, and notifications.
## Error Handling
Transport errors use checked `ControlPlaneException` subclasses:
- `AuthException` for `401` and `403`
- `NotFoundException` for `404`
- `ValidationException` for `422`
- `RemoteException` for `5xx`
- `ControlPlaneException` for other transport or parsing failures
Example:
```java
try {
ServerProfile profile = client.server().currentProfile();
} catch (AuthException e) {
// token is missing, invalid, inactive, or lacks ability
} catch (ControlPlaneException e) {
// generic control-plane failure
}
```
## Endpoint Descriptors
Generated endpoint descriptors are available under:
```text
net.kewwbec.network.generated.api.v1
```
Use them for stable route metadata and URI templates. Resolve path variables with `PathBuilder`.
```java
String uri = PathBuilder.resolve(
SessionProfileEndpoint.VALUE.uri(),
"player",
playerId
);
```
Do not edit generated descriptors manually. Update routes in `core-control-plane`, then run the SDK sync command from that repo.
+78
View File
@@ -0,0 +1,78 @@
# Generated SDK
The generated SDK is a Java view of the control-plane route contract.
Generated files live under:
```text
src/main/java/net/kewwbec/network/generated/api/v1
```
These files include:
- route names
- allowed HTTP methods
- URI templates
- required abilities
- middleware metadata
- request field descriptors
- response type descriptors
- typed domain clients and DTOs for generated API resources
## Usage
Import the generated root:
```java
import net.kewwbec.network.generated.api.v1.ControlPlaneApiV1;
```
Use domain APIs to inspect route descriptors:
```java
ControlPlaneApiV1 api = ControlPlaneApiV1.INSTANCE;
String version = api.apiVersion();
int endpointCount = api.endpointCount();
```
Use route endpoint constants with handwritten clients or batching:
```java
String uri = PathBuilder.resolve(
SessionProfileEndpoint.VALUE.uri(),
"player",
playerId
);
```
Use generated domain clients for direct typed calls:
```java
PlayerPermissionSnapshot permissions =
NetworkPlugin.client().players().authorizationPermissionSnapshot(playerId);
AssetPackVersion activePack =
NetworkPlugin.client().assets().getActivePack(packKey);
```
Some generated clients include hand-friendly aliases for route-shaped names. For example, the assets domain exposes `getActivePack(...)` and `getPackVersionFiles(...)` over the generated active-pack and version-files endpoints.
## Regeneration
The source of truth is `../core-control-plane`.
Run from `core-control-plane`:
```bash
php artisan control-plane:sync-network-sdk --dry-run
php artisan control-plane:sync-network-sdk
```
Review generated churn carefully before committing. Broad generated diffs can happen if `core-control-plane` has unrelated route changes.
## Rules
- Do not manually patch generated Java.
- Fix Laravel routes, requests, resources, or the generator upstream.
- Re-run the sync after route changes.
- Keep handwritten Java under `net.kewwbec.network`, outside `generated`.
+134
View File
@@ -0,0 +1,134 @@
# Getting Started
## Build
From `core-network`:
```bat
gradlew.bat compileJava
gradlew.bat test
gradlew.bat shadowJar
```
`compileJava` and `test` are the fastest checks for SDK/runtime changes. `shadowJar` builds the plugin jar.
## Configure The Shared Client
`core-network` auto-configures `NetworkPlugin.client()` during plugin setup when startup configuration is present.
Recommended production setup uses environment variables:
```powershell
$env:KWB_CONTROL_PLANE_BASE_URL = "https://control-plane.internal"
$env:KWB_CONTROL_PLANE_TOKEN = "server-issued-token"
```
Supported optional environment variables:
```text
KWB_CONTROL_PLANE_BATCHING_ENABLED=true
KWB_CONTROL_PLANE_BATCH_FLUSH_INTERVAL_MS=250
KWB_CONTROL_PLANE_BATCH_MAX_SIZE=100
KWB_CONTROL_PLANE_BATCH_ENDPOINT=/api/v1/batch
```
Launcher-controlled startup can use JVM properties:
```text
-Dkweebec.controlPlane.baseUrl=https://control-plane.internal
-Dkweebec.controlPlane.token=server-issued-token
-Dkweebec.controlPlane.batching.enabled=true
-Dkweebec.controlPlane.batch.flushIntervalMs=250
-Dkweebec.controlPlane.batch.maxSize=100
-Dkweebec.controlPlane.batch.endpoint=/api/v1/batch
```
For small local deployments, `core-network` creates `control-plane.json` in the plugin data directory. Fill in the placeholders:
```json
{
"BaseUrl": "https://control-plane.internal",
"Token": "local-dev-token",
"BatchingEnabled": true,
"BatchFlushIntervalMs": 250,
"MaxBatchSize": 100,
"BatchEndpoint": "/api/v1/batch"
}
```
Precedence is environment variables, then JVM properties, then `control-plane.json`.
Manual setup still works and overrides auto-config:
```java
NetworkPlugin.configure(
"https://control-plane.internal",
System.getenv("KWB_CONTROL_PLANE_TOKEN")
);
```
Dependent plugins then use:
```java
ControlPlaneClient client = NetworkPlugin.client();
```
You can inspect the source that configured the shared client:
```java
NetworkPlugin.configurationSource();
```
## Dependency Setup
Dependent jars should declare `core-network` as a plugin dependency in their manifest:
```json
{
"Dependencies": {
"core-network": "*"
}
}
```
## Common Direct Calls
```java
ServerProfile profile = NetworkPlugin.client()
.server()
.currentProfile();
```
```java
PlayerSessionProfile profile = NetworkPlugin.client()
.players()
.getSessionProfile(playerId);
```
```java
NetworkPlugin.client()
.presence()
.heartbeat(instanceId, HeartbeatPayload.running(currentPlayers, maxPlayers));
```
## Runtime Scheduling
`NetworkPlugin` starts a scheduled loop when the Hytale plugin starts. The loop:
- flushes due batched API calls
- flushes dirty cached API entries
Default batch cadence is `250ms`.
Builder overrides:
```java
ControlPlaneClient client = ControlPlaneClient.builder()
.baseUrl("https://control-plane.internal")
.token(token)
.batchingEnabled(true)
.batchFlushInterval(Duration.ofMillis(250))
.maxBatchSize(100)
.batchEndpoint("/api/v1/batch")
.build();
```
+44
View File
@@ -0,0 +1,44 @@
# Core Network Wiki
`core-network` is the Hytale plugin-side runtime for Kweebec Network control-plane access.
It provides:
- a shared `ControlPlaneClient`
- handwritten convenience clients for common server/player operations
- generated API endpoint descriptors from `core-control-plane`
- batched HTTP calls through `/api/v1/batch`
- API response storage policies for runtime, player, and asset-store-backed data
- reactive player cache hooks for ECS/event-driven invalidation and prewarming
## Pages
- [Getting Started](Getting-Started.md)
- [Control Plane Client](Control-Plane-Client.md)
- [Batched API Calls](Batched-API-Calls.md)
- [API Response Storage](API-Response-Storage.md)
- [Generated SDK](Generated-SDK.md)
- [Troubleshooting](Troubleshooting.md)
## Package Map
```text
net.kewwbec.network
ControlPlaneClient.java shared client entry point
NetworkPlugin.java Hytale plugin entry point and scheduler hook
client/ handwritten domain clients
http/ low-level JSON HTTP transport
batch/ one-off and looped batched calls
cache/ cached API data and storage policies
generated/api/v1/ generated endpoint descriptors
models/ handwritten response/request models
```
## Design Rules
- Do not edit `net.kewwbec.network.generated` manually.
- Add handwritten runtime behavior outside `generated`.
- Treat `core-control-plane` as the API owner.
- Use batching for repeated or concurrent calls that can tolerate async completion.
- Use cache policies when plugin code needs shared ownership of API freshness or push timing.
- Use `reactivePlayerCache()` from ECS/event handlers when player cache state must react immediately to permission, sanction, notification, or join events.
+116
View File
@@ -0,0 +1,116 @@
# Troubleshooting
## `NetworkPlugin has not been configured`
No startup source produced both a control-plane base URL and token before another plugin requested `NetworkPlugin.client()`.
Fix with one of:
- environment variables: `KWB_CONTROL_PLANE_BASE_URL` and `KWB_CONTROL_PLANE_TOKEN`
- JVM properties: `kweebec.controlPlane.baseUrl` and `kweebec.controlPlane.token`
- local plugin config: `control-plane.json`
- explicit startup call: `NetworkPlugin.configure(baseUrl, token)`
Check which source configured the client:
```java
NetworkPlugin.configurationSource();
```
`Optional.empty()` means no source configured the shared client.
## Startup Config Precedence
Startup values are resolved in this order:
1. environment variables
2. JVM system properties
3. `control-plane.json`
Environment variables and JVM properties override only the fields they set. Missing fields can still come from a lower-priority source.
## Config File Token Warning
`control-plane.json` is a short-term local deployment fallback. Prefer environment variables for production or control-plane-provisioned servers.
JVM `-D` properties are supported, but some host tooling can expose process arguments. Avoid logging launcher commands that contain tokens.
## Auth Failures
`AuthException` maps to HTTP `401` or `403`.
Common causes:
- missing token
- expired or invalid token
- inactive service account
- token missing required ability
- server-instance token trying to access a different instance route
## Batch Futures Never Complete
Likely causes:
- batching scheduler has not started
- `NetworkPlugin` is not loaded as the plugin main class
- code enqueued calls but never called `batch().flush()` in tests
- control plane does not expose `/api/v1/batch`
In tests, call:
```java
client.batch().flush();
```
## Cached Reads Keep Returning Stale Data
This can be expected behavior. If a refresh fails and an old value exists, cache APIs return the stale value and increment failed sync metadata.
Check:
```java
cache.metadata(call, context)
```
Look at:
- `failedSyncCount`
- `lastError`
- `syncState`
## `PLAYER cache calls require PlayerCacheContext`
Player storage is explicit. Use:
```java
new PlayerCacheContext(playerId, playerRef, entityStore)
```
Core-network will not infer player identity from `/players/{player}` URIs.
## `NetworkApiDataComponent has not been registered`
The player cache component is registered in `NetworkPlugin.setup()`.
This error usually means:
- tests are using player storage without plugin setup
- `NetworkPlugin` is not the active Hytale plugin main class
- code is accessing player cache before component registration
## Asset Store Cache Does Nothing
`ASSET_STORE` storage requires a plugin-provided `AssetStoreCacheAdapter`.
Core-network does not create a generic Hytale asset store because Hytale assets require concrete asset classes, codecs, keys, and load/write behavior.
## Manifest Rewrites During Gradle Tasks
`processResources` depends on `updatePluginManifest`, which rewrites `src/main/resources/manifest.json` from Gradle properties.
If the manifest changes unexpectedly after tests/builds, check:
- `includes_pack` in `gradle.properties`
- `version` in `gradle.properties`
Avoid committing unrelated manifest churn.
+8 -3
View File
@@ -1,9 +1,9 @@
# The current version of your project. Please use semantic versioning!
version=0.0.2
version=0.5.3
# 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.
@@ -19,6 +19,11 @@ includes_pack=true
# official launcher.
patchline=release
# The exact Hytale build to compile against. Use the build string from the
# launcher (format YYYY.MM.DD-<hash>) so Gradle pulls the matching server jar
# for your selected patchline.
hytale_build=0.5.3
# Determines if the development server should also load mods from the user's
# standard mods folder. This lets you test mods by installing them where a
# normal player would, instead of adding them as dependencies or adding them
@@ -29,4 +34,4 @@ load_user_mods=false
# manually. You may also want to use a custom path if you are building in
# a non-standard environment like a build server. The home path should
# the folder that contains the install and UserData folder.
# hytale_home=./test-file
# hytale_home=./test-file
+1 -1
View File
@@ -1 +1 @@
rootProject.name = 'ExamplePlugin'
rootProject.name = 'core-network'
@@ -0,0 +1,201 @@
package net.kewwbec.network;
import net.kewwbec.network.auth.ApiToken;
import net.kewwbec.network.batch.BatchClient;
import net.kewwbec.network.cache.ApiDataCacheClient;
import net.kewwbec.network.cache.ReactivePlayerCacheHooks;
import net.kewwbec.network.generated.api.v1.announcements.AnnouncementsClient;
import net.kewwbec.network.generated.api.v1.assets.AssetsClient;
import net.kewwbec.network.generated.api.v1.audit.AuditClient;
import net.kewwbec.network.generated.api.v1.configs.ConfigsClient;
import net.kewwbec.network.generated.api.v1.entitlements.EntitlementsClient;
import net.kewwbec.network.generated.api.v1.gamepermissions.GamePermissionsClient;
import net.kewwbec.network.generated.api.v1.gameplay.GameplayClient;
import net.kewwbec.network.generated.api.v1.health.HealthClient;
import net.kewwbec.network.generated.api.v1.hub.HubClient;
import net.kewwbec.network.generated.api.v1.infrastructure.InfrastructureClient;
import net.kewwbec.network.generated.api.v1.integrations.IntegrationsClient;
import net.kewwbec.network.generated.api.v1.moderation.ModerationClient;
import net.kewwbec.network.generated.api.v1.players.PlayersClient;
import net.kewwbec.network.generated.api.v1.plugins.PluginsClient;
import net.kewwbec.network.generated.api.v1.progression.ProgressionClient;
import net.kewwbec.network.generated.api.v1.rewards.RewardsClient;
import net.kewwbec.network.generated.api.v1.server.ServerClient;
import net.kewwbec.network.generated.api.v1.serviceaccounts.ServiceAccountsClient;
import net.kewwbec.network.generated.api.v1.social.SocialClient;
import net.kewwbec.network.generated.api.v1.support.SupportClient;
import net.kewwbec.network.generated.api.v1.worlds.WorldsClient;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import java.time.Duration;
public final class ControlPlaneClient {
private final ControlPlaneHttpClient http;
private final AnnouncementsClient announcements;
private final AssetsClient assets;
private final AuditClient audit;
private final ConfigsClient configs;
private final EntitlementsClient entitlements;
private final GamePermissionsClient gamePermissions;
private final GameplayClient gameplay;
private final HealthClient health;
private final HubClient hub;
private final InfrastructureClient infrastructure;
private final IntegrationsClient integrations;
private final ModerationClient moderation;
private final PlayersClient players;
private final PluginsClient plugins;
private final ProgressionClient progression;
private final RewardsClient rewards;
private final ServerClient server;
private final ServiceAccountsClient serviceAccounts;
private final SocialClient social;
private final SupportClient support;
private final WorldsClient worlds;
private final BatchClient batch;
private final ApiDataCacheClient cache;
private final ReactivePlayerCacheHooks reactivePlayerCache;
private final boolean batchingEnabled;
private final Duration batchFlushInterval;
private final int maxBatchSize;
private final String batchEndpoint;
private ControlPlaneClient(Builder builder) {
this.http = new ControlPlaneHttpClient(builder.baseUrl, builder.token, builder.connectTimeout);
this.announcements = new AnnouncementsClient(http);
this.assets = new AssetsClient(http);
this.audit = new AuditClient(http);
this.configs = new ConfigsClient(http);
this.entitlements = new EntitlementsClient(http);
this.gamePermissions = new GamePermissionsClient(http);
this.gameplay = new GameplayClient(http);
this.health = new HealthClient(http);
this.hub = new HubClient(http);
this.infrastructure = new InfrastructureClient(http);
this.integrations = new IntegrationsClient(http);
this.moderation = new ModerationClient(http);
this.players = new PlayersClient(http);
this.plugins = new PluginsClient(http);
this.progression = new ProgressionClient(http);
this.rewards = new RewardsClient(http);
this.server = new ServerClient(http);
this.serviceAccounts = new ServiceAccountsClient(http);
this.social = new SocialClient(http);
this.support = new SupportClient(http);
this.worlds = new WorldsClient(http);
this.batch = new BatchClient(http, builder.batchEndpoint, builder.maxBatchSize);
this.batchingEnabled = builder.batchingEnabled;
this.batchFlushInterval = builder.batchFlushInterval;
this.maxBatchSize = builder.maxBatchSize;
this.batchEndpoint = builder.batchEndpoint;
this.cache = new ApiDataCacheClient(http, batch, this::batchingEnabled);
this.reactivePlayerCache = new ReactivePlayerCacheHooks(cache);
}
public static Builder builder() {
return new Builder();
}
public AnnouncementsClient announcements() { return announcements; }
public AssetsClient assets() { return assets; }
public AuditClient audit() { return audit; }
public ConfigsClient configs() { return configs; }
public EntitlementsClient entitlements() { return entitlements; }
public GamePermissionsClient gamePermissions() { return gamePermissions; }
public GameplayClient gameplay() { return gameplay; }
public HealthClient health() { return health; }
public HubClient hub() { return hub; }
public InfrastructureClient infrastructure() { return infrastructure; }
public IntegrationsClient integrations() { return integrations; }
public ModerationClient moderation() { return moderation; }
public PlayersClient players() { return players; }
public PluginsClient plugins() { return plugins; }
public ProgressionClient progression() { return progression; }
public RewardsClient rewards() { return rewards; }
public ServerClient server() { return server; }
public ServiceAccountsClient serviceAccounts() { return serviceAccounts; }
public SocialClient social() { return social; }
public SupportClient support() { return support; }
public WorldsClient worlds() { return worlds; }
public BatchClient batch() { return batch; }
public ApiDataCacheClient cache() { return cache; }
public ReactivePlayerCacheHooks reactivePlayerCache() { return reactivePlayerCache; }
public boolean batchingEnabled() { return batchingEnabled; }
public Duration batchFlushInterval() { return batchFlushInterval; }
public int maxBatchSize() { return maxBatchSize; }
public String batchEndpoint() { return batchEndpoint; }
public static final class Builder {
private String baseUrl;
private ApiToken token;
private Duration connectTimeout = Duration.ofSeconds(5);
private boolean batchingEnabled = true;
private Duration batchFlushInterval = Duration.ofMillis(250);
private int maxBatchSize = 100;
private String batchEndpoint = "/api/v1/batch";
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 Builder batchingEnabled(boolean batchingEnabled) {
this.batchingEnabled = batchingEnabled;
return this;
}
public Builder batchFlushInterval(Duration batchFlushInterval) {
this.batchFlushInterval = batchFlushInterval;
return this;
}
public Builder maxBatchSize(int maxBatchSize) {
this.maxBatchSize = maxBatchSize;
return this;
}
public Builder batchEndpoint(String batchEndpoint) {
this.batchEndpoint = batchEndpoint;
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");
}
if (connectTimeout == null || connectTimeout.isNegative() || connectTimeout.isZero()) {
throw new IllegalStateException("connectTimeout must be positive");
}
if (batchFlushInterval == null || batchFlushInterval.isNegative() || batchFlushInterval.isZero()) {
throw new IllegalStateException("batchFlushInterval must be positive");
}
if (maxBatchSize <= 0) {
throw new IllegalStateException("maxBatchSize must be positive");
}
if (batchEndpoint == null || batchEndpoint.isBlank()) {
throw new IllegalStateException("batchEndpoint must be set");
}
return new ControlPlaneClient(this);
}
}
}
@@ -0,0 +1,240 @@
package net.kewwbec.network;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.util.Config;
import net.kewwbec.network.cache.NetworkApiDataComponent;
import net.kewwbec.network.config.ControlPlaneConfigurationSource;
import net.kewwbec.network.config.ControlPlaneRuntimeConfig;
import net.kewwbec.network.config.ControlPlaneStartupConfigResolver;
import net.kewwbec.network.config.ResolvedControlPlaneStartupConfig;
import net.kewwbec.network.errors.ControlPlaneException;
import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
/**
* Plugin entry point for core-network.
*
* The shared client is configured from startup environment variables, JVM
* properties, control-plane.json, or an explicit {@link #configure(String, String)}
* call before dependent plugins use {@link #client()}.
*
* Dependent plugins declare this plugin in their manifest Dependencies block:
* "Dependencies": { "core-network": "*" }
*/
public final class NetworkPlugin extends JavaPlugin {
private static volatile ControlPlaneClient instance;
private static volatile ControlPlaneConfigurationSource configurationSource;
private static volatile boolean runtimeStarted;
private static volatile ScheduledFuture<?> batchTask;
private static final Object CONFIG_LOCK = new Object();
private static final Object BATCH_LOCK = new Object();
private final Config<ControlPlaneRuntimeConfig> controlPlaneConfig;
public NetworkPlugin(@Nonnull JavaPluginInit init) {
super(init);
this.controlPlaneConfig = withConfig("control-plane", ControlPlaneRuntimeConfig.CODEC);
}
@Override
protected void setup() {
NetworkApiDataComponent.setComponentType(getEntityStoreRegistry().registerComponent(
NetworkApiDataComponent.class,
"NetworkApiData",
NetworkApiDataComponent.CODEC
));
loadAndApplyStartupConfig();
}
@Override
protected void start() {
runtimeStarted = true;
startBatching();
}
/**
* 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) {
configure(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) {
configure(client, ControlPlaneConfigurationSource.MANUAL);
}
private static void configure(ControlPlaneClient client, ControlPlaneConfigurationSource source) {
if (client == null) {
throw new IllegalArgumentException("client must not be null");
}
synchronized (CONFIG_LOCK) {
instance = client;
configurationSource = source;
}
if (runtimeStarted) {
restartBatching();
}
}
static boolean configureFromStartup(ResolvedControlPlaneStartupConfig resolved) {
if (resolved == null) {
throw new IllegalArgumentException("resolved must not be null");
}
ControlPlaneClient client = resolved.settings().buildClient();
synchronized (CONFIG_LOCK) {
if (instance != null) {
return false;
}
instance = client;
configurationSource = resolved.source();
}
if (runtimeStarted) {
startBatching();
}
return true;
}
/**
* 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. " +
"Set KWB_CONTROL_PLANE_BASE_URL and KWB_CONTROL_PLANE_TOKEN, JVM properties, " +
"control-plane.json, or 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;
}
public static Optional<ControlPlaneConfigurationSource> configurationSource() {
return Optional.ofNullable(configurationSource);
}
public static void startBatching() {
startBatching(HytaleServer.SCHEDULED_EXECUTOR);
}
public static void startBatching(ScheduledExecutorService scheduler) {
ControlPlaneClient c = instance;
if (c == null || !c.batchingEnabled()) {
return;
}
synchronized (BATCH_LOCK) {
if (batchTask != null && !batchTask.isCancelled() && !batchTask.isDone()) {
return;
}
long intervalMillis = Math.max(1L, c.batchFlushInterval().toMillis());
batchTask = scheduler.scheduleAtFixedRate(() -> {
try {
ControlPlaneClient current = instance;
if (current != null && current.batchingEnabled()) {
current.batch().flushDue();
current.cache().flushDirty();
}
} catch (Exception ignored) {
}
}, intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
}
}
private static void restartBatching() {
cancelBatchingTask();
startBatching();
}
private static void cancelBatchingTask() {
synchronized (BATCH_LOCK) {
if (batchTask != null) {
batchTask.cancel(false);
batchTask = null;
}
}
}
@Override
public void shutdown() {
runtimeStarted = false;
cancelBatchingTask();
ControlPlaneClient c = instance;
if (c != null) {
try {
c.batch().flush();
} catch (RuntimeException ignored) {
c.batch().cancelPending(new ControlPlaneException("Batch client shut down before pending calls were flushed"));
}
}
}
private void loadAndApplyStartupConfig() {
ControlPlaneRuntimeConfig runtimeConfig;
try {
runtimeConfig = controlPlaneConfig.load().join();
} catch (RuntimeException e) {
getLogger().at(Level.WARNING).withCause(e).log(
"Failed to load core-network control-plane config; trying environment and JVM properties only"
);
runtimeConfig = new ControlPlaneRuntimeConfig();
}
applyStartupConfig(runtimeConfig);
try {
controlPlaneConfig.save().join();
} catch (RuntimeException e) {
getLogger().at(Level.WARNING).withCause(e).log("Failed to save core-network control-plane config placeholder");
}
}
private void applyStartupConfig(ControlPlaneRuntimeConfig runtimeConfig) {
if (isConfigured()) {
return;
}
Optional<ResolvedControlPlaneStartupConfig> resolved;
try {
resolved = new ControlPlaneStartupConfigResolver().resolve(runtimeConfig.toStartupSettings());
} catch (RuntimeException e) {
getLogger().at(Level.WARNING).withCause(e).log(
"Core-network control-plane startup configuration is invalid"
);
return;
}
if (resolved.isEmpty()) {
getLogger().at(Level.WARNING).log(
"Core-network control plane is not configured; set KWB_CONTROL_PLANE_BASE_URL and KWB_CONTROL_PLANE_TOKEN, JVM properties, or control-plane.json"
);
return;
}
if (configureFromStartup(resolved.get())) {
getLogger().at(Level.INFO).log("Configured core-network control plane from %s", resolved.get().source());
}
}
}
@@ -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,233 @@
package net.kewwbec.network.batch;
import com.google.gson.JsonObject;
import net.kewwbec.network.errors.ControlPlaneException;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public final class BatchClient {
private final ControlPlaneHttpClient http;
private final String batchEndpoint;
private final int maxBatchSize;
private final ConcurrentLinkedQueue<PendingCall<?>> pendingCalls = new ConcurrentLinkedQueue<>();
private final Map<String, LoopRegistration<?>> loopedCalls = new ConcurrentHashMap<>();
private final AtomicBoolean flushing = new AtomicBoolean(false);
public BatchClient(ControlPlaneHttpClient http, String batchEndpoint, int maxBatchSize) {
this.http = Objects.requireNonNull(http, "http must not be null");
this.batchEndpoint = requireApiUri(batchEndpoint);
if (maxBatchSize <= 0) {
throw new IllegalArgumentException("maxBatchSize must be positive");
}
this.maxBatchSize = maxBatchSize;
}
public <T> CompletableFuture<T> enqueue(
ApiEndpoint endpoint,
String resolvedUri,
String method,
Object body,
Class<T> responseType
) {
String normalizedMethod = normalizeMethod(method);
if (endpoint != null && !endpoint.methods().contains(normalizedMethod)) {
throw new IllegalArgumentException("Endpoint " + endpoint.name() + " does not allow " + normalizedMethod);
}
PendingCall<T> call = new PendingCall<>(
UUID.randomUUID().toString(),
normalizedMethod,
requireApiUri(resolvedUri),
body,
Objects.requireNonNull(responseType, "responseType must not be null")
);
pendingCalls.add(call);
return call.future;
}
public <T> CompletableFuture<T> get(ApiEndpoint endpoint, String resolvedUri, Class<T> responseType) {
return enqueue(endpoint, resolvedUri, "GET", null, responseType);
}
public <T> CompletableFuture<T> post(ApiEndpoint endpoint, String resolvedUri, Object body, Class<T> responseType) {
return enqueue(endpoint, resolvedUri, "POST", body, responseType);
}
public CompletableFuture<JsonObject> enqueueJson(String method, String resolvedUri, Object body) {
return enqueue(null, resolvedUri, method, body, JsonObject.class);
}
public <T> LoopHandle registerLoopedCall(LoopedApiCall<T> call) {
LoopRegistration<T> registration = new LoopRegistration<>(Objects.requireNonNull(call, "call must not be null"));
LoopRegistration<?> previous = loopedCalls.putIfAbsent(call.id(), registration);
if (previous != null) {
throw new IllegalArgumentException("Looped API call already registered: " + call.id());
}
return registration;
}
public void flushDue() {
Instant now = Instant.now();
for (LoopRegistration<?> registration : loopedCalls.values()) {
registration.enqueueIfDue(this, now);
}
flush();
}
public void flush() {
if (!flushing.compareAndSet(false, true)) {
return;
}
try {
List<PendingCall<?>> batch = drainBatch();
while (!batch.isEmpty()) {
sendBatch(batch);
batch = drainBatch();
}
} finally {
flushing.set(false);
}
}
public void cancelPending(Throwable cause) {
PendingCall<?> call;
while ((call = pendingCalls.poll()) != null) {
call.completeExceptionally(cause);
}
}
private List<PendingCall<?>> drainBatch() {
List<PendingCall<?>> batch = new ArrayList<>(maxBatchSize);
PendingCall<?> call;
while (batch.size() < maxBatchSize && (call = pendingCalls.poll()) != null) {
batch.add(call);
}
return batch;
}
private void sendBatch(List<PendingCall<?>> batch) {
try {
http.postBatch(batchEndpoint, batch);
} catch (ControlPlaneException e) {
for (PendingCall<?> call : batch) {
call.completeExceptionally(e);
}
}
}
private static String normalizeMethod(String method) {
if (method == null || method.isBlank()) {
throw new IllegalArgumentException("method must not be blank");
}
return method.toUpperCase(Locale.ROOT);
}
private static String requireApiUri(String uri) {
if (uri == null || uri.isBlank()) {
throw new IllegalArgumentException("uri must not be blank");
}
if (!uri.startsWith("/api/v1/") && !uri.equals("/api/v1")) {
throw new IllegalArgumentException("uri must be a relative /api/v1 path");
}
if (uri.contains("://")) {
throw new IllegalArgumentException("uri must not be absolute");
}
return uri;
}
public static final class PendingCall<T> {
private final String id;
private final String method;
private final String uri;
private final Object body;
private final Class<T> responseType;
private final CompletableFuture<T> future = new CompletableFuture<>();
private PendingCall(String id, String method, String uri, Object body, Class<T> responseType) {
this.id = id;
this.method = method;
this.uri = uri;
this.body = body;
this.responseType = responseType;
}
public String id() { return id; }
public String method() { return method; }
public String uri() { return uri; }
public Object body() { return body; }
public Class<T> responseType() { return responseType; }
public void complete(T value) {
future.complete(value);
}
public void completeExceptionally(Throwable cause) {
future.completeExceptionally(cause);
}
}
private final class LoopRegistration<T> implements LoopHandle {
private final LoopedApiCall<T> call;
private final AtomicBoolean cancelled = new AtomicBoolean(false);
private volatile Instant nextRunAt = Instant.EPOCH;
private LoopRegistration(LoopedApiCall<T> call) {
this.call = call;
}
private void enqueueIfDue(BatchClient batchClient, Instant now) {
if (cancelled.get() || now.isBefore(nextRunAt)) {
return;
}
nextRunAt = now.plus(call.interval());
try {
CompletableFuture<T> future = batchClient.enqueue(
call.endpoint(),
call.uri(),
call.method(),
call.body(),
call.responseType()
);
if (call.onSuccess() != null) {
future.thenAccept(call.onSuccess());
}
if (call.onFailure() != null) {
future.exceptionally(error -> {
call.onFailure().accept(error);
return null;
});
}
} catch (RuntimeException e) {
if (call.onFailure() != null) {
call.onFailure().accept(e);
} else {
throw e;
}
}
}
@Override
public void cancel() {
if (cancelled.compareAndSet(false, true)) {
loopedCalls.remove(call.id(), this);
}
}
@Override
public boolean isCancelled() {
return cancelled.get();
}
}
}
@@ -0,0 +1,7 @@
package net.kewwbec.network.batch;
public interface LoopHandle {
void cancel();
boolean isCancelled();
}
@@ -0,0 +1,127 @@
package net.kewwbec.network.batch;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import java.time.Duration;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
public final class LoopedApiCall<T> {
private final String id;
private final Duration interval;
private final ApiEndpoint endpoint;
private final String method;
private final Supplier<String> uriSupplier;
private final Supplier<?> bodySupplier;
private final Class<T> responseType;
private final Consumer<T> onSuccess;
private final Consumer<Throwable> onFailure;
private LoopedApiCall(Builder<T> builder) {
this.id = requireNonBlank(builder.id, "id");
this.interval = Objects.requireNonNull(builder.interval, "interval must not be null");
if (this.interval.isZero() || this.interval.isNegative()) {
throw new IllegalArgumentException("interval must be positive");
}
this.endpoint = builder.endpoint;
this.method = requireNonBlank(builder.method, "method").toUpperCase(Locale.ROOT);
this.uriSupplier = Objects.requireNonNull(builder.uriSupplier, "uriSupplier must not be null");
this.bodySupplier = builder.bodySupplier;
this.responseType = Objects.requireNonNull(builder.responseType, "responseType must not be null");
this.onSuccess = builder.onSuccess;
this.onFailure = builder.onFailure;
}
public static <T> Builder<T> builder(Class<T> responseType) {
return new Builder<>(responseType);
}
public String id() { return id; }
public Duration interval() { return interval; }
public ApiEndpoint endpoint() { return endpoint; }
public String method() { return method; }
public String uri() { return uriSupplier.get(); }
public Object body() { return bodySupplier != null ? bodySupplier.get() : null; }
public Class<T> responseType() { return responseType; }
public Consumer<T> onSuccess() { return onSuccess; }
public Consumer<Throwable> onFailure() { return onFailure; }
private static String requireNonBlank(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
return value;
}
public static final class Builder<T> {
private final Class<T> responseType;
private String id;
private Duration interval;
private ApiEndpoint endpoint;
private String method;
private Supplier<String> uriSupplier;
private Supplier<?> bodySupplier;
private Consumer<T> onSuccess;
private Consumer<Throwable> onFailure;
private Builder(Class<T> responseType) {
this.responseType = responseType;
}
public Builder<T> id(String id) {
this.id = id;
return this;
}
public Builder<T> interval(Duration interval) {
this.interval = interval;
return this;
}
public Builder<T> endpoint(ApiEndpoint endpoint) {
this.endpoint = endpoint;
return this;
}
public Builder<T> method(String method) {
this.method = method;
return this;
}
public Builder<T> uri(String uri) {
this.uriSupplier = () -> uri;
return this;
}
public Builder<T> uri(Supplier<String> uriSupplier) {
this.uriSupplier = uriSupplier;
return this;
}
public Builder<T> body(Object body) {
this.bodySupplier = () -> body;
return this;
}
public Builder<T> body(Supplier<?> bodySupplier) {
this.bodySupplier = bodySupplier;
return this;
}
public Builder<T> onSuccess(Consumer<T> onSuccess) {
this.onSuccess = onSuccess;
return this;
}
public Builder<T> onFailure(Consumer<Throwable> onFailure) {
this.onFailure = onFailure;
return this;
}
public LoopedApiCall<T> build() {
return new LoopedApiCall<>(this);
}
}
}
@@ -0,0 +1,107 @@
package net.kewwbec.network.cache;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.time.Instant;
public final class ApiCacheEntry {
private JsonElement payload;
private String valueClassName;
private Instant fetchedAt;
private Instant dirtyAt;
private int failedSyncCount;
private String lastError;
private SyncState syncState = SyncState.CLEAN;
public ApiCacheEntry(JsonElement payload, String valueClassName, Instant fetchedAt) {
this.payload = payload;
this.valueClassName = valueClassName;
this.fetchedAt = fetchedAt;
}
public JsonElement payload() { return payload; }
public String valueClassName() { return valueClassName; }
public Instant fetchedAt() { return fetchedAt; }
public Instant dirtyAt() { return dirtyAt; }
public int failedSyncCount() { return failedSyncCount; }
public String lastError() { return lastError; }
public SyncState syncState() { return syncState; }
public boolean isFresh(PullPolicy policy, Instant now) {
if (payload == null || fetchedAt == null) {
return false;
}
return policy.maxAge()
.map(maxAge -> !fetchedAt.plus(maxAge).isBefore(now))
.orElse(true);
}
public boolean shouldPush(PushPolicy policy, Instant now) {
if (dirtyAt == null || syncState != SyncState.DIRTY) {
return false;
}
return policy.dirtyMaxAge()
.map(maxAge -> !dirtyAt.plus(maxAge).isAfter(now))
.orElse(false);
}
public void replacePulled(JsonElement payload, String valueClassName, Instant fetchedAt) {
this.payload = payload;
this.valueClassName = valueClassName;
this.fetchedAt = fetchedAt;
this.failedSyncCount = 0;
this.lastError = null;
this.syncState = SyncState.CLEAN;
}
public void replaceLocal(JsonElement payload, String valueClassName, Instant dirtyAt) {
this.payload = payload;
this.valueClassName = valueClassName;
this.dirtyAt = dirtyAt;
this.syncState = SyncState.DIRTY;
}
public void markPushed(Instant fetchedAt) {
this.fetchedAt = fetchedAt;
this.dirtyAt = null;
this.failedSyncCount = 0;
this.lastError = null;
this.syncState = SyncState.CLEAN;
}
public void markFailed(Throwable error) {
this.failedSyncCount++;
this.lastError = error.getMessage();
this.syncState = SyncState.FAILED;
}
public ApiCacheMetadata metadata() {
return new ApiCacheMetadata(fetchedAt, dirtyAt, failedSyncCount, lastError, syncState);
}
public JsonObject toJsonObject() {
JsonObject obj = new JsonObject();
obj.add("payload", payload);
obj.addProperty("valueClassName", valueClassName);
if (fetchedAt != null) obj.addProperty("fetchedAt", fetchedAt.toString());
if (dirtyAt != null) obj.addProperty("dirtyAt", dirtyAt.toString());
obj.addProperty("failedSyncCount", failedSyncCount);
if (lastError != null) obj.addProperty("lastError", lastError);
obj.addProperty("syncState", syncState.name());
return obj;
}
public static ApiCacheEntry fromJsonObject(JsonObject obj) {
ApiCacheEntry entry = new ApiCacheEntry(
obj.get("payload"),
obj.has("valueClassName") ? obj.get("valueClassName").getAsString() : JsonObject.class.getName(),
obj.has("fetchedAt") ? Instant.parse(obj.get("fetchedAt").getAsString()) : null
);
entry.dirtyAt = obj.has("dirtyAt") ? Instant.parse(obj.get("dirtyAt").getAsString()) : null;
entry.failedSyncCount = obj.has("failedSyncCount") ? obj.get("failedSyncCount").getAsInt() : 0;
entry.lastError = obj.has("lastError") ? obj.get("lastError").getAsString() : null;
entry.syncState = obj.has("syncState") ? SyncState.valueOf(obj.get("syncState").getAsString()) : SyncState.CLEAN;
return entry;
}
}
@@ -0,0 +1,20 @@
package net.kewwbec.network.cache;
import java.util.Objects;
public record ApiCacheKey(String callId, String contextKey) {
public ApiCacheKey {
if (callId == null || callId.isBlank()) {
throw new IllegalArgumentException("callId must not be blank");
}
contextKey = contextKey == null || contextKey.isBlank() ? "shared" : contextKey;
}
public String encoded() {
return callId + "|" + contextKey;
}
public static ApiCacheKey of(String callId, String contextKey) {
return new ApiCacheKey(callId, Objects.requireNonNullElse(contextKey, "shared"));
}
}
@@ -0,0 +1,15 @@
package net.kewwbec.network.cache;
import java.time.Instant;
public record ApiCacheMetadata(
Instant fetchedAt,
Instant dirtyAt,
int failedSyncCount,
String lastError,
SyncState syncState
) {
public boolean hasValue() {
return fetchedAt != null;
}
}
@@ -0,0 +1,14 @@
package net.kewwbec.network.cache;
import java.util.Collection;
import java.util.Optional;
interface ApiCacheStore {
Optional<ApiCacheEntry> get(ApiCacheKey key, CacheContext context);
void put(ApiCacheKey key, ApiCacheEntry entry, CacheContext context);
void remove(ApiCacheKey key, CacheContext context);
Collection<StoredCacheEntry> entries();
}
@@ -0,0 +1,252 @@
package net.kewwbec.network.cache;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import net.kewwbec.network.batch.BatchClient;
import net.kewwbec.network.errors.ControlPlaneException;
import net.kewwbec.network.http.ControlPlaneHttpClient;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public final class ApiDataCacheClient {
private static final Gson GSON = new GsonBuilder().create();
private final ControlPlaneHttpClient http;
private final BatchClient batch;
private final Supplier<Boolean> batchingEnabled;
private final RuntimeApiCacheStore runtimeStore = new RuntimeApiCacheStore();
private final PlayerApiCacheStore playerStore = new PlayerApiCacheStore();
private final Map<String, CachedApiCall<?>> registeredCalls = new ConcurrentHashMap<>();
private final Map<ApiCacheKey, CacheContext> touchedContexts = new ConcurrentHashMap<>();
public ApiDataCacheClient(ControlPlaneHttpClient http, BatchClient batch, Supplier<Boolean> batchingEnabled) {
this.http = http;
this.batch = batch;
this.batchingEnabled = batchingEnabled;
}
public <T> CachedApiCall<T> register(CachedApiCall<T> call) {
CachedApiCall<?> previous = registeredCalls.putIfAbsent(call.id(), call);
if (previous != null) {
throw new IllegalArgumentException("Cached API call already registered: " + call.id());
}
return call;
}
public <T> CompletableFuture<T> getOrPull(CachedApiCall<T> call, CacheContext context) {
registerIfNeeded(call);
ApiCacheKey key = key(call, context);
ApiCacheStore store = storeFor(call);
Optional<ApiCacheEntry> existing = store.get(key, context);
if (existing.isPresent() && existing.get().isFresh(call.pullPolicy(), Instant.now())) {
return CompletableFuture.completedFuture(value(existing.get(), call.responseType()));
}
return pullAndStore(call, context, key, store, existing);
}
public <T> CompletableFuture<T> forcePull(CachedApiCall<T> call, CacheContext context) {
registerIfNeeded(call);
ApiCacheKey key = key(call, context);
ApiCacheStore store = storeFor(call);
return pullAndStore(call, context, key, store, store.get(key, context));
}
public <T> CompletableFuture<T> prewarm(CachedApiCall<T> call, CacheContext context) {
return forcePull(call, context);
}
public void invalidate(CachedApiCall<?> call, CacheContext context) {
registerIfNeeded(call);
ApiCacheKey key = key(call, context);
storeFor(call).remove(key, context);
touchedContexts.remove(key);
}
public boolean invalidate(String callId, CacheContext context) {
CachedApiCall<?> call = registeredCalls.get(callId);
if (call == null) {
return false;
}
invalidate(call, context);
return true;
}
public <T> void updateLocal(CachedApiCall<T> call, CacheContext context, T value) {
registerIfNeeded(call);
ApiCacheKey key = key(call, context);
ApiCacheEntry entry = storeFor(call).get(key, context)
.orElseGet(() -> new ApiCacheEntry(null, call.responseType().getName(), null));
entry.replaceLocal(GSON.toJsonTree(value), call.responseType().getName(), Instant.now());
storeFor(call).put(key, entry, context);
touchedContexts.put(key, context);
}
public <T> CompletableFuture<T> forcePush(CachedApiCall<T> call, CacheContext context) {
registerIfNeeded(call);
ApiCacheKey key = key(call, context);
ApiCacheStore store = storeFor(call);
ApiCacheEntry entry = store.get(key, context)
.orElseThrow(() -> new IllegalStateException("No cached value exists for " + key.encoded()));
T value = value(entry, call.responseType());
return push(call, context, key, store, entry, value);
}
public CompletableFuture<Void> flushDirty() {
Collection<CompletableFuture<?>> futures = new ArrayList<>();
Instant now = Instant.now();
for (CachedApiCall<?> call : registeredCalls.values()) {
collectDirtyFlushes(call, runtimeStore.entries(), now, futures);
if (call.storageTarget() == StorageTarget.ASSET_STORE) {
collectDirtyFlushes(call, storeFor(call).entries(), now, futures);
}
}
for (Map.Entry<ApiCacheKey, CacheContext> touched : touchedContexts.entrySet()) {
CachedApiCall<?> call = registeredCalls.get(touched.getKey().callId());
if (call != null && call.storageTarget() == StorageTarget.PLAYER) {
ApiCacheStore store = storeFor(call);
store.get(touched.getKey(), touched.getValue())
.filter(entry -> entry.shouldPush(call.pushPolicy(), now))
.ifPresent(entry -> futures.add(pushUnchecked(call, touched.getValue(), touched.getKey(), store, entry)));
}
}
return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new));
}
public Optional<ApiCacheMetadata> metadata(CachedApiCall<?> call, CacheContext context) {
ApiCacheKey key = key(call, context);
return storeFor(call).get(key, context).map(ApiCacheEntry::metadata);
}
private <T> CompletableFuture<T> pullAndStore(
CachedApiCall<T> call,
CacheContext context,
ApiCacheKey key,
ApiCacheStore store,
Optional<ApiCacheEntry> existing
) {
return request(call.method(), call.uri(), call.body(), call.responseType())
.thenApply(value -> {
ApiCacheEntry entry = existing.orElseGet(() -> new ApiCacheEntry(null, call.responseType().getName(), null));
entry.replacePulled(GSON.toJsonTree(value), call.responseType().getName(), Instant.now());
store.put(key, entry, context);
touchedContexts.put(key, context);
if (call.onSuccess() != null) {
call.onSuccess().accept(value);
}
return value;
})
.exceptionally(error -> {
Throwable cause = unwrap(error);
if (existing.isPresent() && existing.get().payload() != null) {
ApiCacheEntry entry = existing.get();
entry.markFailed(cause);
store.put(key, entry, context);
touchedContexts.put(key, context);
if (call.onFailure() != null) {
call.onFailure().accept(cause);
}
return value(entry, call.responseType());
}
if (call.onFailure() != null) {
call.onFailure().accept(cause);
}
throw new CompletionException(cause);
});
}
private <T> CompletableFuture<T> push(
CachedApiCall<T> call,
CacheContext context,
ApiCacheKey key,
ApiCacheStore store,
ApiCacheEntry entry,
T value
) {
return request(call.pushMethod(value), call.pushUri(context, value), call.pushBody(context, value), call.responseType())
.thenApply(response -> {
entry.markPushed(Instant.now());
store.put(key, entry, context);
touchedContexts.put(key, context);
return response;
})
.exceptionally(error -> {
entry.markFailed(unwrap(error));
store.put(key, entry, context);
touchedContexts.put(key, context);
throw new CompletionException(unwrap(error));
});
}
private <T> CompletableFuture<T> request(String method, String uri, Object body, Class<T> responseType) {
if (batchingEnabled.get()) {
return batch.enqueue(null, uri, method, body, responseType);
}
return CompletableFuture.supplyAsync(() -> {
try {
return http.request(method, uri, body, responseType);
} catch (ControlPlaneException e) {
throw new CompletionException(e);
}
});
}
private void collectDirtyFlushes(CachedApiCall<?> call, Collection<StoredCacheEntry> entries, Instant now, Collection<CompletableFuture<?>> futures) {
for (StoredCacheEntry stored : entries) {
if (!stored.key().callId().equals(call.id()) || !stored.entry().shouldPush(call.pushPolicy(), now)) {
continue;
}
futures.add(pushUnchecked(call, stored.context(), stored.key(), storeFor(call), stored.entry()));
}
}
private <T> CompletableFuture<T> pushUnchecked(
CachedApiCall<?> rawCall,
CacheContext context,
ApiCacheKey key,
ApiCacheStore store,
ApiCacheEntry entry
) {
@SuppressWarnings("unchecked")
CachedApiCall<T> call = (CachedApiCall<T>) rawCall;
return push(call, context, key, store, entry, value(entry, call.responseType()));
}
private ApiCacheStore storeFor(CachedApiCall<?> call) {
return switch (call.storageTarget()) {
case PLAYER -> playerStore;
case RUNTIME_SHARED -> runtimeStore;
case ASSET_STORE -> new AssetStoreApiCacheStore(call.assetStoreAdapter());
};
}
private ApiCacheKey key(CachedApiCall<?> call, CacheContext context) {
return ApiCacheKey.of(call.id(), context == null ? "shared" : context.contextKey());
}
private <T> T value(ApiCacheEntry entry, Class<T> responseType) {
if (responseType == JsonObject.class && entry.payload() != null && entry.payload().isJsonObject()) {
return responseType.cast(entry.payload().getAsJsonObject());
}
return GSON.fromJson(entry.payload(), responseType);
}
private void registerIfNeeded(CachedApiCall<?> call) {
registeredCalls.putIfAbsent(call.id(), call);
}
private Throwable unwrap(Throwable error) {
if (error instanceof CompletionException && error.getCause() != null) {
return error.getCause();
}
return error;
}
}
@@ -0,0 +1,35 @@
package net.kewwbec.network.cache;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
final class AssetStoreApiCacheStore implements ApiCacheStore {
private final AssetStoreCacheAdapter adapter;
AssetStoreApiCacheStore(AssetStoreCacheAdapter adapter) {
this.adapter = Objects.requireNonNull(adapter, "adapter must not be null");
}
@Override
public Optional<ApiCacheEntry> get(ApiCacheKey key, CacheContext context) {
return adapter.read(key, context);
}
@Override
public void put(ApiCacheKey key, ApiCacheEntry entry, CacheContext context) {
adapter.write(key, entry, context);
}
@Override
public void remove(ApiCacheKey key, CacheContext context) {
adapter.remove(key, context);
}
@Override
public Collection<StoredCacheEntry> entries() {
return adapter.entries().stream()
.map(entry -> new StoredCacheEntry(entry.key(), entry.entry(), entry.context()))
.toList();
}
}
@@ -0,0 +1,25 @@
package net.kewwbec.network.cache;
import com.google.gson.JsonElement;
import java.util.Collection;
import java.util.Optional;
public interface AssetStoreCacheAdapter {
Optional<ApiCacheEntry> read(ApiCacheKey key, CacheContext context);
void write(ApiCacheKey key, ApiCacheEntry entry, CacheContext context);
default void remove(ApiCacheKey key, CacheContext context) {
}
default Collection<StoredAssetCacheEntry> entries() {
return java.util.List.of();
}
record StoredAssetCacheEntry(ApiCacheKey key, ApiCacheEntry entry, CacheContext context) {}
static ApiCacheEntry entry(JsonElement payload, String valueClassName, java.time.Instant fetchedAt) {
return new ApiCacheEntry(payload, valueClassName, fetchedAt);
}
}
@@ -0,0 +1,13 @@
package net.kewwbec.network.cache;
public interface CacheContext {
String contextKey();
static CacheContext shared() {
return () -> "shared";
}
static CacheContext of(String contextKey) {
return () -> contextKey;
}
}
@@ -0,0 +1,209 @@
package net.kewwbec.network.cache;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import java.time.Duration;
import java.util.Locale;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
public final class CachedApiCall<T> {
private final String id;
private final ApiEndpoint endpoint;
private final String method;
private final Supplier<String> uriSupplier;
private final Supplier<?> bodySupplier;
private final Class<T> responseType;
private final StorageTarget storageTarget;
private final PullPolicy pullPolicy;
private final PushPolicy pushPolicy;
private final AssetStoreCacheAdapter assetStoreAdapter;
private final String pushMethod;
private final BiFunction<CacheContext, T, String> pushUriMapper;
private final BiFunction<CacheContext, T, Object> pushBodyMapper;
private final Consumer<T> onSuccess;
private final Consumer<Throwable> onFailure;
private CachedApiCall(Builder<T> builder) {
this.id = requireNonBlank(builder.id, "id");
this.endpoint = builder.endpoint;
this.method = requireNonBlank(builder.method, "method").toUpperCase(Locale.ROOT);
this.uriSupplier = Objects.requireNonNull(builder.uriSupplier, "uriSupplier must not be null");
this.bodySupplier = builder.bodySupplier;
this.responseType = Objects.requireNonNull(builder.responseType, "responseType must not be null");
this.storageTarget = Objects.requireNonNull(builder.storageTarget, "storageTarget must not be null");
this.pullPolicy = Objects.requireNonNull(builder.pullPolicy, "pullPolicy must not be null");
this.pushPolicy = Objects.requireNonNull(builder.pushPolicy, "pushPolicy must not be null");
this.assetStoreAdapter = builder.assetStoreAdapter;
if (storageTarget == StorageTarget.ASSET_STORE && assetStoreAdapter == null) {
throw new IllegalArgumentException("ASSET_STORE cache calls require an AssetStoreCacheAdapter");
}
this.pushMethod = builder.pushMethod == null ? null : builder.pushMethod.toUpperCase(Locale.ROOT);
this.pushUriMapper = builder.pushUriMapper;
this.pushBodyMapper = builder.pushBodyMapper;
this.onSuccess = builder.onSuccess;
this.onFailure = builder.onFailure;
}
public static <T> Builder<T> builder(Class<T> responseType) {
return new Builder<>(responseType);
}
public String id() { return id; }
public ApiEndpoint endpoint() { return endpoint; }
public String method() { return method; }
public String uri() { return uriSupplier.get(); }
public Object body() { return bodySupplier != null ? bodySupplier.get() : null; }
public Class<T> responseType() { return responseType; }
public StorageTarget storageTarget() { return storageTarget; }
public PullPolicy pullPolicy() { return pullPolicy; }
public PushPolicy pushPolicy() { return pushPolicy; }
public AssetStoreCacheAdapter assetStoreAdapter() { return assetStoreAdapter; }
public Consumer<T> onSuccess() { return onSuccess; }
public Consumer<Throwable> onFailure() { return onFailure; }
public String pushMethod(T value) {
if (pushMethod != null) {
return pushMethod;
}
if ("GET".equals(method)) {
throw new IllegalStateException("GET cache calls require explicit push mapping");
}
return method;
}
public String pushUri(CacheContext context, T value) {
return pushUriMapper != null ? pushUriMapper.apply(context, value) : uri();
}
public Object pushBody(CacheContext context, T value) {
return pushBodyMapper != null ? pushBodyMapper.apply(context, value) : value;
}
private static String requireNonBlank(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
return value;
}
public static final class Builder<T> {
private final Class<T> responseType;
private String id;
private ApiEndpoint endpoint;
private String method;
private Supplier<String> uriSupplier;
private Supplier<?> bodySupplier;
private StorageTarget storageTarget = StorageTarget.RUNTIME_SHARED;
private PullPolicy pullPolicy = PullPolicy.neverRefresh();
private PushPolicy pushPolicy = PushPolicy.manualPushOnly();
private AssetStoreCacheAdapter assetStoreAdapter;
private String pushMethod;
private BiFunction<CacheContext, T, String> pushUriMapper;
private BiFunction<CacheContext, T, Object> pushBodyMapper;
private Consumer<T> onSuccess;
private Consumer<Throwable> onFailure;
private Builder(Class<T> responseType) {
this.responseType = responseType;
}
public Builder<T> id(String id) {
this.id = id;
return this;
}
public Builder<T> endpoint(ApiEndpoint endpoint) {
this.endpoint = endpoint;
return this;
}
public Builder<T> method(String method) {
this.method = method;
return this;
}
public Builder<T> uri(String uri) {
this.uriSupplier = () -> uri;
return this;
}
public Builder<T> uri(Supplier<String> uriSupplier) {
this.uriSupplier = uriSupplier;
return this;
}
public Builder<T> body(Object body) {
this.bodySupplier = () -> body;
return this;
}
public Builder<T> body(Supplier<?> bodySupplier) {
this.bodySupplier = bodySupplier;
return this;
}
public Builder<T> storageTarget(StorageTarget storageTarget) {
this.storageTarget = storageTarget;
return this;
}
public Builder<T> pullPolicy(PullPolicy pullPolicy) {
this.pullPolicy = pullPolicy;
return this;
}
public Builder<T> maxAge(Duration maxAge) {
this.pullPolicy = PullPolicy.maxAge(maxAge);
return this;
}
public Builder<T> neverRefresh() {
this.pullPolicy = PullPolicy.neverRefresh();
return this;
}
public Builder<T> pushPolicy(PushPolicy pushPolicy) {
this.pushPolicy = pushPolicy;
return this;
}
public Builder<T> dirtyMaxAge(Duration dirtyMaxAge) {
this.pushPolicy = PushPolicy.dirtyMaxAge(dirtyMaxAge);
return this;
}
public Builder<T> manualPushOnly() {
this.pushPolicy = PushPolicy.manualPushOnly();
return this;
}
public Builder<T> assetStoreAdapter(AssetStoreCacheAdapter assetStoreAdapter) {
this.assetStoreAdapter = assetStoreAdapter;
return this;
}
public Builder<T> push(String method, BiFunction<CacheContext, T, String> uriMapper, BiFunction<CacheContext, T, Object> bodyMapper) {
this.pushMethod = method;
this.pushUriMapper = uriMapper;
this.pushBodyMapper = bodyMapper;
return this;
}
public Builder<T> onSuccess(Consumer<T> onSuccess) {
this.onSuccess = onSuccess;
return this;
}
public Builder<T> onFailure(Consumer<Throwable> onFailure) {
this.onFailure = onFailure;
return this;
}
public CachedApiCall<T> build() {
return new CachedApiCall<>(this);
}
}
}
@@ -0,0 +1,62 @@
package net.kewwbec.network.cache;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.codecs.map.MapCodec;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
public final class NetworkApiDataComponent implements Component<EntityStore> {
public static final BuilderCodec<NetworkApiDataComponent> CODEC =
BuilderCodec.builder(NetworkApiDataComponent.class, NetworkApiDataComponent::new)
.append(new KeyedCodec<>("Entries", new MapCodec<>(Codec.STRING, HashMap::new, false)),
(component, entries) -> component.entries = new HashMap<>(entries),
component -> component.entries)
.add()
.build();
private static ComponentType<EntityStore, NetworkApiDataComponent> componentType;
private Map<String, String> entries = new HashMap<>();
public static void setComponentType(ComponentType<EntityStore, NetworkApiDataComponent> componentType) {
NetworkApiDataComponent.componentType = componentType;
}
public static ComponentType<EntityStore, NetworkApiDataComponent> componentType() {
if (componentType == null) {
throw new IllegalStateException("NetworkApiDataComponent has not been registered");
}
return componentType;
}
public Map<String, String> entries() {
return entries;
}
public String get(String key) {
return entries.get(key);
}
public void put(String key, String value) {
entries.put(key, value);
}
public void remove(String key) {
entries.remove(key);
}
@Nonnull
@Override
public Component<EntityStore> clone() {
NetworkApiDataComponent copy = new NetworkApiDataComponent();
copy.entries = new HashMap<>(entries);
return copy;
}
}
@@ -0,0 +1,50 @@
package net.kewwbec.network.cache;
import com.google.gson.JsonParser;
import java.util.Collection;
import java.util.Optional;
final class PlayerApiCacheStore implements ApiCacheStore {
@Override
public Optional<ApiCacheEntry> get(ApiCacheKey key, CacheContext context) {
PlayerCacheContext playerContext = playerContext(context);
NetworkApiDataComponent component = playerContext.store()
.ensureAndGetComponent(playerContext.ref(), NetworkApiDataComponent.componentType());
String raw = component.get(key.encoded());
if (raw == null || raw.isBlank()) {
return Optional.empty();
}
return Optional.of(ApiCacheEntry.fromJsonObject(JsonParser.parseString(raw).getAsJsonObject()));
}
@Override
public void put(ApiCacheKey key, ApiCacheEntry entry, CacheContext context) {
PlayerCacheContext playerContext = playerContext(context);
NetworkApiDataComponent component = playerContext.store()
.ensureAndGetComponent(playerContext.ref(), NetworkApiDataComponent.componentType());
component.put(key.encoded(), entry.toJsonObject().toString());
playerContext.store().putComponent(playerContext.ref(), NetworkApiDataComponent.componentType(), component);
}
@Override
public void remove(ApiCacheKey key, CacheContext context) {
PlayerCacheContext playerContext = playerContext(context);
NetworkApiDataComponent component = playerContext.store()
.ensureAndGetComponent(playerContext.ref(), NetworkApiDataComponent.componentType());
component.remove(key.encoded());
playerContext.store().putComponent(playerContext.ref(), NetworkApiDataComponent.componentType(), component);
}
@Override
public Collection<StoredCacheEntry> entries() {
return java.util.List.of();
}
private PlayerCacheContext playerContext(CacheContext context) {
if (!(context instanceof PlayerCacheContext playerContext)) {
throw new IllegalArgumentException("PLAYER cache calls require PlayerCacheContext");
}
return playerContext;
}
}
@@ -0,0 +1,30 @@
package net.kewwbec.network.cache;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.Objects;
public final class PlayerCacheContext implements CacheContext {
private final String playerId;
private final Ref<EntityStore> ref;
private final Store<EntityStore> store;
public PlayerCacheContext(String playerId, Ref<EntityStore> ref, Store<EntityStore> store) {
if (playerId == null || playerId.isBlank()) {
throw new IllegalArgumentException("playerId must not be blank");
}
this.playerId = playerId;
this.ref = Objects.requireNonNull(ref, "ref must not be null");
this.store = Objects.requireNonNull(store, "store must not be null");
}
@Override
public String contextKey() {
return playerId;
}
public Ref<EntityStore> ref() { return ref; }
public Store<EntityStore> store() { return store; }
}
+29
View File
@@ -0,0 +1,29 @@
package net.kewwbec.network.cache;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
public final class PullPolicy {
private final Duration maxAge;
private PullPolicy(Duration maxAge) {
this.maxAge = maxAge;
}
public static PullPolicy maxAge(Duration maxAge) {
Objects.requireNonNull(maxAge, "maxAge must not be null");
if (maxAge.isNegative() || maxAge.isZero()) {
throw new IllegalArgumentException("maxAge must be positive");
}
return new PullPolicy(maxAge);
}
public static PullPolicy neverRefresh() {
return new PullPolicy(null);
}
public Optional<Duration> maxAge() {
return Optional.ofNullable(maxAge);
}
}
+29
View File
@@ -0,0 +1,29 @@
package net.kewwbec.network.cache;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
public final class PushPolicy {
private final Duration dirtyMaxAge;
private PushPolicy(Duration dirtyMaxAge) {
this.dirtyMaxAge = dirtyMaxAge;
}
public static PushPolicy dirtyMaxAge(Duration dirtyMaxAge) {
Objects.requireNonNull(dirtyMaxAge, "dirtyMaxAge must not be null");
if (dirtyMaxAge.isNegative() || dirtyMaxAge.isZero()) {
throw new IllegalArgumentException("dirtyMaxAge must be positive");
}
return new PushPolicy(dirtyMaxAge);
}
public static PushPolicy manualPushOnly() {
return new PushPolicy(null);
}
public Optional<Duration> dirtyMaxAge() {
return Optional.ofNullable(dirtyMaxAge);
}
}
@@ -0,0 +1,134 @@
package net.kewwbec.network.cache;
import net.kewwbec.network.ControlPlaneClient;
import net.kewwbec.network.generated.api.v1.players.AuthorizationPermissionSnapshotEndpoint;
import net.kewwbec.network.generated.api.v1.players.ModerationSanctionSnapshotEndpoint;
import net.kewwbec.network.generated.api.v1.players.NotificationsIndexEndpoint;
import net.kewwbec.network.generated.api.v1.players.PlayerNotification;
import net.kewwbec.network.generated.api.v1.players.PlayerPermissionSnapshot;
import net.kewwbec.network.generated.api.v1.players.PlayerSanctionSnapshot;
import net.kewwbec.network.http.PathBuilder;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public final class ReactivePlayerCacheHooks {
public static final String SANCTION_SNAPSHOT_PREFIX = "players.moderation.sanction-snapshot";
public static final String PERMISSION_SNAPSHOT_PREFIX = "players.authorization.permission-snapshot";
public static final String NOTIFICATIONS_INDEX_PREFIX = "players.notifications.index";
private final ApiDataCacheClient cache;
public ReactivePlayerCacheHooks(ControlPlaneClient client) {
this(Objects.requireNonNull(client, "client must not be null").cache());
}
public ReactivePlayerCacheHooks(ApiDataCacheClient cache) {
this.cache = Objects.requireNonNull(cache, "cache must not be null");
}
public CompletableFuture<Void> onPlayerJoin(PlayerCacheContext context) {
return CompletableFuture.allOf(
refreshSanctionSnapshot(context),
refreshPermissionSnapshot(context),
prewarmNotifications(context)
);
}
public CompletableFuture<PlayerSanctionSnapshot> onSanctionChanged(PlayerCacheContext context) {
return refreshSanctionSnapshot(context);
}
public CompletableFuture<PlayerPermissionSnapshot> onPermissionChanged(PlayerCacheContext context) {
return refreshPermissionSnapshot(context);
}
public CompletableFuture<PlayerNotification[]> onNotificationCreated(PlayerCacheContext context) {
return refreshNotifications(context);
}
public void invalidateSanctionSnapshot(PlayerCacheContext context) {
cache.invalidate(sanctionSnapshotCall(context.contextKey()), context);
}
public void invalidatePermissionSnapshot(PlayerCacheContext context) {
cache.invalidate(permissionSnapshotCall(context.contextKey()), context);
}
public void invalidateNotifications(PlayerCacheContext context) {
cache.invalidate(notificationsIndexCall(context.contextKey()), context);
}
public CompletableFuture<PlayerSanctionSnapshot> refreshSanctionSnapshot(PlayerCacheContext context) {
CachedApiCall<PlayerSanctionSnapshot> call = sanctionSnapshotCall(context.contextKey());
cache.invalidate(call, context);
return cache.prewarm(call, context);
}
public CompletableFuture<PlayerPermissionSnapshot> refreshPermissionSnapshot(PlayerCacheContext context) {
CachedApiCall<PlayerPermissionSnapshot> call = permissionSnapshotCall(context.contextKey());
cache.invalidate(call, context);
return cache.prewarm(call, context);
}
public CompletableFuture<PlayerNotification[]> refreshNotifications(PlayerCacheContext context) {
CachedApiCall<PlayerNotification[]> call = notificationsIndexCall(context.contextKey());
cache.invalidate(call, context);
return cache.prewarm(call, context);
}
public CompletableFuture<PlayerNotification[]> prewarmNotifications(PlayerCacheContext context) {
return cache.getOrPull(notificationsIndexCall(context.contextKey()), context);
}
public static CachedApiCall<PlayerSanctionSnapshot> sanctionSnapshotCall(String playerId) {
String player = requirePlayerId(playerId);
return CachedApiCall.builder(PlayerSanctionSnapshot.class)
.id(callId(SANCTION_SNAPSHOT_PREFIX, player))
.endpoint(ModerationSanctionSnapshotEndpoint.VALUE)
.method("GET")
.uri(() -> PathBuilder.resolve(ModerationSanctionSnapshotEndpoint.VALUE.uri(), "player", player))
.storageTarget(StorageTarget.PLAYER)
.pullPolicy(PullPolicy.maxAge(Duration.ofSeconds(30)))
.manualPushOnly()
.build();
}
public static CachedApiCall<PlayerPermissionSnapshot> permissionSnapshotCall(String playerId) {
String player = requirePlayerId(playerId);
return CachedApiCall.builder(PlayerPermissionSnapshot.class)
.id(callId(PERMISSION_SNAPSHOT_PREFIX, player))
.endpoint(AuthorizationPermissionSnapshotEndpoint.VALUE)
.method("GET")
.uri(() -> PathBuilder.resolve(AuthorizationPermissionSnapshotEndpoint.VALUE.uri(), "player", player))
.storageTarget(StorageTarget.PLAYER)
.pullPolicy(PullPolicy.maxAge(Duration.ofSeconds(30)))
.manualPushOnly()
.build();
}
public static CachedApiCall<PlayerNotification[]> notificationsIndexCall(String playerId) {
String player = requirePlayerId(playerId);
return CachedApiCall.builder(PlayerNotification[].class)
.id(callId(NOTIFICATIONS_INDEX_PREFIX, player))
.endpoint(NotificationsIndexEndpoint.VALUE)
.method("GET")
.uri(() -> PathBuilder.resolve(NotificationsIndexEndpoint.VALUE.uri(), "player", player))
.storageTarget(StorageTarget.PLAYER)
.pullPolicy(PullPolicy.maxAge(Duration.ofSeconds(15)))
.manualPushOnly()
.build();
}
public static String callId(String prefix, String playerId) {
return prefix + ":" + requirePlayerId(playerId);
}
private static String requirePlayerId(String playerId) {
if (playerId == null || playerId.isBlank()) {
throw new IllegalArgumentException("playerId must not be blank");
}
return playerId;
}
}
@@ -0,0 +1,31 @@
package net.kewwbec.network.cache;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
final class RuntimeApiCacheStore implements ApiCacheStore {
private final ConcurrentHashMap<ApiCacheKey, ApiCacheEntry> entries = new ConcurrentHashMap<>();
@Override
public Optional<ApiCacheEntry> get(ApiCacheKey key, CacheContext context) {
return Optional.ofNullable(entries.get(key));
}
@Override
public void put(ApiCacheKey key, ApiCacheEntry entry, CacheContext context) {
entries.put(key, entry);
}
@Override
public void remove(ApiCacheKey key, CacheContext context) {
entries.remove(key);
}
@Override
public Collection<StoredCacheEntry> entries() {
return entries.entrySet().stream()
.map(entry -> new StoredCacheEntry(entry.getKey(), entry.getValue(), CacheContext.of(entry.getKey().contextKey())))
.toList();
}
}
@@ -0,0 +1,7 @@
package net.kewwbec.network.cache;
public enum StorageTarget {
PLAYER,
RUNTIME_SHARED,
ASSET_STORE
}
@@ -0,0 +1,4 @@
package net.kewwbec.network.cache;
record StoredCacheEntry(ApiCacheKey key, ApiCacheEntry entry, CacheContext context) {
}
@@ -0,0 +1,7 @@
package net.kewwbec.network.cache;
public enum SyncState {
CLEAN,
DIRTY,
FAILED
}
@@ -0,0 +1,8 @@
package net.kewwbec.network.config;
public enum ControlPlaneConfigurationSource {
MANUAL,
CONFIG_FILE,
SYSTEM_PROPERTY,
ENVIRONMENT
}
@@ -0,0 +1,109 @@
package net.kewwbec.network.config;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import java.time.Duration;
public final class ControlPlaneRuntimeConfig {
public static final BuilderCodec<ControlPlaneRuntimeConfig> CODEC =
BuilderCodec.builder(ControlPlaneRuntimeConfig.class, ControlPlaneRuntimeConfig::new)
.append(new KeyedCodec<>("BaseUrl", Codec.STRING),
(config, value) -> config.baseUrl = value,
ControlPlaneRuntimeConfig::getBaseUrl)
.add()
.append(new KeyedCodec<>("Token", Codec.STRING),
(config, value) -> config.token = value,
ControlPlaneRuntimeConfig::getToken)
.add()
.append(new KeyedCodec<>("BatchingEnabled", Codec.BOOLEAN),
(config, value) -> {
if (value != null) config.batchingEnabled = value;
},
ControlPlaneRuntimeConfig::isBatchingEnabled)
.add()
.append(new KeyedCodec<>("BatchFlushIntervalMs", Codec.INTEGER),
(config, value) -> {
if (value != null) config.batchFlushIntervalMs = value;
},
ControlPlaneRuntimeConfig::getBatchFlushIntervalMs)
.add()
.append(new KeyedCodec<>("MaxBatchSize", Codec.INTEGER),
(config, value) -> {
if (value != null) config.maxBatchSize = value;
},
ControlPlaneRuntimeConfig::getMaxBatchSize)
.add()
.append(new KeyedCodec<>("BatchEndpoint", Codec.STRING),
(config, value) -> config.batchEndpoint = value,
ControlPlaneRuntimeConfig::getBatchEndpoint)
.add()
.build();
private String baseUrl = "";
private String token = "";
private boolean batchingEnabled = ControlPlaneStartupSettings.DEFAULT_BATCHING_ENABLED;
private int batchFlushIntervalMs = Math.toIntExact(ControlPlaneStartupSettings.DEFAULT_BATCH_FLUSH_INTERVAL.toMillis());
private int maxBatchSize = ControlPlaneStartupSettings.DEFAULT_MAX_BATCH_SIZE;
private String batchEndpoint = ControlPlaneStartupSettings.DEFAULT_BATCH_ENDPOINT;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isBatchingEnabled() {
return batchingEnabled;
}
public void setBatchingEnabled(boolean batchingEnabled) {
this.batchingEnabled = batchingEnabled;
}
public int getBatchFlushIntervalMs() {
return batchFlushIntervalMs;
}
public void setBatchFlushIntervalMs(int batchFlushIntervalMs) {
this.batchFlushIntervalMs = batchFlushIntervalMs;
}
public int getMaxBatchSize() {
return maxBatchSize;
}
public void setMaxBatchSize(int maxBatchSize) {
this.maxBatchSize = maxBatchSize;
}
public String getBatchEndpoint() {
return batchEndpoint;
}
public void setBatchEndpoint(String batchEndpoint) {
this.batchEndpoint = batchEndpoint;
}
public ControlPlaneStartupSettings toStartupSettings() {
return new ControlPlaneStartupSettings(
baseUrl,
token,
batchingEnabled,
Duration.ofMillis(batchFlushIntervalMs),
maxBatchSize,
batchEndpoint
);
}
}
@@ -0,0 +1,196 @@
package net.kewwbec.network.config;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
public final class ControlPlaneStartupConfigResolver {
public static final String ENV_BASE_URL = "KWB_CONTROL_PLANE_BASE_URL";
public static final String ENV_TOKEN = "KWB_CONTROL_PLANE_TOKEN";
public static final String ENV_BATCHING_ENABLED = "KWB_CONTROL_PLANE_BATCHING_ENABLED";
public static final String ENV_BATCH_FLUSH_INTERVAL_MS = "KWB_CONTROL_PLANE_BATCH_FLUSH_INTERVAL_MS";
public static final String ENV_MAX_BATCH_SIZE = "KWB_CONTROL_PLANE_BATCH_MAX_SIZE";
public static final String ENV_BATCH_ENDPOINT = "KWB_CONTROL_PLANE_BATCH_ENDPOINT";
public static final String PROPERTY_BASE_URL = "kweebec.controlPlane.baseUrl";
public static final String PROPERTY_TOKEN = "kweebec.controlPlane.token";
public static final String PROPERTY_BATCHING_ENABLED = "kweebec.controlPlane.batching.enabled";
public static final String PROPERTY_BATCH_FLUSH_INTERVAL_MS = "kweebec.controlPlane.batch.flushIntervalMs";
public static final String PROPERTY_MAX_BATCH_SIZE = "kweebec.controlPlane.batch.maxSize";
public static final String PROPERTY_BATCH_ENDPOINT = "kweebec.controlPlane.batch.endpoint";
public Optional<ResolvedControlPlaneStartupConfig> resolve(ControlPlaneStartupSettings fileSettings) {
return resolve(System.getenv(), System.getProperties(), fileSettings);
}
public Optional<ResolvedControlPlaneStartupConfig> resolve(
Map<String, String> environment,
Properties systemProperties,
ControlPlaneStartupSettings fileSettings
) {
MutableSettings settings = MutableSettings.from(fileSettings != null
? fileSettings
: ControlPlaneStartupSettings.defaults());
ControlPlaneConfigurationSource source = settings.hasRequiredCredentials()
? ControlPlaneConfigurationSource.CONFIG_FILE
: null;
if (applySystemProperties(settings, systemProperties)) {
source = ControlPlaneConfigurationSource.SYSTEM_PROPERTY;
}
if (applyEnvironment(settings, environment)) {
source = ControlPlaneConfigurationSource.ENVIRONMENT;
}
ControlPlaneStartupSettings resolved = settings.toImmutable();
if (!resolved.hasRequiredCredentials()) {
return Optional.empty();
}
return Optional.of(new ResolvedControlPlaneStartupConfig(
resolved,
source != null ? source : ControlPlaneConfigurationSource.CONFIG_FILE
));
}
private boolean applySystemProperties(MutableSettings settings, Properties properties) {
if (properties == null) {
return false;
}
boolean changed = false;
changed |= stringProperty(properties, PROPERTY_BASE_URL).map(value -> {
settings.baseUrl = value;
return true;
}).orElse(false);
changed |= stringProperty(properties, PROPERTY_TOKEN).map(value -> {
settings.token = value;
return true;
}).orElse(false);
changed |= stringProperty(properties, PROPERTY_BATCHING_ENABLED).map(value -> {
settings.batchingEnabled = parseBoolean(PROPERTY_BATCHING_ENABLED, value);
return true;
}).orElse(false);
changed |= stringProperty(properties, PROPERTY_BATCH_FLUSH_INTERVAL_MS).map(value -> {
settings.batchFlushInterval = parsePositiveMillis(PROPERTY_BATCH_FLUSH_INTERVAL_MS, value);
return true;
}).orElse(false);
changed |= stringProperty(properties, PROPERTY_MAX_BATCH_SIZE).map(value -> {
settings.maxBatchSize = parsePositiveInt(PROPERTY_MAX_BATCH_SIZE, value);
return true;
}).orElse(false);
changed |= stringProperty(properties, PROPERTY_BATCH_ENDPOINT).map(value -> {
settings.batchEndpoint = value;
return true;
}).orElse(false);
return changed;
}
private boolean applyEnvironment(MutableSettings settings, Map<String, String> environment) {
if (environment == null) {
return false;
}
boolean changed = false;
changed |= stringEnv(environment, ENV_BASE_URL).map(value -> {
settings.baseUrl = value;
return true;
}).orElse(false);
changed |= stringEnv(environment, ENV_TOKEN).map(value -> {
settings.token = value;
return true;
}).orElse(false);
changed |= stringEnv(environment, ENV_BATCHING_ENABLED).map(value -> {
settings.batchingEnabled = parseBoolean(ENV_BATCHING_ENABLED, value);
return true;
}).orElse(false);
changed |= stringEnv(environment, ENV_BATCH_FLUSH_INTERVAL_MS).map(value -> {
settings.batchFlushInterval = parsePositiveMillis(ENV_BATCH_FLUSH_INTERVAL_MS, value);
return true;
}).orElse(false);
changed |= stringEnv(environment, ENV_MAX_BATCH_SIZE).map(value -> {
settings.maxBatchSize = parsePositiveInt(ENV_MAX_BATCH_SIZE, value);
return true;
}).orElse(false);
changed |= stringEnv(environment, ENV_BATCH_ENDPOINT).map(value -> {
settings.batchEndpoint = value;
return true;
}).orElse(false);
return changed;
}
private static Optional<String> stringProperty(Properties properties, String key) {
return nonBlank(properties.getProperty(key));
}
private static Optional<String> stringEnv(Map<String, String> environment, String key) {
return nonBlank(environment.get(key));
}
private static Optional<String> nonBlank(String value) {
if (value == null) {
return Optional.empty();
}
String trimmed = value.trim();
return trimmed.isEmpty() ? Optional.empty() : Optional.of(trimmed);
}
private static boolean parseBoolean(String key, String value) {
if ("true".equalsIgnoreCase(value)) {
return true;
}
if ("false".equalsIgnoreCase(value)) {
return false;
}
throw new IllegalArgumentException(key + " must be true or false");
}
private static Duration parsePositiveMillis(String key, String value) {
return Duration.ofMillis(parsePositiveInt(key, value));
}
private static int parsePositiveInt(String key, String value) {
try {
int parsed = Integer.parseInt(value);
if (parsed <= 0) {
throw new IllegalArgumentException(key + " must be positive");
}
return parsed;
} catch (NumberFormatException e) {
throw new IllegalArgumentException(key + " must be a positive integer", e);
}
}
private static final class MutableSettings {
private String baseUrl;
private String token;
private boolean batchingEnabled;
private Duration batchFlushInterval;
private int maxBatchSize;
private String batchEndpoint;
private static MutableSettings from(ControlPlaneStartupSettings settings) {
MutableSettings mutable = new MutableSettings();
mutable.baseUrl = settings.baseUrl();
mutable.token = settings.token();
mutable.batchingEnabled = settings.batchingEnabled();
mutable.batchFlushInterval = settings.batchFlushInterval();
mutable.maxBatchSize = settings.maxBatchSize();
mutable.batchEndpoint = settings.batchEndpoint();
return mutable;
}
private boolean hasRequiredCredentials() {
return baseUrl != null && token != null;
}
private ControlPlaneStartupSettings toImmutable() {
return new ControlPlaneStartupSettings(
baseUrl,
token,
batchingEnabled,
batchFlushInterval,
maxBatchSize,
batchEndpoint
);
}
}
}
@@ -0,0 +1,75 @@
package net.kewwbec.network.config;
import net.kewwbec.network.ControlPlaneClient;
import java.time.Duration;
import java.util.Objects;
public record ControlPlaneStartupSettings(
String baseUrl,
String token,
boolean batchingEnabled,
Duration batchFlushInterval,
int maxBatchSize,
String batchEndpoint
) {
public static final boolean DEFAULT_BATCHING_ENABLED = true;
public static final Duration DEFAULT_BATCH_FLUSH_INTERVAL = Duration.ofMillis(250);
public static final int DEFAULT_MAX_BATCH_SIZE = 100;
public static final String DEFAULT_BATCH_ENDPOINT = "/api/v1/batch";
public ControlPlaneStartupSettings {
baseUrl = trimToNull(baseUrl);
token = trimToNull(token);
batchFlushInterval = Objects.requireNonNullElse(batchFlushInterval, DEFAULT_BATCH_FLUSH_INTERVAL);
batchEndpoint = defaultIfBlank(batchEndpoint, DEFAULT_BATCH_ENDPOINT);
if (batchFlushInterval.isZero() || batchFlushInterval.isNegative()) {
throw new IllegalArgumentException("batchFlushInterval must be positive");
}
if (maxBatchSize <= 0) {
throw new IllegalArgumentException("maxBatchSize must be positive");
}
}
public static ControlPlaneStartupSettings defaults() {
return new ControlPlaneStartupSettings(
null,
null,
DEFAULT_BATCHING_ENABLED,
DEFAULT_BATCH_FLUSH_INTERVAL,
DEFAULT_MAX_BATCH_SIZE,
DEFAULT_BATCH_ENDPOINT
);
}
public boolean hasRequiredCredentials() {
return baseUrl != null && token != null;
}
public ControlPlaneClient buildClient() {
if (!hasRequiredCredentials()) {
throw new IllegalStateException("baseUrl and token must be set");
}
return ControlPlaneClient.builder()
.baseUrl(baseUrl)
.token(token)
.batchingEnabled(batchingEnabled)
.batchFlushInterval(batchFlushInterval)
.maxBatchSize(maxBatchSize)
.batchEndpoint(batchEndpoint)
.build();
}
private static String trimToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
private static String defaultIfBlank(String value, String defaultValue) {
String trimmed = trimToNull(value);
return trimmed == null ? defaultValue : trimmed;
}
}
@@ -0,0 +1,7 @@
package net.kewwbec.network.config;
public record ResolvedControlPlaneStartupConfig(
ControlPlaneStartupSettings settings,
ControlPlaneConfigurationSource source
) {
}
@@ -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;
}
}
@@ -0,0 +1,145 @@
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.batch.BatchApi;
import net.kewwbec.network.generated.api.v1.configs.ConfigsApi;
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 = "e918a5e4ab91e8ed17150e80ee593fd4400d2d54a994b28cf7281bef22eacd73";
private static final int DOMAIN_COUNT = 22;
private static final int ENDPOINT_COUNT = 140;
private ControlPlaneApiV1() {
}
public String apiVersion() {
return API_VERSION;
}
public String basePath() {
return BASE_PATH;
}
public String fingerprint() {
return FINGERPRINT;
}
public int domainCount() {
return DOMAIN_COUNT;
}
public int endpointCount() {
return ENDPOINT_COUNT;
}
public AnnouncementsApi announcements() {
return AnnouncementsApi.INSTANCE;
}
public AssetsApi assets() {
return AssetsApi.INSTANCE;
}
public AuditApi audit() {
return AuditApi.INSTANCE;
}
public BatchApi batch() {
return BatchApi.INSTANCE;
}
public ConfigsApi configs() {
return ConfigsApi.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;
}
public IntegrationsApi integrations() {
return IntegrationsApi.INSTANCE;
}
public ModerationApi moderation() {
return ModerationApi.INSTANCE;
}
public PlayersApi players() {
return PlayersApi.INSTANCE;
}
public PluginsApi plugins() {
return PluginsApi.INSTANCE;
}
public ProgressionApi progression() {
return ProgressionApi.INSTANCE;
}
public RewardsApi rewards() {
return RewardsApi.INSTANCE;
}
public ServerApi server() {
return ServerApi.INSTANCE;
}
public ServiceAccountsApi serviceAccounts() {
return ServiceAccountsApi.INSTANCE;
}
public SocialApi social() {
return SocialApi.INSTANCE;
}
public SupportApi support() {
return SupportApi.INSTANCE;
}
public WorldsApi worlds() {
return WorldsApi.INSTANCE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.announcements;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ActiveEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.announcements.active",
List.of("GET"),
"/api/v1/announcements/active",
List.of("announcements.read"),
List.of("api", "auth:sanctum", "service.abilities:announcements.read"),
List.of(),
new RequestDescriptor("App\\Domains\\Announcements\\Http\\Requests\\ListActiveAnnouncementsRequest", "query", List.of(new RequestField("audience", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"network\",\"server\",\"website\",\"player_portal\""))), 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))
)
;
private ActiveEndpoint() {
}
}
@@ -0,0 +1,41 @@
package net.kewwbec.network.generated.api.v1.announcements;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
public final class AnnouncementsApi {
public static final AnnouncementsApi INSTANCE = new AnnouncementsApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
ActiveEndpoint.VALUE
);
private AnnouncementsApi() {
}
public String domain() {
return "announcements";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint active() {
return ActiveEndpoint.VALUE;
}
}
@@ -0,0 +1,41 @@
package net.kewwbec.network.generated.api.v1.audit;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
public final class AuditApi {
public static final AuditApi INSTANCE = new AuditApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
EventsIndexEndpoint.VALUE
);
private AuditApi() {
}
public String domain() {
return "audit";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint eventsIndex() {
return EventsIndexEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.audit;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class EventsIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.audit.events.index",
List.of("GET"),
"/api/v1/audit/events",
List.of("audit-events.read"),
List.of("api", "auth:sanctum", "service.abilities:audit-events.read"),
List.of(),
new RequestDescriptor("App\\Domains\\Audit\\Http\\Requests\\ListAuditEventsRequest", "query", List.of(new RequestField("action", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("actor_type", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("actor_id", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("target_type", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("target_id", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), 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))
)
;
private EventsIndexEndpoint() {
}
}
@@ -0,0 +1,41 @@
package net.kewwbec.network.generated.api.v1.health;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
public final class HealthApi {
public static final HealthApi INSTANCE = new HealthApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
HealthEndpoint.VALUE
);
private HealthApi() {
}
public String domain() {
return "health";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint health() {
return HealthEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.health;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class HealthEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.health",
List.of("GET"),
"/api/v1/health",
List.of(),
List.of("api"),
List.of(),
null,
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private HealthEndpoint() {
}
}
@@ -0,0 +1,66 @@
package net.kewwbec.network.generated.api.v1.infrastructure;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
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,
ServerTemplatesStoreEndpoint.VALUE,
ServerTemplatesUpdateEndpoint.VALUE
);
private InfrastructureApi() {
}
public String domain() {
return "infrastructure";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint serverInstancesDestinationValidation() {
return ServerInstancesDestinationValidationEndpoint.VALUE;
}
public ApiEndpoint serverInstancesPresenceIndex() {
return ServerInstancesPresenceIndexEndpoint.VALUE;
}
public ApiEndpoint serverTemplatesIndex() {
return ServerTemplatesIndexEndpoint.VALUE;
}
public ApiEndpoint serverTemplatesShow() {
return ServerTemplatesShowEndpoint.VALUE;
}
public ApiEndpoint serverTemplatesStore() {
return ServerTemplatesStoreEndpoint.VALUE;
}
public ApiEndpoint serverTemplatesUpdate() {
return ServerTemplatesUpdateEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.infrastructure;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ServerInstancesPresenceIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.infrastructure.server-instances.presence.index",
List.of("GET"),
"/api/v1/infrastructure/server-instances/presence",
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("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))
)
;
private ServerInstancesPresenceIndexEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.infrastructure;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ServerTemplatesIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.infrastructure.server-templates.index",
List.of("GET"),
"/api/v1/infrastructure/server-templates",
List.of("server-templates.read"),
List.of("api", "auth:sanctum", "service.abilities:server-templates.read"),
List.of(),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\ListServerTemplatesRequest", "query", 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("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("is_active", 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))
)
;
private ServerTemplatesIndexEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.infrastructure;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ServerTemplatesShowEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.infrastructure.server-templates.show",
List.of("GET"),
"/api/v1/infrastructure/server-templates/{serverTemplate}",
List.of("server-templates.read"),
List.of("api", "auth:sanctum", "service.abilities:server-templates.read"),
List.of(new RouteParameter("serverTemplate", new TypeDescriptor("App\\Domains\\Infrastructure\\Models\\ServerTemplate", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Infrastructure\\Resources\\ServerTemplateResource", false, false))
)
;
private ServerTemplatesShowEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.infrastructure;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ServerTemplatesStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.infrastructure.server-templates.store",
List.of("POST"),
"/api/v1/infrastructure/server-templates",
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("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_resource", new TypeDescriptor("App\\Domains\\Infrastructure\\Resources\\ServerTemplateResource", false, false))
)
;
private ServerTemplatesStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.infrastructure;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ServerTemplatesUpdateEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.infrastructure.server-templates.update",
List.of("PATCH"),
"/api/v1/infrastructure/server-templates/{serverTemplate}",
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("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_resource", new TypeDescriptor("App\\Domains\\Infrastructure\\Resources\\ServerTemplateResource", false, false))
)
;
private ServerTemplatesUpdateEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.integrations;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class IndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.integrations.index",
List.of("GET"),
"/api/v1/integrations",
List.of("integrations.read"),
List.of("api", "auth:sanctum", "service.abilities:integrations.read"),
List.of(),
new RequestDescriptor("App\\Domains\\Integrations\\Http\\Requests\\ListIntegrationsRequest", "query", List.of(new RequestField("driver", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"discord\",\"website\",\"webhook\",\"commerce\",\"analytics\",\"internal_tool\""))), new RequestField("direction", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"inbound\",\"outbound\",\"bidirectional\""))), new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"active\",\"paused\",\"error\",\"retired\""))), 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))
)
;
private IndexEndpoint() {
}
}
@@ -0,0 +1,51 @@
package net.kewwbec.network.generated.api.v1.integrations;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
public final class IntegrationsApi {
public static final IntegrationsApi INSTANCE = new IntegrationsApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
IndexEndpoint.VALUE,
StoreEndpoint.VALUE,
UpdateEndpoint.VALUE
);
private IntegrationsApi() {
}
public String domain() {
return "integrations";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint index() {
return IndexEndpoint.VALUE;
}
public ApiEndpoint store() {
return StoreEndpoint.VALUE;
}
public ApiEndpoint update() {
return UpdateEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.integrations;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class StoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.integrations.store",
List.of("POST"),
"/api/v1/integrations",
List.of("integrations.write"),
List.of("api", "auth:sanctum", "service.abilities:integrations.write"),
List.of(),
new RequestDescriptor("App\\Domains\\Integrations\\Http\\Requests\\StoreIntegrationRequest", "body", List.of(new RequestField("key", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"), new RuleDescriptor("string", null, "unique:integrations,key"))), new RequestField("name", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("driver", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"discord\",\"website\",\"webhook\",\"commerce\",\"analytics\",\"internal_tool\""))), new RequestField("direction", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"inbound\",\"outbound\",\"bidirectional\""))), new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"active\",\"paused\",\"error\",\"retired\""))), new RequestField("endpoint", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("secret_reference", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("last_sync_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Integrations\\Resources\\IntegrationResource", false, false))
)
;
private StoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.integrations;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class UpdateEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.integrations.update",
List.of("PATCH"),
"/api/v1/integrations/{integration}",
List.of("integrations.write"),
List.of("api", "auth:sanctum", "service.abilities:integrations.write"),
List.of(new RouteParameter("integration", new TypeDescriptor("App\\Domains\\Integrations\\Models\\Integration", false, false))),
new RequestDescriptor("App\\Domains\\Integrations\\Http\\Requests\\UpdateIntegrationRequest", "body", List.of(new RequestField("name", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("driver", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"discord\",\"website\",\"webhook\",\"commerce\",\"analytics\",\"internal_tool\""))), new RequestField("direction", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"inbound\",\"outbound\",\"bidirectional\""))), new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"active\",\"paused\",\"error\",\"retired\""))), new RequestField("endpoint", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("secret_reference", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("last_sync_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("last_error_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("last_error_summary", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Integrations\\Resources\\IntegrationResource", false, false))
)
;
private UpdateEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesAppealsStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.appeals.store",
List.of("POST"),
"/api/v1/moderation/cases/{moderationCase}/appeals",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false))),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\StoreModerationCaseAppealRequest", "body", List.of(new RequestField("submitted_by_player_id", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("reason", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"))), new RequestField("submitted_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private CasesAppealsStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesEvidenceStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.evidence.store",
List.of("POST"),
"/api/v1/moderation/cases/{moderationCase}/evidence",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false))),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\StoreModerationCaseEvidenceRequest", "body", List.of(new RequestField("kind", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"chat_log\",\"screenshot\",\"video\",\"external_link\",\"note\""))), new RequestField("headline", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("summary", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("storage_driver", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("storage_reference", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:1000"))), new RequestField("submitted_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), 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))
)
;
private CasesEvidenceStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.index",
List.of("GET"),
"/api/v1/moderation/cases",
List.of("moderation.cases.read"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.read"),
List.of(),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\ListModerationCasesRequest", "query", List.of(new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"open\",\"under_review\",\"resolved\",\"closed\""))), new RequestField("case_type", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"report\",\"investigation\",\"appeal\""))), new RequestField("priority", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"low\",\"normal\",\"high\",\"urgent\""))), new RequestField("subject_player_id", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("assigned_to_player_id", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), 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))
)
;
private CasesIndexEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesPunishmentsStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.punishments.store",
List.of("POST"),
"/api/v1/moderation/cases/{moderationCase}/punishments",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false))),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\StoreModerationPunishmentRequest", "body", List.of(new RequestField("punishment_type", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"warning\",\"mute\",\"suspension\",\"ban\""))), new RequestField("reason", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"))), new RequestField("starts_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("ends_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), 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))
)
;
private CasesPunishmentsStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesPunishmentsUpdateEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.punishments.update",
List.of("PATCH"),
"/api/v1/moderation/cases/{moderationCase}/punishments/{punishment}",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false)), new RouteParameter("punishment", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationPunishment", false, false))),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\RevokeModerationPunishmentRequest", "body", List.of(new RequestField("revoked_reason", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"))), new RequestField("revoked_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private CasesPunishmentsUpdateEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesReopenEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.reopen",
List.of("POST"),
"/api/v1/moderation/cases/{moderationCase}/reopen",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false))),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\ReopenModerationCaseRequest", "body", List.of(new RequestField("reason", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"))), new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"open\",\"under_review\""))), new RequestField("summary", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("assigned_to_player_id", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("reopened_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Moderation\\Resources\\ModerationCaseResource", false, false))
)
;
private CasesReopenEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesShowEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.show",
List.of("GET"),
"/api/v1/moderation/cases/{moderationCase}",
List.of("moderation.cases.read"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.read"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Moderation\\Resources\\ModerationCaseResource", false, false))
)
;
private CasesShowEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.store",
List.of("POST"),
"/api/v1/moderation/cases",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\StoreModerationCaseRequest", "body", List.of(new RequestField("case_type", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"report\",\"investigation\",\"appeal\""))), new RequestField("priority", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"low\",\"normal\",\"high\",\"urgent\""))), new RequestField("subject_player_id", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("reported_by_player_id", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("assigned_to_player_id", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("title", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("summary", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("incident_occurred_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Moderation\\Resources\\ModerationCaseResource", false, false))
)
;
private CasesStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class CasesUpdateEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.moderation.cases.update",
List.of("PATCH"),
"/api/v1/moderation/cases/{moderationCase}",
List.of("moderation.cases.write"),
List.of("api", "auth:sanctum", "service.abilities:moderation.cases.write"),
List.of(new RouteParameter("moderationCase", new TypeDescriptor("App\\Domains\\Moderation\\Models\\ModerationCase", false, false))),
new RequestDescriptor("App\\Domains\\Moderation\\Http\\Requests\\UpdateModerationCaseRequest", "body", List.of(new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"open\",\"under_review\",\"resolved\",\"closed\""))), new RequestField("priority", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"low\",\"normal\",\"high\",\"urgent\""))), new RequestField("assigned_to_player_id", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("summary", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("resolution_summary", List.of(new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\RequiredIf", ""), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("last_reviewed_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("resolved_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Moderation\\Resources\\ModerationCaseResource", false, false))
)
;
private CasesUpdateEndpoint() {
}
}
@@ -0,0 +1,81 @@
package net.kewwbec.network.generated.api.v1.moderation;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
public final class ModerationApi {
public static final ModerationApi INSTANCE = new ModerationApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
CasesAppealsStoreEndpoint.VALUE,
CasesEvidenceStoreEndpoint.VALUE,
CasesIndexEndpoint.VALUE,
CasesPunishmentsStoreEndpoint.VALUE,
CasesPunishmentsUpdateEndpoint.VALUE,
CasesReopenEndpoint.VALUE,
CasesShowEndpoint.VALUE,
CasesStoreEndpoint.VALUE,
CasesUpdateEndpoint.VALUE
);
private ModerationApi() {
}
public String domain() {
return "moderation";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint casesAppealsStore() {
return CasesAppealsStoreEndpoint.VALUE;
}
public ApiEndpoint casesEvidenceStore() {
return CasesEvidenceStoreEndpoint.VALUE;
}
public ApiEndpoint casesIndex() {
return CasesIndexEndpoint.VALUE;
}
public ApiEndpoint casesPunishmentsStore() {
return CasesPunishmentsStoreEndpoint.VALUE;
}
public ApiEndpoint casesPunishmentsUpdate() {
return CasesPunishmentsUpdateEndpoint.VALUE;
}
public ApiEndpoint casesReopen() {
return CasesReopenEndpoint.VALUE;
}
public ApiEndpoint casesShow() {
return CasesShowEndpoint.VALUE;
}
public ApiEndpoint casesStore() {
return CasesStoreEndpoint.VALUE;
}
public ApiEndpoint casesUpdate() {
return CasesUpdateEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class NotificationsIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.players.notifications.index",
List.of("GET"),
"/api/v1/players/{player}/notifications",
List.of("notifications.read"),
List.of("api", "auth:sanctum", "service.abilities:notifications.read"),
List.of(new RouteParameter("player", new TypeDescriptor("App\\Domains\\Identity\\Models\\Player", false, false))),
new RequestDescriptor("App\\Domains\\Notifications\\Http\\Requests\\ListPlayerNotificationsRequest", "query", List.of(new RequestField("status", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"unread\",\"read\",\"archived\""))), 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))
)
;
private NotificationsIndexEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class NotificationsReadEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.players.notifications.read",
List.of("PATCH"),
"/api/v1/players/{player}/notifications/{notification}/read",
List.of("notifications.write"),
List.of("api", "auth:sanctum", "service.abilities:notifications.write"),
List.of(new RouteParameter("player", new TypeDescriptor("App\\Domains\\Identity\\Models\\Player", false, false)), new RouteParameter("notification", new TypeDescriptor("App\\Domains\\Notifications\\Models\\PlayerNotification", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Notifications\\Resources\\PlayerNotificationResource", false, false))
)
;
private NotificationsReadEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class NotificationsStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.players.notifications.store",
List.of("POST"),
"/api/v1/players/{player}/notifications",
List.of("notifications.write"),
List.of("api", "auth:sanctum", "service.abilities:notifications.write"),
List.of(new RouteParameter("player", new TypeDescriptor("App\\Domains\\Identity\\Models\\Player", false, false))),
new RequestDescriptor("App\\Domains\\Notifications\\Http\\Requests\\StorePlayerNotificationRequest", "body", List.of(new RequestField("type", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("title", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("body", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"))), new RequestField("priority", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"low\",\"normal\",\"high\""))), new RequestField("sent_at", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Notifications\\Resources\\PlayerNotificationResource", false, false))
)
;
private NotificationsStoreEndpoint() {
}
}
@@ -0,0 +1,156 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
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,
SocialPrivacyUpdateEndpoint.VALUE,
VisibilityUpdateEndpoint.VALUE,
WebsiteProfileEndpoint.VALUE,
XpLedgerIndexEndpoint.VALUE,
XpLedgerStoreEndpoint.VALUE,
XpProgressIndexEndpoint.VALUE
);
private PlayersApi() {
}
public String domain() {
return "players";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
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;
}
public ApiEndpoint notificationsRead() {
return NotificationsReadEndpoint.VALUE;
}
public ApiEndpoint notificationsStore() {
return NotificationsStoreEndpoint.VALUE;
}
public ApiEndpoint presence() {
return PresenceEndpoint.VALUE;
}
public ApiEndpoint sessionProfile() {
return SessionProfileEndpoint.VALUE;
}
public ApiEndpoint socialGraph() {
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;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class SessionProfileEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.players.session-profile",
List.of("GET"),
"/api/v1/players/{player}/session-profile",
List.of("players.session-profile.read"),
List.of("api", "auth:sanctum", "service.abilities:players.session-profile.read"),
List.of(new RouteParameter("player", new TypeDescriptor("App\\Domains\\Identity\\Models\\Player", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Identity\\Resources\\PlayerSessionProfileResource", false, false))
)
;
private SessionProfileEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class SocialGraphEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.players.social-graph",
List.of("GET"),
"/api/v1/players/{player}/social-graph",
List.of("social.read"),
List.of("api", "auth:sanctum", "service.abilities:social.read"),
List.of(new RouteParameter("player", new TypeDescriptor("App\\Domains\\Identity\\Models\\Player", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Social\\Resources\\PlayerSocialGraphResource", false, false))
)
;
private SocialGraphEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.players;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class WebsiteProfileEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.players.website-profile",
List.of("GET"),
"/api/v1/players/{player}/website-profile",
List.of("players.website-profile.read"),
List.of("api", "auth:sanctum", "service.abilities:players.website-profile.read"),
List.of(new RouteParameter("player", new TypeDescriptor("App\\Domains\\Identity\\Models\\Player", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Identity\\Resources\\PlayerWebsiteProfileResource", false, false))
)
;
private WebsiteProfileEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ConfigVersionsIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.config-versions.index",
List.of("GET"),
"/api/v1/plugins/{plugin}/config-versions",
List.of("plugins.read"),
List.of("api", "auth:sanctum", "service.abilities:plugins.read"),
List.of(new RouteParameter("plugin", new TypeDescriptor("App\\Domains\\Plugins\\Models\\Plugin", false, false))),
new RequestDescriptor("App\\Domains\\Plugins\\Http\\Requests\\ListPluginConfigVersionsRequest", "query", List.of(new RequestField("limit", List.of(new RuleDescriptor("string", null, "nullable"), 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))
)
;
private ConfigVersionsIndexEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ConfigVersionsStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.config-versions.store",
List.of("POST"),
"/api/v1/plugins/{plugin}/config-versions",
List.of("plugins.deploy.write"),
List.of("api", "auth:sanctum", "service.abilities:plugins.deploy.write"),
List.of(new RouteParameter("plugin", new TypeDescriptor("App\\Domains\\Plugins\\Models\\Plugin", false, false))),
new RequestDescriptor("App\\Domains\\Plugins\\Http\\Requests\\StorePluginConfigVersionRequest", "body", List.of(new RequestField("version", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("format", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("content_driver", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("content_reference", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:2048"))), new RequestField("checksum", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("released_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Plugins\\Resources\\PluginConfigVersionResource", false, false))
)
;
private ConfigVersionsStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class DeploymentsIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.deployments.index",
List.of("GET"),
"/api/v1/plugins/{plugin}/deployments",
List.of("plugins.read"),
List.of("api", "auth:sanctum", "service.abilities:plugins.read"),
List.of(new RouteParameter("plugin", new TypeDescriptor("App\\Domains\\Plugins\\Models\\Plugin", false, false))),
new RequestDescriptor("App\\Domains\\Plugins\\Http\\Requests\\ListPluginDeploymentHistoryRequest", "query", List.of(new RequestField("server_template", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("status", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"active\",\"superseded\",\"rolled_back\""))), new RequestField("limit", List.of(new RuleDescriptor("string", null, "nullable"), 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))
)
;
private DeploymentsIndexEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class DeploymentsRollbackEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.deployments.rollback",
List.of("POST"),
"/api/v1/plugins/deployments/{pluginDeployment}/rollback",
List.of("plugins.deploy.write"),
List.of("api", "auth:sanctum", "service.abilities:plugins.deploy.write"),
List.of(new RouteParameter("pluginDeployment", new TypeDescriptor("App\\Domains\\Plugins\\Models\\PluginDeployment", false, false))),
new RequestDescriptor("App\\Domains\\Plugins\\Http\\Requests\\RollbackPluginDeploymentRequest", "body", List.of(new RequestField("reason", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"))), new RequestField("rolled_back_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Plugins\\Resources\\PluginDeploymentResource", false, false))
)
;
private DeploymentsRollbackEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class DeploymentsStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.deployments.store",
List.of("POST"),
"/api/v1/plugins/{plugin}/deployments",
List.of("plugins.deploy.write"),
List.of("api", "auth:sanctum", "service.abilities:plugins.deploy.write"),
List.of(new RouteParameter("plugin", new TypeDescriptor("App\\Domains\\Plugins\\Models\\Plugin", false, false))),
new RequestDescriptor("App\\Domains\\Plugins\\Http\\Requests\\StorePluginDeploymentRequest", "body", List.of(new RequestField("plugin_version_id", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:plugin_versions,id"))), new RequestField("plugin_config_version_id", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:plugin_config_versions,id"))), new RequestField("server_template_id", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:server_templates,id"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Plugins\\Resources\\PluginDeploymentResource", false, false))
)
;
private DeploymentsStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class IndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.index",
List.of("GET"),
"/api/v1/plugins",
List.of("plugins.read"),
List.of("api", "auth:sanctum", "service.abilities:plugins.read"),
List.of(),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("Illuminate\\Http\\Resources\\Json\\AnonymousResourceCollection", false, false))
)
;
private IndexEndpoint() {
}
}
@@ -0,0 +1,71 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
public final class PluginsApi {
public static final PluginsApi INSTANCE = new PluginsApi();
private static final List<ApiEndpoint> ENDPOINTS = List.of(
ConfigVersionsIndexEndpoint.VALUE,
ConfigVersionsStoreEndpoint.VALUE,
DeploymentsIndexEndpoint.VALUE,
DeploymentsRollbackEndpoint.VALUE,
DeploymentsStoreEndpoint.VALUE,
IndexEndpoint.VALUE,
ShowEndpoint.VALUE
);
private PluginsApi() {
}
public String domain() {
return "plugins";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint configVersionsIndex() {
return ConfigVersionsIndexEndpoint.VALUE;
}
public ApiEndpoint configVersionsStore() {
return ConfigVersionsStoreEndpoint.VALUE;
}
public ApiEndpoint deploymentsIndex() {
return DeploymentsIndexEndpoint.VALUE;
}
public ApiEndpoint deploymentsRollback() {
return DeploymentsRollbackEndpoint.VALUE;
}
public ApiEndpoint deploymentsStore() {
return DeploymentsStoreEndpoint.VALUE;
}
public ApiEndpoint index() {
return IndexEndpoint.VALUE;
}
public ApiEndpoint show() {
return ShowEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.plugins;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ShowEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.plugins.show",
List.of("GET"),
"/api/v1/plugins/{plugin}",
List.of("plugins.read"),
List.of("api", "auth:sanctum", "service.abilities:plugins.read"),
List.of(new RouteParameter("plugin", new TypeDescriptor("App\\Domains\\Plugins\\Models\\Plugin", false, false))),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Plugins\\Resources\\PluginResource", false, false))
)
;
private ShowEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.server;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class InstancesHeartbeatEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.server.instances.heartbeat",
List.of("POST"),
"/api/v1/server/instances/{instance}/heartbeat",
List.of("server-instance.heartbeat.write"),
List.of("api", "auth:sanctum", "service.abilities:server-instance.heartbeat.write", "service.instance:instance"),
List.of(new RouteParameter("instance", new TypeDescriptor("App\\Domains\\Infrastructure\\Models\\ServerInstance", false, false))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\ServerHeartbeatRequest", "body", List.of(new RequestField("status", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"starting\",\"running\",\"draining\",\"stopped\",\"unhealthy\""))), new RequestField("current_players", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("max_players", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"), new RuleDescriptor("string", null, "gte:current_players"))), new RequestField("host", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("port", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "between:1,65535"))), new RequestField("reported_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("meta", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private InstancesHeartbeatEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.server;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class InstancesMatchesResultsStoreEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.server.instances.matches.results.store",
List.of("POST"),
"/api/v1/server/instances/{instance}/matches/results",
List.of("matches.result.write"),
List.of("api", "auth:sanctum", "service.abilities:matches.result.write", "service.instance:instance"),
List.of(new RouteParameter("instance", new TypeDescriptor("App\\Domains\\Infrastructure\\Models\\ServerInstance", false, false))),
new RequestDescriptor("App\\Domains\\Gameplay\\Http\\Requests\\StoreMatchResultRequest", "body", List.of(new RequestField("match_id", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("game_mode", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("status", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"completed\",\"abandoned\",\"cancelled\""))), new RequestField("started_at", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "date"))), new RequestField("ended_at", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "date"), new RuleDescriptor("string", null, "after_or_equal:started_at"))), new RequestField("replay_driver", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("replay_reference", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:2048"))), new RequestField("metadata", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))), new RequestField("participants", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "array"), new RuleDescriptor("string", null, "min:1"), new RuleDescriptor("string", null, "max:100"))), new RequestField("participants.*.player_id", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "exists:players,id"))), new RequestField("participants.*.team_key", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:100"))), new RequestField("participants.*.placement", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:1"))), new RequestField("participants.*.outcome", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"win\",\"loss\",\"draw\",\"abandoned\""))), new RequestField("participants.*.score", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"))), new RequestField("participants.*.stats", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Gameplay\\Resources\\MatchSessionResource", false, false))
)
;
private InstancesMatchesResultsStoreEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.server;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class InstancesRegisterEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.server.instances.register",
List.of("POST"),
"/api/v1/server/instances/{instance}/register",
List.of("presence.write"),
List.of("api", "auth:sanctum", "service.abilities:presence.write", "service.instance:instance"),
List.of(new RouteParameter("instance", new TypeDescriptor("App\\Domains\\Infrastructure\\Models\\ServerInstance", false, false))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\ServerRegistrationRequest", "body", List.of(new RequestField("status", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("object", "Illuminate\\Validation\\Rules\\In", "in:\"starting\",\"running\",\"draining\",\"unhealthy\""))), new RequestField("host", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("port", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "between:1,65535"))), new RequestField("current_players", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("max_players", List.of(new RuleDescriptor("string", null, "required"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"), new RuleDescriptor("string", null, "gte:current_players"))), new RequestField("reported_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("meta", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private InstancesRegisterEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.server;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class InstancesShutdownEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.server.instances.shutdown",
List.of("POST"),
"/api/v1/server/instances/{instance}/shutdown",
List.of("presence.write"),
List.of("api", "auth:sanctum", "service.abilities:presence.write", "service.instance:instance"),
List.of(new RouteParameter("instance", new TypeDescriptor("App\\Domains\\Infrastructure\\Models\\ServerInstance", false, false))),
new RequestDescriptor("App\\Domains\\Infrastructure\\Http\\Requests\\ServerShutdownRequest", "body", List.of(new RequestField("reason", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))), new RequestField("current_players", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "integer"), new RuleDescriptor("string", null, "min:0"))), new RequestField("reported_at", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "date"))), new RequestField("meta", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "array"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private InstancesShutdownEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.server;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class ProfileEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.server.profile",
List.of("GET"),
"/api/v1/server/profile",
List.of("server.profile.read"),
List.of("api", "auth:sanctum", "service.abilities:server.profile.read"),
List.of(),
null,
new ResponseDescriptor("json_resource", new TypeDescriptor("App\\Domains\\Infrastructure\\Resources\\ServerProfileResource", false, false))
)
;
private ProfileEndpoint() {
}
}
@@ -0,0 +1,76 @@
package net.kewwbec.network.generated.api.v1.server;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
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
);
private ServerApi() {
}
public String domain() {
return "server";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
throw new IllegalArgumentException("Unknown route: " + routeName);
}
public ApiEndpoint instancesAssetPacksVersionsAcknowledge() {
return InstancesAssetPacksVersionsAcknowledgeEndpoint.VALUE;
}
public ApiEndpoint instancesGameplayAttemptsStore() {
return InstancesGameplayAttemptsStoreEndpoint.VALUE;
}
public ApiEndpoint instancesHeartbeat() {
return InstancesHeartbeatEndpoint.VALUE;
}
public ApiEndpoint instancesMatchesResultsStore() {
return InstancesMatchesResultsStoreEndpoint.VALUE;
}
public ApiEndpoint instancesPlayersPresenceStore() {
return InstancesPlayersPresenceStoreEndpoint.VALUE;
}
public ApiEndpoint instancesRegister() {
return InstancesRegisterEndpoint.VALUE;
}
public ApiEndpoint instancesShutdown() {
return InstancesShutdownEndpoint.VALUE;
}
public ApiEndpoint profile() {
return ProfileEndpoint.VALUE;
}
}
@@ -0,0 +1,76 @@
package net.kewwbec.network.generated.api.v1.serviceaccounts;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
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,
UpdateEndpoint.VALUE
);
private ServiceAccountsApi() {
}
public String domain() {
return "service-accounts";
}
public int endpointCount() {
return ENDPOINTS.size();
}
public List<ApiEndpoint> endpoints() {
return ENDPOINTS;
}
public ApiEndpoint endpoint(String routeName) {
for (ApiEndpoint endpoint : ENDPOINTS) {
if (endpoint.name().equals(routeName)) {
return endpoint;
}
}
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;
}
public ApiEndpoint tokensIndex() {
return TokensIndexEndpoint.VALUE;
}
public ApiEndpoint tokensRotate() {
return TokensRotateEndpoint.VALUE;
}
public ApiEndpoint tokensStore() {
return TokensStoreEndpoint.VALUE;
}
public ApiEndpoint update() {
return UpdateEndpoint.VALUE;
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.serviceaccounts;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class TokensDestroyEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.service-accounts.tokens.destroy",
List.of("DELETE"),
"/api/v1/service-accounts/{serviceAccount}/tokens/{tokenId}",
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 RouteParameter("tokenId", new TypeDescriptor("string", true, false))),
new RequestDescriptor("App\\Domains\\Authorization\\Http\\Requests\\RevokeServiceAccountTokenRequest", "body", List.of(new RequestField("reason", List.of(new RuleDescriptor("string", null, "nullable"), new RuleDescriptor("string", null, "string"), new RuleDescriptor("string", null, "max:255"))))),
new ResponseDescriptor("json_response", new TypeDescriptor("Illuminate\\Http\\JsonResponse", false, false))
)
;
private TokensDestroyEndpoint() {
}
}
@@ -0,0 +1,28 @@
package net.kewwbec.network.generated.api.v1.serviceaccounts;
import java.util.List;
import net.kewwbec.network.generated.api.v1.shared.ApiEndpoint;
import net.kewwbec.network.generated.api.v1.shared.RequestDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RequestField;
import net.kewwbec.network.generated.api.v1.shared.ResponseDescriptor;
import net.kewwbec.network.generated.api.v1.shared.RouteParameter;
import net.kewwbec.network.generated.api.v1.shared.RuleDescriptor;
import net.kewwbec.network.generated.api.v1.shared.TypeDescriptor;
public final class TokensIndexEndpoint {
public static final ApiEndpoint VALUE =
new ApiEndpoint(
"api.v1.service-accounts.tokens.index",
List.of("GET"),
"/api/v1/service-accounts/{serviceAccount}/tokens",
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\\ListServiceAccountTokensRequest", "query", List.of(new RequestField("include_expired", List.of(new RuleDescriptor("string", null, "sometimes"), new RuleDescriptor("string", null, "boolean"))))),
new ResponseDescriptor("json_resource", new TypeDescriptor("Illuminate\\Http\\Resources\\Json\\AnonymousResourceCollection", false, false))
)
;
private TokensIndexEndpoint() {
}
}

Some files were not shown because too many files have changed in this diff Show More