Compare commits
13 Commits
0036b323ac
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| be35d92b52 | |||
| 59330bdf84 | |||
| 7a4a0bd4bd | |||
| 7ab54bb8d3 | |||
| b6a2a9d515 | |||
| be9968c345 | |||
| a1f96f58e6 | |||
| 839df5f059 | |||
| 647fe81537 | |||
| 772dabc708 | |||
| bbd0265f2e | |||
| b0d6afbd4c | |||
| b2045653ef |
+57
-53
@@ -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
@@ -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/
|
||||
@@ -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
@@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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()`.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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();
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
+41
@@ -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() {
|
||||
}
|
||||
}
|
||||
+66
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
+76
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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() {
|
||||
}
|
||||
}
|
||||
+28
@@ -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
Reference in New Issue
Block a user