Skip to content

Commit

Permalink
Handle event updates asynchronously with the spring event system
Browse files Browse the repository at this point in the history
Fixed some bugs, including missing notification updates and missing event updates after squad or slot renames
  • Loading branch information
Alf-Melmac committed Aug 5, 2023
1 parent e69d341 commit 662ef03
Show file tree
Hide file tree
Showing 22 changed files with 344 additions and 240 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.webalf.slotbot.configuration;

import de.webalf.slotbot.processor.HibernateInterceptor;
import de.webalf.slotbot.service.update.HibernateInterceptor;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Configuration;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/de/webalf/slotbot/model/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import de.webalf.slotbot.exception.BusinessRuntimeException;
import de.webalf.slotbot.service.GuildService;
import de.webalf.slotbot.service.bot.EventNotificationService;
import de.webalf.slotbot.service.event.EventArchiveEvent;
import de.webalf.slotbot.model.event.EventArchiveEvent;
import de.webalf.slotbot.util.EventUtils;
import de.webalf.slotbot.util.StringUtils;
import jakarta.persistence.*;
Expand Down
22 changes: 0 additions & 22 deletions src/main/java/de/webalf/slotbot/model/User.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
package de.webalf.slotbot.model;

import de.webalf.slotbot.util.DateUtils;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static de.webalf.slotbot.util.DateUtils.getLocalDateTimeComparator;

/**
* @author Alf
* @since 06.09.2020
Expand All @@ -32,9 +26,6 @@ public class User extends AbstractDiscordIdEntity {
@Column(name = "user_external_calendar", nullable = false)
private boolean externalCalendarIntegrationActive = false;

@OneToMany(mappedBy = "user")
private Set<Slot> slots = new HashSet<>();

@OneToMany(mappedBy = "user")
private Set<GuildUsers> guilds = new HashSet<>();

Expand All @@ -53,19 +44,6 @@ public boolean isDefaultUser() {
return getId() == DEFAULT_USER_ID;
}

public Optional<LocalDateTime> getLastEventDateTime() {
return getSlots().stream()
.map(Slot::getSquad)
.map(Squad::getEvent)
.map(Event::getDateTime)
.filter(dateTime -> dateTime.isBefore(DateUtils.now()))
.min(getLocalDateTimeComparator());
}

public List<Event> getSlottedEvents() {
return getSlots().stream().map(Slot::getEvent).toList();
}

public Set<Guild> getGuilds() {
return guilds.stream().map(GuildUsers::getGuild).collect(Collectors.toUnmodifiableSet());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package de.webalf.slotbot.service.event;
package de.webalf.slotbot.model.event;

import de.webalf.slotbot.model.Event;
import lombok.Builder;
import lombok.NonNull;

/**
* @author Alf
* @see EventArchiveInitializedEvent
* @since 24.07.2023
*/
@Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.webalf.slotbot.service.event;
package de.webalf.slotbot.model.event;

import de.webalf.slotbot.model.Event;
import lombok.Builder;
Expand All @@ -9,6 +9,7 @@
* @param guild Archiving guild
* @param discordGuild Archiving guild
* @author Alf
* @see EventArchiveEvent
* @since 23.07.2023
*/
@Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.webalf.slotbot.model.event;

/**
* Event for metadata changes to an event:
* <ul>
* <li>Event name</li>
* <li>Hidden status</li>
* <li>Date time</li>
* </ul>
*
* @author Alf
* @since 05.08.2023
*/
public record EventMetadataUpdateEvent(long eventId) {}
13 changes: 13 additions & 0 deletions src/main/java/de/webalf/slotbot/model/event/EventUpdateEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.webalf.slotbot.model.event;

import lombok.Builder;

/**
* Event for any changes to an event.
*
* @param slotlistChanged marks if the slotlist changed
* @author Alf
* @since 05.08.2023
*/
@Builder
public record EventUpdateEvent(long event, boolean embedChanged, boolean slotlistChanged) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package de.webalf.slotbot.model.event;

import de.webalf.slotbot.model.Event;
import de.webalf.slotbot.model.Slot;
import de.webalf.slotbot.model.User;
import lombok.Builder;
import lombok.NonNull;

