diff --git a/pom.xml b/pom.xml
index a539756..3cbd775 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
jar
- 21
+ 25
1.18.44
${java.version}
${java.version}
diff --git a/src/main/java/net/hypixel/nerdbot/tickets/listener/TicketListener.java b/src/main/java/net/hypixel/nerdbot/tickets/listener/TicketListener.java
index 02c04df..6647d8e 100644
--- a/src/main/java/net/hypixel/nerdbot/tickets/listener/TicketListener.java
+++ b/src/main/java/net/hypixel/nerdbot/tickets/listener/TicketListener.java
@@ -32,9 +32,12 @@
import net.hypixel.nerdbot.tickets.model.TicketFieldValue;
import net.hypixel.nerdbot.discord.util.DiscordBotEnvironment;
+import net.hypixel.nerdbot.marmalade.validation.Validator;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* Primary event listener for the ticketing system.
@@ -177,6 +180,37 @@ public void onModalInteraction(ModalInteractionEvent event) {
if (templateOpt.isPresent()) {
TicketTemplate template = templateOpt.get();
+
+ // Validate all fields at once so the user sees every problem in one reply
+ Validator validator = Validator.create();
+ for (TicketTemplateField field : template.getFields()) {
+ ModalMapping mapping = event.getValue(field.getId());
+ String value = mapping != null ? mapping.getAsString() : "";
+
+ if (field.isRequired()) {
+ validator.notBlank(value, field.getLabel());
+ }
+ if (value != null && !value.isEmpty()) {
+ if (field.getMinLength() > 0) {
+ validator.minLength(value, field.getMinLength(), field.getLabel());
+ }
+ if (field.getMaxLength() > 0) {
+ validator.maxLength(value, field.getMaxLength(), field.getLabel());
+ }
+ }
+ }
+
+ if (validator.hasErrors()) {
+ String errorMessage = validator.getErrors().stream()
+ .map(e -> "- " + e.fieldName() + ": " + e.message())
+ .collect(Collectors.joining("\n"));
+ event.getHook().editOriginal("Please fix the following errors:\n" + errorMessage).queue(
+ null,
+ error -> log.error("Failed to send validation error reply to user {}", userId, error)
+ );
+ return;
+ }
+
for (TicketTemplateField field : template.getFields()) {
ModalMapping mapping = event.getValue(field.getId());
if (mapping != null && !mapping.getAsString().isEmpty()) {
diff --git a/src/main/java/net/hypixel/nerdbot/tickets/repository/TicketRepository.java b/src/main/java/net/hypixel/nerdbot/tickets/repository/TicketRepository.java
index 46c9bf1..0c861be 100644
--- a/src/main/java/net/hypixel/nerdbot/tickets/repository/TicketRepository.java
+++ b/src/main/java/net/hypixel/nerdbot/tickets/repository/TicketRepository.java
@@ -17,6 +17,8 @@
import net.hypixel.nerdbot.tickets.model.Ticket;
import net.hypixel.nerdbot.tickets.model.TicketReservationResult;
import net.hypixel.nerdbot.tickets.model.TicketStatus;
+import net.hypixel.nerdbot.marmalade.exception.RepositoryException;
+import net.hypixel.nerdbot.marmalade.functional.Result;
import net.hypixel.nerdbot.marmalade.storage.repository.Repository;
import org.bson.Document;
import org.bson.conversions.Bson;
@@ -107,31 +109,38 @@ public com.mongodb.client.result.DeleteResult deleteFromDatabase(String id) {
/**
* Override findById to use integer type for ticketNumber filter.
+ * Checks the open ticket cache, then the closed ticket cache, then MongoDB.
*/
@Override
- public Ticket findById(String id) {
+ public Result findById(String id) {
Ticket cachedObject = getCache().getIfPresent(id);
if (cachedObject != null) {
log.debug("Found ticket with ID {} in cache", id);
- return cachedObject;
+ return Result.success(cachedObject);
}
// Also check closed ticket cache
Ticket closedTicket = closedTicketCache.getIfPresent(id);
if (closedTicket != null) {
log.debug("Found ticket with ID {} in closed cache", id);
- return closedTicket;
+ return Result.success(closedTicket);
}
- // Use integer for ticketNumber filter to match MongoDB's storage type
- Document document = getMongoCollection().find(Filters.eq("ticketNumber", Integer.parseInt(id))).first();
- if (document != null) {
- Ticket ticket = documentToEntity(document);
- cacheTicketAppropriately(ticket);
- return ticket;
+ try {
+ // Use integer for ticketNumber filter to match MongoDB's storage type
+ Document document = getMongoCollection().find(Filters.eq("ticketNumber", Integer.parseInt(id))).first();
+ if (document != null) {
+ Ticket ticket = documentToEntity(document);
+ cacheTicketAppropriately(ticket);
+ return Result.success(ticket);
+ }
+ } catch (com.mongodb.MongoException e) {
+ log.error("Database error looking up ticket with ID {}", id, e);
+ return Result.failure(new RepositoryException("Database error looking up ticket ID: " + id, e));
}
- return null;
+ log.debug("Ticket with ID {} not found in cache or database", id);
+ return Result.failure(new RepositoryException("Not found: " + id));
}
/**
@@ -237,10 +246,13 @@ public Optional findByChannelId(String channelId) {
String ticketId = channelIdIndex.get(channelId);
if (ticketId != null) {
// Try open ticket cache first (most common case)
- Ticket cachedTicket = findById(ticketId);
- if (cachedTicket != null) {
- log.debug("Found open ticket {} in cache for thread {}", cachedTicket.getFormattedTicketId(), channelId);
- return Optional.of(cachedTicket);
+ Result findResult = findById(ticketId);
+ if (findResult.isSuccess()) {
+ Ticket cachedTicket = findResult.orElse(null);
+ if (cachedTicket != null) {
+ log.debug("Found open ticket {} in cache for thread {}", cachedTicket.getFormattedTicketId(), channelId);
+ return Optional.of(cachedTicket);
+ }
}
// Try closed ticket cache