Integrate ChatLogHandler across chat modules and add centralized message logging with validation through ChatLogMapper.
This commit is contained in:
@@ -12,6 +12,10 @@ dependencies {
|
||||
}
|
||||
compileOnly("org.spongepowered:configurate-yaml:4.2.0") // Configurate
|
||||
compileOnly("net.luckperms:api:5.5") // Luckperms
|
||||
|
||||
//API validation
|
||||
implementation("org.hibernate.validator:hibernate-validator:9.0.1.Final")
|
||||
implementation("org.glassfish:jakarta.el:5.0.0-M1")
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
||||
@@ -16,11 +16,14 @@ public class ChatLogQueries {
|
||||
|
||||
protected static void createChatLogTable() {
|
||||
String nicknamesTableQuery = """
|
||||
CREATE TABLE chat_log (
|
||||
CREATE TABLE IF NOT EXISTSq:Q chat_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
uuid CHAR(36) NOT NULL,
|
||||
time_stamp TIMESTAMP(6) NOT NULL,
|
||||
server VARCHAR(50) NOT NULL,
|
||||
type VARCHAR(16) NOT NULL DEFAULT 'public',
|
||||
channel VARCHAR(36) DEFAULT NULL,
|
||||
receiver CHAR(36) DEFAULT NULL,
|
||||
chat_message VARCHAR(300) NOT NULL,
|
||||
mini_message JSON,
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
@@ -38,7 +41,7 @@ public class ChatLogQueries {
|
||||
}
|
||||
|
||||
public static @NotNull CompletableFuture<Boolean> storeMessages(HashMap<UUID, List<ChatLog>> chatMessages) {
|
||||
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, type, channel, receiver, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = DatabaseConnection.createTransactionConnection()) {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
|
||||
@@ -67,7 +70,7 @@ public class ChatLogQueries {
|
||||
}
|
||||
|
||||
public static @NotNull CompletableFuture<List<ChatLog>> retrieveMessages(ChatLogHandler chatLogHandler, UUID uuid, Duration duration, String server) {
|
||||
String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ?";
|
||||
String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ? AND type = 'public'";
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = DatabaseConnection.getConnection()) {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(query);
|
||||
@@ -77,8 +80,7 @@ public class ChatLogQueries {
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
List<ChatLog> chatLogs = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
ChatLog chatLog = chatLogHandler.loadFromResultSet(resultSet);
|
||||
chatLogs.add(chatLog);
|
||||
chatLogHandler.loadFromResultSet(resultSet).ifPresent(chatLogs::add);
|
||||
}
|
||||
return chatLogs;
|
||||
} catch (SQLException sqlException) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.alttd.chat.database;
|
||||
|
||||
|
||||
import com.alttd.chat.config.Config;
|
||||
|
||||
import java.sql.Connection;
|
||||
@@ -27,6 +26,7 @@ public class DatabaseConnection {
|
||||
|
||||
/**
|
||||
* Opens the connection if it's not already open.
|
||||
*
|
||||
* @throws SQLException If it can't create the connection.
|
||||
*/
|
||||
public void openConnection() throws SQLException {
|
||||
@@ -45,14 +45,16 @@ public class DatabaseConnection {
|
||||
}
|
||||
|
||||
connection = DriverManager.getConnection(
|
||||
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true"+
|
||||
"&useSSL=false",
|
||||
Config.USERNAME, Config.PASSWORD);
|
||||
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
|
||||
"&useSSL=false&preserveInstants=true",
|
||||
Config.USERNAME, Config.PASSWORD
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection for the database
|
||||
*
|
||||
* @return Returns the connection.
|
||||
*/
|
||||
public static Connection getConnection() {
|
||||
@@ -69,13 +71,15 @@ public class DatabaseConnection {
|
||||
* Creates a transactional database connection.
|
||||
*
|
||||
* @return A {@code Connection} object representing the transactional database connection.
|
||||
*
|
||||
* @throws SQLException If there is an error creating the database connection.
|
||||
*/
|
||||
public static Connection createTransactionConnection() throws SQLException {
|
||||
connection = DriverManager.getConnection(
|
||||
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true"+
|
||||
"&useSSL=false",
|
||||
Config.USERNAME, Config.PASSWORD);
|
||||
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
|
||||
"&useSSL=false&preserveInstants=true",
|
||||
Config.USERNAME, Config.PASSWORD
|
||||
);
|
||||
connection.setAutoCommit(false);
|
||||
return connection;
|
||||
}
|
||||
@@ -88,4 +92,4 @@ public class DatabaseConnection {
|
||||
return connection != null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ public abstract class Toggleable {
|
||||
|
||||
public static Toggleable getToggleable(UUID uuid) {
|
||||
for (Toggleable toggleableClass : togglableClasses) {
|
||||
if (toggleableClass.isToggled(uuid))
|
||||
if (toggleableClass.isToggled(uuid)) {
|
||||
return toggleableClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.alttd.chat.objects.chat_log;
|
||||
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import com.alttd.chat.objects.BatchInsertable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -12,22 +11,20 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.UUID;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class ChatLog implements BatchInsertable {
|
||||
|
||||
@Getter
|
||||
private final UUID uuid;
|
||||
@Getter
|
||||
private final Instant timestamp;
|
||||
private final String server;
|
||||
@Getter
|
||||
private final ChatLogType type;
|
||||
private final String channel;
|
||||
private final String receiver;
|
||||
private final String message;
|
||||
@Getter
|
||||
private final Component miniMessage;
|
||||
@Getter
|
||||
private final boolean blocked;
|
||||
|
||||
@Override
|
||||
@@ -35,24 +32,13 @@ public class ChatLog implements BatchInsertable {
|
||||
preparedStatement.setString(1, uuid.toString());
|
||||
preparedStatement.setTimestamp(2, Timestamp.from(timestamp));
|
||||
preparedStatement.setString(3, server);
|
||||
preparedStatement.setString(4, message);
|
||||
preparedStatement.setString(5,
|
||||
preparedStatement.setString(4, ChatLogTypeMapper.toDb(type));
|
||||
preparedStatement.setString(5, channel);
|
||||
preparedStatement.setString(6, receiver);
|
||||
preparedStatement.setString(7, message);
|
||||
preparedStatement.setString(8,
|
||||
miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)
|
||||
);
|
||||
preparedStatement.setInt(6, blocked ? 1 : 0);
|
||||
}
|
||||
|
||||
public ChatMessageDto toDto() {
|
||||
ChatMessageDto chatMessageDto = new ChatMessageDto();
|
||||
if (miniMessage == null) {
|
||||
throw new IllegalArgumentException("MiniMessage cannot be null");
|
||||
}
|
||||
chatMessageDto.setMessage(GsonComponentSerializer.gson().serialize(miniMessage));
|
||||
chatMessageDto.setChannel(ChatMessageDto.ChannelEnum.CHAT);
|
||||
chatMessageDto.setServer(server);
|
||||
chatMessageDto.setTimestamp(timestamp.atOffset(ZoneOffset.UTC));
|
||||
chatMessageDto.setUuid(uuid);
|
||||
chatMessageDto.setBlocked(blocked);
|
||||
return chatMessageDto;
|
||||
preparedStatement.setInt(9, blocked ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.alttd.chat.objects.chat_log;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.database.ChatLogQueries;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -14,6 +15,7 @@ import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@Slf4j
|
||||
public class ChatLogHandler {
|
||||
|
||||
private final ChatLogWebHandler chatLogWebHandler = new ChatLogWebHandler();
|
||||
@@ -111,20 +113,48 @@ public class ChatLogHandler {
|
||||
});
|
||||
}
|
||||
|
||||
public ChatLog loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException {
|
||||
public Optional<ChatLog> loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException {
|
||||
UUID chatLogUUID = UUID.fromString(resultSet.getString("uuid"));
|
||||
Instant chatTimestamp = resultSet.getTimestamp("time_stamp").toInstant();
|
||||
String server = resultSet.getString("server");
|
||||
String stringType = resultSet.getString("type");
|
||||
ChatLogType type;
|
||||
try {
|
||||
type = ChatLogTypeMapper.fromDb(stringType);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load chat log from result set: {}", e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
String channel = resultSet.getString("channel");
|
||||
String receiver = resultSet.getString("receiver");
|
||||
String chatMessage = resultSet.getString("chat_message");
|
||||
String stringMiniMessage = resultSet.getString("mini_message");
|
||||
Component miniMessage = stringMiniMessage == null ? null : GsonComponentSerializer.gson().deserialize(
|
||||
stringMiniMessage);
|
||||
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
|
||||
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, miniMessage, chatMessageBlocked);
|
||||
return Optional.of(new ChatLog(chatLogUUID,
|
||||
chatTimestamp,
|
||||
server,
|
||||
type,
|
||||
channel,
|
||||
receiver,
|
||||
chatMessage,
|
||||
miniMessage,
|
||||
chatMessageBlocked
|
||||
));
|
||||
}
|
||||
|
||||
public void addChatLog(UUID uuid, String server, String message, Component miniMessage, boolean blocked) {
|
||||
ChatLog chatLog = new ChatLog(uuid, Instant.now(), server, message, miniMessage, blocked);
|
||||
public void addChatLog(UUID uuid, String server, String message, ChatLogType chatLogType, String channel, String receiver, Component miniMessage, boolean blocked) {
|
||||
ChatLog chatLog = new ChatLog(uuid,
|
||||
Instant.now(),
|
||||
server,
|
||||
chatLogType,
|
||||
channel,
|
||||
receiver,
|
||||
message,
|
||||
miniMessage,
|
||||
blocked
|
||||
);
|
||||
addLog(chatLog);
|
||||
chatLogWebHandler.forwardChatLogToWeb(chatLog);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.alttd.chat.objects.chat_log;
|
||||
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class ChatLogMapper {
|
||||
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
Validator validator = factory.getValidator();
|
||||
|
||||
public Optional<ChatMessageDto> toDto(ChatLog chatLog) {
|
||||
ChatMessageDto chatMessageDto = new ChatMessageDto();
|
||||
if (chatLog.getMiniMessage() == null) {
|
||||
throw new IllegalArgumentException("MiniMessage cannot be null");
|
||||
}
|
||||
chatMessageDto.setUuid(chatLog.getUuid());
|
||||
chatMessageDto.setTimestamp(chatLog.getTimestamp().atOffset(ZoneOffset.UTC));
|
||||
chatMessageDto.setServer(chatLog.getServer());
|
||||
chatMessageDto.setType(ChatLogTypeMapper.toDto(chatLog.getType()));
|
||||
chatMessageDto.setChannel(chatLog.getChannel());
|
||||
chatMessageDto.setReceiver(chatLog.getReceiver());
|
||||
chatMessageDto.setMessage(GsonComponentSerializer.gson().serialize(chatLog.getMiniMessage()));
|
||||
chatMessageDto.setBlocked(chatLog.isBlocked());
|
||||
if (checkIsInvalid(chatMessageDto)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(chatMessageDto);
|
||||
}
|
||||
|
||||
private boolean checkIsInvalid(ChatMessageDto chatMessageDto) {
|
||||
Set<ConstraintViolation<ChatMessageDto>> violations = validator.validate(chatMessageDto);
|
||||
if (violations.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
log.error("Validation failed for chat message: {}, {}", chatMessageDto, violations);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.alttd.chat.objects.chat_log;
|
||||
|
||||
public enum ChatLogType {
|
||||
PUBLIC,
|
||||
GLOBAL,
|
||||
PARTY,
|
||||
GAC,
|
||||
MSG,
|
||||
CUSTOM;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.alttd.chat.objects.chat_log;
|
||||
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ChatLogTypeMapper {
|
||||
|
||||
public static String toDb(ChatLogType chatLogType) {
|
||||
return switch (chatLogType) {
|
||||
case PUBLIC -> "public";
|
||||
case GLOBAL -> "global";
|
||||
case PARTY -> "party";
|
||||
case GAC -> "gac";
|
||||
case MSG -> "msg";
|
||||
case CUSTOM -> "custom";
|
||||
};
|
||||
}
|
||||
|
||||
public ChatMessageDto.TypeEnum toDto(ChatLogType chatLogType) {
|
||||
return switch (chatLogType) {
|
||||
case PUBLIC -> ChatMessageDto.TypeEnum.PUBLIC;
|
||||
case GLOBAL -> ChatMessageDto.TypeEnum.GLOBAL;
|
||||
case PARTY -> ChatMessageDto.TypeEnum.PARTY;
|
||||
case GAC -> ChatMessageDto.TypeEnum.GAC;
|
||||
case MSG -> ChatMessageDto.TypeEnum.MSG;
|
||||
case CUSTOM -> ChatMessageDto.TypeEnum.CUSTOM;
|
||||
};
|
||||
}
|
||||
|
||||
public ChatLogType fromDb(String dbType) {
|
||||
return switch (dbType) {
|
||||
case "public" -> ChatLogType.PUBLIC;
|
||||
case "global" -> ChatLogType.GLOBAL;
|
||||
case "party" -> ChatLogType.PARTY;
|
||||
case "gac" -> ChatLogType.GAC;
|
||||
case "msg" -> ChatLogType.MSG;
|
||||
case "custom" -> ChatLogType.CUSTOM;
|
||||
default -> throw new IllegalArgumentException("Invalid chat log type: " + dbType);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -74,7 +75,11 @@ public class ChatLogWebHandler {
|
||||
chatLogList.add(log);
|
||||
}
|
||||
|
||||
List<ChatMessageDto> batch = chatLogList.stream().map(ChatLog::toDto).toList();
|
||||
List<ChatMessageDto> batch = chatLogList.stream()
|
||||
.map(ChatLogMapper::toDto)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
|
||||
if (!chatLogList.isEmpty()) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user