Add web-api module with OpenAPI integration, enable chat logs forwarding to web backend, and update dependencies across modules.

This commit is contained in:
akastijn 2026-07-18 20:21:25 +02:00
parent dc513ac2f0
commit 5a508fdf28
10 changed files with 356 additions and 24 deletions

View File

@ -3,6 +3,9 @@ plugins {
} }
dependencies { dependencies {
implementation(project(":web-api")) // Web-API
compileOnly("org.projectlombok:lombok:1.18.46")
annotationProcessor("org.projectlombok:lombok:1.18.46")
// Cosmos // Cosmos
compileOnly("com.alttd.cosmos:cosmos-api:1.21.8-R0.1-SNAPSHOT") { compileOnly("com.alttd.cosmos:cosmos-api:1.21.8-R0.1-SNAPSHOT") {
isChanging = true isChanging = true

View File

@ -1,6 +1,9 @@
package com.alttd.chat.objects.chat_log; package com.alttd.chat.objects.chat_log;
import com.alttd.altitudeweb.model.ChatMessageDto;
import com.alttd.chat.objects.BatchInsertable; import com.alttd.chat.objects.BatchInsertable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -11,24 +14,21 @@ import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
@AllArgsConstructor
public class ChatLog implements BatchInsertable { public class ChatLog implements BatchInsertable {
@Getter
private final UUID uuid; private final UUID uuid;
@Getter
private final Instant timestamp; private final Instant timestamp;
private final String server; private final String server;
@Getter
private final String message; private final String message;
@Getter
private final Component miniMessage; private final Component miniMessage;
@Getter
private final boolean blocked; private final boolean blocked;
protected ChatLog(UUID uuid, Instant timestamp, String server, String message, Component miniMessage, boolean blocked) {
this.uuid = uuid;
this.timestamp = timestamp;
this.server = server;
this.message = message;
this.miniMessage = miniMessage;
this.blocked = blocked;
}
@Override @Override
public void prepareStatement(@NotNull PreparedStatement preparedStatement) throws SQLException { public void prepareStatement(@NotNull PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setString(1, uuid.toString()); preparedStatement.setString(1, uuid.toString());
@ -41,19 +41,17 @@ public class ChatLog implements BatchInsertable {
preparedStatement.setInt(6, blocked ? 1 : 0); preparedStatement.setInt(6, blocked ? 1 : 0);
} }
public UUID getUuid() { public ChatMessageDto toDto() {
return uuid; ChatMessageDto chatMessageDto = new ChatMessageDto();
} if (miniMessage == null) {
throw new IllegalArgumentException("MiniMessage cannot be null");
public Instant getTimestamp() { }
return timestamp; chatMessageDto.setMessage(GsonComponentSerializer.gson().serialize(miniMessage));
} chatMessageDto.setChannel(ChatMessageDto.ChannelEnum.CHAT);
chatMessageDto.setServer(server);
public String getMessage() { chatMessageDto.setTimestamp(timestamp);
return message; chatMessageDto.setUuid(uuid);
} chatMessageDto.setBlocked(blocked);
return chatMessageDto;
public boolean isBlocked() {
return blocked;
} }
} }

View File

@ -16,6 +16,7 @@ import java.util.concurrent.*;
public class ChatLogHandler { public class ChatLogHandler {
private final ChatLogWebHandler chatLogWebHandler = new ChatLogWebHandler();
private static ChatLogHandler instance = null; private static ChatLogHandler instance = null;
private ScheduledExecutorService executorService = null; private ScheduledExecutorService executorService = null;
@ -123,7 +124,9 @@ public class ChatLogHandler {
} }
public void addChatLog(UUID uuid, String server, String message, Component miniMessage, boolean blocked) { public void addChatLog(UUID uuid, String server, String message, Component miniMessage, boolean blocked) {
addLog(new ChatLog(uuid, Instant.now(), server, message, miniMessage, blocked)); ChatLog chatLog = new ChatLog(uuid, Instant.now(), server, message, miniMessage, blocked);
addLog(chatLog);
chatLogWebHandler.forwardChatLogToWeb(chatLog);
} }
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) { public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {

View File

@ -0,0 +1,96 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.altitudeweb.api.ChatApi;
import com.alttd.altitudeweb.invoker.ApiClient;
import com.alttd.altitudeweb.invoker.ApiException;
import com.alttd.altitudeweb.model.ChatMessageDto;
import com.alttd.chat.util.ALogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatLogWebHandler {
private static final int MAX_QUEUE_SIZE = 100;
private static final Logger log = LoggerFactory.getLogger(ChatLogWebHandler.class);
private final Queue<ChatLog> queue = new ConcurrentLinkedQueue<>();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final ChatApi chatApi;
public ChatLogWebHandler() {
ApiClient apiClient = new ApiClient();
apiClient.setBasePath("http://10.0.0.109");
chatApi = new ChatApi(apiClient);
}
private volatile boolean sending;
public void forwardChatLogToWeb(ChatLog chatLog) {
if (queue.size() >= MAX_QUEUE_SIZE) {
queue.clear();
log.error("Chat log queue overflow, is the web backend still running?");
}
queue.add(chatLog);
triggerSend();
}
private void triggerSend() {
if (sending) {
return;
}
synchronized (this) {
if (sending || queue.isEmpty()) {
return;
}
sending = true;
}
executor.execute(this::sendBatch);
}
private void sendBatch() {
try {
ArrayList<ChatLog> chatLogList = new ArrayList<>();
ChatLog log;
while ((log = queue.poll()) != null) {
if (log.getMiniMessage() == null) {
ALogger.warn("No mini message for message, skipping");
continue;
}
chatLogList.add(log);
}
List<ChatMessageDto> batch = chatLogList.stream().map(ChatLog::toDto).toList();
if (!chatLogList.isEmpty()) {
try {
chatApi.sendChatMessages(batch);
} catch (ApiException e) {
ALogger.error("Failed to send chat messages to web backend, " +
"adding messages back to queue for another try", e
);
queue.addAll(chatLogList);
}
}
} finally {
synchronized (this) {
sending = false;
}
triggerSend();
}
}
}

View File

@ -11,6 +11,8 @@ dependencies {
compileOnly("com.alttd.cosmos:cosmos-api:1.21.8-R0.1-SNAPSHOT") { compileOnly("com.alttd.cosmos:cosmos-api:1.21.8-R0.1-SNAPSHOT") {
isChanging = true isChanging = true
} }
compileOnly("org.projectlombok:lombok:1.18.46")
annotationProcessor("org.projectlombok:lombok:1.18.46")
compileOnly("com.gitlab.ruany:LiteBansAPI:0.6.1") // move to proxy compileOnly("com.gitlab.ruany:LiteBansAPI:0.6.1") // move to proxy
compileOnly("org.apache.commons:commons-lang3:3.17.0") // needs an alternative, already removed from upstream api and will be removed in server compileOnly("org.apache.commons:commons-lang3:3.17.0") // needs an alternative, already removed from upstream api and will be removed in server
compileOnly("net.luckperms:api:5.5") // Luckperms compileOnly("net.luckperms:api:5.5") // Luckperms

View File

@ -35,3 +35,5 @@ pluginManagement {
gradlePluginPortal() gradlePluginPortal()
} }
} }
include("web-api")

View File

@ -5,6 +5,8 @@ plugins {
dependencies { dependencies {
implementation(project(":api")) // API implementation(project(":api")) // API
compileOnly("org.projectlombok:lombok:1.18.46")
annotationProcessor("org.projectlombok:lombok:1.18.46")
compileOnly("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT") compileOnly("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
annotationProcessor("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT") annotationProcessor("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
implementation("mysql:mysql-connector-java:8.0.33") // mysql implementation("mysql:mysql-connector-java:8.0.33") // mysql

69
web-api/build.gradle.kts Normal file
View File

@ -0,0 +1,69 @@
plugins {
java
id("org.openapi.generator") version "7.12.0"
}
group = "com.alttd.chat"
version = "2.0.0-SNAPSHOT"
dependencies {
implementation("io.swagger.core.v3:swagger-annotations:2.2.28")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("com.google.code.gson:gson:2.12.1")
implementation("io.gsonfire:gson-fire:1.9.0")
implementation("javax.annotation:javax.annotation-api:1.3.2")
implementation("jakarta.annotation:jakarta.annotation-api:2.1.1")
}
sourceSets {
main {
java {
srcDir("${projectDir}/build/generated-sources/client/src/main/java")
}
}
}
tasks.named<org.openapitools.generator.gradle.plugin.tasks.GenerateTask>("openApiGenerate") {
generatorName.set("java")
inputSpec.set("${projectDir}/src/main/resources/chat-api.yml")
configFile.set("${projectDir}/src/main/resources/config_backend.json")
outputDir.set("${projectDir}/build/generated-sources/client")
apiPackage.set("com.alttd.altitudeweb.api")
modelPackage.set("com.alttd.altitudeweb.model")
modelNameSuffix.set("Dto")
additionalProperties.set(
mapOf(
"dateLibrary" to "java8",
"library" to "okhttp-gson"
)
)
typeMappings.set(mapOf("OffsetDateTime" to "Instant"))
importMappings.set(mapOf("OffsetDateTime" to "java.time.Instant"))
generateApiTests.set(false)
generateApiDocumentation.set(false)
generateModelTests.set(false)
generateModelDocumentation.set(false)
generateAliasAsModel.set(true)
}
sourceSets {
main {
java {
srcDir(layout.buildDirectory.dir("generated/openapi/src/main/java"))
}
}
}
tasks.compileJava {
dependsOn("openApiGenerate")
}

View File

@ -0,0 +1,135 @@
openapi: 3.0.3
info:
title: Minecraft Network API
version: 1.0.0
servers:
- url: http://localhost:8080/api
tags:
- name: chat
description: Data for displaying Chat messages to clients
paths:
/chat/send/chat/message:
post:
tags:
- chat
summary: Sends one or more chat messages
operationId: sendChatMessages
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ChatMessage"
responses:
"202":
description: Accepted
/chat/send/servers/state:
post:
tags:
- chat
summary: Update server states
operationId: updateServerStates
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ServerState"
responses:
"202":
description: Accepted
components:
schemas:
User:
type: object
required:
- uuid
- name
- styledName
properties:
uuid:
type: string
format: uuid
name:
type: string
styledName:
type: string
ChatMessage:
type: object
required:
- uuid
- message
- channel
- server
- timestamp
- blocked
properties:
uuid:
type: string
format: uuid
channel:
type: string
enum:
- CHAT
- PARTY
- AC
- GAC
message:
type: string
server:
type: string
timestamp:
type: string
format: date-time
blocked:
type: boolean
PrivateMessage:
type: object
required:
- sender
- receiver
- message
- senderServer
- receiverServer
properties:
sender:
type: string
format: uuid
receiver:
type: string
format: uuid
message:
type: string
senderServer:
type: string
receiverServer:
type: string
Server:
type: object
properties:
name:
type: string
players:
type: array
items:
$ref: "#/components/schemas/User"
ServerState:
type: object
properties:
servers:
type: array
items:
$ref: "#/components/schemas/Server"

View File

@ -0,0 +1,22 @@
{
"library": "okhttp-gson",
"hideGenerationTimestamp": true,
"modelPackage": "com.alttd.altitudeweb.model",
"apiPackage": "com.alttd.altitudeweb.api",
"invokerPackage": "com.alttd.altitudeweb.invoker",
"serializableModel": true,
"openApiNullable": false,
"useTags": true,
"generateApis": true,
"generateApiTests": false,
"generateApiDocumentation": false,
"generateModels": true,
"generateModelTests": false,
"generateSupportingFiles": true,
"modelNameSuffix": "Dto",
"generateTests": false,
"dateLibrary": "java8",
"enumUnknownDefaultCase": true,
"disallowAdditionalPropertiesIfNotPresent": false,
"useJakartaEe": true
}