/**
* Event for slot user change in an event.
*
* @param event event containing the slot
* @param slot slot where the user changed
* @param currentUser new user of the slot
* @param previousUser previous user of the slot
* @author Alf
* @since 05.08.2023
*/
@Builder
public record SlotUserChangedEvent(@NonNull Event event, @NonNull Slot slot, User currentUser, User previousUser) {
/**
* Checks if <code>currentUser</code> is present and is not the {@link User#isDefaultUser() default user}
*/
public boolean currentUserIs() {
return currentUser != null && !currentUser.isDefaultUser();
}

/**
* Checks if <code>previousUser</code> is present and is not the {@link User#isDefaultUser() default user}
*/
public boolean previousUserIs() {
return previousUser != null && !previousUser.isDefaultUser();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import de.webalf.slotbot.model.Event;
import de.webalf.slotbot.model.Guild;
import de.webalf.slotbot.model.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
Expand Down Expand Up @@ -89,4 +90,8 @@ public interface EventRepository extends SuperIdEntityJpaRepository<Event> {
WHERE discordInformation.channel = :channel AND s.user.id <> de.webalf.slotbot.model.User.DEFAULT_USER_ID
""")
List<Long> findAllParticipantIds(@Param("channel") long channel);

Optional<Event> findFirstByOwnerGuildAndSquadListSlotListUserOrderByDateTimeDesc(Guild ownerGuild, User user);

List<Event> findBySquadListSlotListUser(User user);
}
48 changes: 26 additions & 22 deletions src/main/java/de/webalf/slotbot/service/EventCalendarService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
import de.webalf.slotbot.model.EventDiscordInformation;
import de.webalf.slotbot.model.Guild;
import de.webalf.slotbot.model.User;
import de.webalf.slotbot.model.event.EventMetadataUpdateEvent;
import de.webalf.slotbot.model.event.SlotUserChangedEvent;
import de.webalf.slotbot.util.EventCalendarUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.model.Calendar;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -45,14 +49,24 @@ public void rebuildCalendar(@NonNull Guild guild) {
return;
}

if (log.isTraceEnabled()) {
log.trace("Building calendar for guild {} [{}]", guild.getGroupIdentifier(), guild.getId());
}
log.trace("Building calendar for guild {} [{}]", guild.getGroupIdentifier(), guild.getId());
//This may be moved to the guild entity once shared events are persistently matched to foreign guilds
final List<Event> events = eventService.findAllPublicByGuild(guild);
buildAndWrite(events, guild.getId());
}

@EventListener
@Async
public void rebuildCalendar(@NonNull SlotUserChangedEvent changedEvent) {
if (changedEvent.previousUserIs()) {
rebuildCalendar(changedEvent.previousUser());
}

if (changedEvent.currentUserIs()) {
rebuildCalendar(changedEvent.currentUser());
}
}

/**
* Rebuilds the calendar for the given user if {@link User#isExternalCalendarIntegrationActive()}.
* Otherwise, any existing calendar gets deleted
Expand All @@ -63,36 +77,28 @@ public void rebuildCalendar(@NonNull User user) {
if (user.isDefaultUser()) {
return;
}
if (log.isTraceEnabled()) {
log.trace("Building calendar for user {}", user.getId());
}
if (!user.isExternalCalendarIntegrationActive()) {
deleteCalendar(user.getId());
return;
}

buildAndWrite(user.getSlottedEvents(), user.getId());
log.trace("Building calendar for user {}", user.getId());
buildAndWrite(eventService.findEventsOfUser(user), user.getId());
}

/**
* Rebuilds the calendar for the given event
*
* @param event that has changed
*/
private void rebuildCalendars(@NonNull Event event) {
if (log.isTraceEnabled()) {
log.trace("Building calendar for event {} [{}]", event.getName(), event.getId());
}
@EventListener
@Async
public void rebuildCalendars(@NonNull EventMetadataUpdateEvent updateEvent) {
final Event event = eventService.findById(updateEvent.eventId());
log.trace("Creating calendars for event {} [{}]", event.getName(), event.getId());
event.getAllParticipants().forEach(this::rebuildCalendar);
Stream.concat(Stream.of(event.getOwnerGuild()), event.getDiscordInformation().stream().map(EventDiscordInformation::getGuild))
.distinct()
.forEach(this::rebuildCalendar);
}

public void rebuildCalendars(long eventId) {
rebuildCalendars(eventService.findById(eventId));
}

private void buildAndWrite(@NonNull List<Event> events, long id) {
if (events.isEmpty()) {
deleteCalendar(id);
Expand All @@ -102,7 +108,7 @@ private void buildAndWrite(@NonNull List<Event> events, long id) {
log.debug("Building calendar for {}", id);
final Calendar calendar = EventCalendarUtil.buildEventCalendar(events);
writeCalendar(calendar, getCalendarPath(id));
log.trace("Calendar write finished.");
log.trace("Calendar write finished. {} events written", events.size());
}

/**
Expand All @@ -129,9 +135,7 @@ private void writeCalendar(Calendar calendar, @NotBlank String calendarPath) {
*/
private void deleteCalendar(long id) {
try {
if (log.isTraceEnabled()) {
log.trace("Deleting calendar {}", id);
}
log.trace("Deleting calendar {}", id);
final boolean deleted = Files.deleteIfExists(Paths.get(getCalendarPath(id)));
if (deleted && log.isDebugEnabled()) {
log.debug("Deleted calendar of {}", id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import de.webalf.slotbot.model.EventDiscordInformation;
import de.webalf.slotbot.model.dtos.EventDiscordInformationDto;
import de.webalf.slotbot.repository.EventDiscordInformationRepository;
import de.webalf.slotbot.service.event.EventArchiveEvent;
import de.webalf.slotbot.model.event.EventArchiveEvent;
import jakarta.transaction.Transactional;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/de/webalf/slotbot/service/EventService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import de.webalf.slotbot.assembler.website.event.creation.EventPostAssembler;
import de.webalf.slotbot.exception.BusinessRuntimeException;
import de.webalf.slotbot.exception.ForbiddenException;
import de.webalf.slotbot.exception.ResourceNotFoundException;
import de.webalf.slotbot.model.*;
import de.webalf.slotbot.model.dtos.EventDiscordInformationDto;
Expand Down Expand Up @@ -144,6 +143,21 @@ public List<Event> findAllForeignNotAssignedInFuture(long guildId) {
return eventRepository.findAllByDateTimeIsAfterAndNotScheduledAndNotOwnerGuildAndForGuildAndOrderByDateTime(DateUtils.now(), guildId);
}

/**
* Searches for the last event of the given guild the given user is slotted in.
*
* @param user user to search for
* @param ownerGuild event owner guild
* @return Optional containing the last event of the user, or empty if no event is found
*/
public Optional<Event> findLastEventOfUserInGuild(@NonNull User user, @NonNull Guild ownerGuild) {
return eventRepository.findFirstByOwnerGuildAndSquadListSlotListUserOrderByDateTimeDesc(ownerGuild, user);
}

public List<Event> findEventsOfUser(@NonNull User user) {
return eventRepository.findBySquadListSlotListUser(user);
}

/**
* Returns all ids of the {@link User}s slotted in the event associated with the given channelId.
* {@link User#DEFAULT_USER_ID} is filtered out.
Expand Down Expand Up @@ -374,9 +388,6 @@ public Event randomSlot(long channel, UserDto userDto) {
*/
public Event renameSquad(@NonNull Event event, int squadPosition, String squadName) {
final Squad squad = event.findSquadByPosition(squadPosition);
if (squad.isReserve()) {
throw new ForbiddenException("Reserve may not be renamed.");
}
squad.setName(squadName);
return event;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import de.webalf.slotbot.model.Event;
import de.webalf.slotbot.model.Slot;
import de.webalf.slotbot.model.User;
import de.webalf.slotbot.model.dtos.EventDiscordInformationDto;
import de.webalf.slotbot.model.dtos.SlotDto;
import de.webalf.slotbot.model.dtos.UserDto;
import de.webalf.slotbot.model.event.EventArchiveInitializedEvent;
import de.webalf.slotbot.service.EventService;
import de.webalf.slotbot.service.event.EventArchiveInitializedEvent;
import lombok.RequiredArgsConstructor;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -50,6 +51,10 @@ public List<Event> findAllForeignNotAssignedInFuture(long guildId) {
return eventService.findAllForeignNotAssignedInFuture(guildId);
}

public Optional<Event> findLastEventOfUser(User user, de.webalf.slotbot.model.Guild guild) {
return eventService.findLastEventOfUserInGuild(user, guild);
}

public void addDiscordInformation(long eventId, EventDiscordInformationDto dto) {
eventService.addDiscordInformation(eventId, dto);
}
Expand Down

0 comments on commit 662ef03

Please sign in to comment.