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