Skip to content

Commit

Permalink
Sync application and discord role changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Alf-Melmac committed Oct 8, 2023
1 parent eaa78d8 commit 9a985c7
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ public List<GuildDto> getGuilds() {

@GetMapping("/{id}")
public GuildDetailsDto getGuild(@PathVariable(value = "id") long guildId) {
return GuildDetailsAssembler.toDto(guildService.findByDiscordGuild(guildId));
return GuildDetailsAssembler.toDto(guildService.findExisting(guildId));
}

@GetMapping("/{id}/config")
@PreAuthorize("@permissionChecker.hasGuildAdminPrivileges(#guildId)")
public GuildConfigDto getGuildConfig(@PathVariable(value = "id") long guildId) {
return GuildConfigAssembler.toDto(guildService.findByDiscordGuild(guildId));
return GuildConfigAssembler.toDto(guildService.findExisting(guildId));
}

@PutMapping("/{id}/config")
Expand All @@ -77,7 +77,7 @@ public GuildDiscordIntegrationDto getDiscordIntegration(@PathVariable(value = "i

@GetMapping("/{id}/users")
public FrontendPageable<UserInGuildDto> getGuildUsers(@PathVariable(value = "id") long guildId, Pageable pageRequest) {
final Guild guild = guildService.findByDiscordGuild(guildId);
final Guild guild = guildService.findExisting(guildId);
return FrontendPageable.of(guildUsersService.findGuildUsers(guild, pageRequest)
.map(user -> userInGuildAssembler.toDto(user, guild)));
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/de/webalf/slotbot/model/Guild.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import de.webalf.slotbot.converter.persistence.PatternPersistenceConverter;
import de.webalf.slotbot.model.enums.Language;
import de.webalf.slotbot.util.StringUtils;
import de.webalf.slotbot.util.permissions.Role;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.*;
Expand Down Expand Up @@ -87,4 +88,23 @@ public String getSpacerUrl() {
public Locale getLocale() {
return Locale.forLanguageTag(getLanguage().name());
}

/**
* Returns the configured discord role for the given {@link Role}
*
* @param role to get discord role for
* @return discord role id or null if not configured
*/
public Long getDiscordRole(Role role) {
if (role == Role.ADMINISTRATOR) {
return getAdminRole();
}
if (role == Role.EVENT_MANAGE) {
return getEventManageRole();
}
if (role == null) {
return getMemberRole();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.webalf.slotbot.model.event;

import de.webalf.slotbot.model.GuildUser;
import de.webalf.slotbot.util.permissions.Role;
import lombok.NonNull;

/**
* Notifies about a new guild user
*
* @author Alf
* @since 08.10.2023
*/
public record GuildUserCreatedEvent(long guildId, long userId, Role role) {
public GuildUserCreatedEvent(@NonNull GuildUser guildUser) {
this(guildUser.getGuild().getId(), guildUser.getUser().getId(), guildUser.getRole());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.webalf.slotbot.model.event;

import de.webalf.slotbot.model.GuildUser;
import de.webalf.slotbot.util.permissions.Role;
import lombok.NonNull;

/**
* Notifies about user removed from a guild
*
* @author Alf
* @since 08.10.2023
*/
public record GuildUserDeleteEvent(long guildId, long userId, Role role) {
public GuildUserDeleteEvent(@NonNull GuildUser guildUser) {
this(guildUser.getGuild().getId(), guildUser.getUser().getId(), guildUser.getRole());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.webalf.slotbot.model.event;

import de.webalf.slotbot.model.GuildUser;
import de.webalf.slotbot.util.permissions.Role;
import lombok.NonNull;

/**
* Notifies about a change in a guild user's role
*
* @author Alf
* @since 03.10.2023
*/
public record GuildUserRoleUpdateEvent(long guildId, long userId, Role oldRole, Role newRole) {
public GuildUserRoleUpdateEvent(@NonNull GuildUser guildUser, Role oldRole) {
this(guildUser.getGuild().getId(), guildUser.getUser().getId(), oldRole, guildUser.getRole());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public interface GuildRepository extends DiscordIdEntityJpaRepository<Guild> {
List<Guild> findByUrlPatternIsNotNull();

@Query("""
SELECT g FROM Guild g
SELECT COUNT(g) > 0 FROM Guild g
WHERE g.id = :id AND (g.memberRole IN :roles OR g.eventManageRole IN :roles OR g.adminRole IN :roles)""")
Optional<Guild> findByIdAndAnyRoleIn(@Param("id") long id, @Param("roles") Collection<Long> roles);
boolean existsByIdAndAnyRoleIn(@Param("id") long id, @Param("roles") Collection<Long> roles);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ public interface GuildUsersRepository extends JpaRepository<GuildUser, GuildUser

Optional<GuildUser> findByGuildAndUser(Guild guild, User user);

void deleteByGuildAndUser(Guild guild, User user);
void deleteById_GuildIdAndId_UserId(long guildId, long userId);
}
18 changes: 5 additions & 13 deletions src/main/java/de/webalf/slotbot/service/GuildService.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ public Guild find(long id) {
.orElseGet(() -> guildRepository.save(Guild.builder().id(id).build()));
}

Optional<Guild> findExistingOptional(long guildId) {
return guildRepository.findById(guildId);
}

/**
* Returns the guild associated with the given guildId
*
Expand All @@ -94,23 +90,19 @@ Optional<Guild> findExistingOptional(long guildId) {
* @throws ResourceNotFoundException if no guild with this guildId could be found
*/
public Guild findExisting(long guildId) {
return findExistingOptional(guildId).orElseThrow(ResourceNotFoundException::new);
}

public Guild findByDiscordGuild(long discordGuild) {
return guildRepository.findById(discordGuild).orElseThrow(ResourceNotFoundException::new);
return guildRepository.findById(guildId).orElseThrow(ResourceNotFoundException::new);
}

public Optional<Guild> findByName(String name) {
return guildRepository.findByGroupIdentifier(name);
}

/**
* Returns the guild with the given id if any of the given role ids is configured ({@link Guild#getMemberRole()},
* {@link Guild#getEventManageRole()}, {@link Guild#getAdminRole()}) for this guild.
* Checks if there is any guild with the given id and any of the given role ids is configured
* ({@link Guild#getMemberRole()}, {@link Guild#getEventManageRole()}, {@link Guild#getAdminRole()}) for this guild.
*/
public Optional<Guild> findByIdAndAnyRoleIn(long guildId, Set<Long> roles) {
return guildRepository.findByIdAndAnyRoleIn(guildId, roles);
public boolean existsByIdAndAnyRoleIn(long guildId, Set<Long> roles) {
return guildRepository.existsByIdAndAnyRoleIn(guildId, roles);
}

public Guild evaluateReservedFor(String reservedFor) {
Expand Down
160 changes: 97 additions & 63 deletions src/main/java/de/webalf/slotbot/service/GuildUsersService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import de.webalf.slotbot.model.Guild;
import de.webalf.slotbot.model.GuildUser;
import de.webalf.slotbot.model.User;
import de.webalf.slotbot.model.event.GuildUserCreatedEvent;
import de.webalf.slotbot.model.event.GuildUserDeleteEvent;
import de.webalf.slotbot.model.event.GuildUserRoleUpdateEvent;
import de.webalf.slotbot.repository.GlobalRoleRepository;
import de.webalf.slotbot.repository.GuildUsersRepository;
import de.webalf.slotbot.util.permissions.Role;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
Expand All @@ -18,7 +24,6 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import static de.webalf.slotbot.util.permissions.PermissionHelper.buildAuthenticationWithPrefix;
Expand All @@ -31,12 +36,14 @@
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class GuildUsersService {
private final GuildUsersRepository guildUsersRepository;
private final UserService userService;
private final GuildService guildService;
private final GlobalRoleRepository globalRoleRepository;
private final SessionRegistry sessionRegistry;
private final ApplicationEventPublisher eventPublisher;

public Page<GuildUser> findGuildUsers(Guild guild, Pageable pageable) {
return guildUsersRepository.findByGuild(guild, pageable);
Expand All @@ -50,53 +57,33 @@ public GuildUser add(long guildId, long userId) {
final Guild guild = guildService.find(guildId);
final User user = userService.find(userId);

invalidateSession(userId);

log.error("Adding user {} to guild {}", user, guild);
return guildUsersRepository.findByGuildAndUser(guild, user)
.orElseGet(() -> guildUsersRepository.save(GuildUser.builder().user(user).guild(guild).build()));
.orElseGet(() -> create(guild, user, null));
}

private GuildUser create(@NonNull Guild guild, @NonNull User user, Role role) {
final GuildUser guildUser = guildUsersRepository.save(GuildUser.builder().user(user).guild(guild).role(role).build());
eventPublisher.publishEvent(new GuildUserCreatedEvent(guildUser));
return guildUser;
}

public void remove(long guildId, long userId) {
final User user = userService.findExisting(userId);
final Guild guild = guildService.findExisting(guildId);

invalidateSession(userId);

guildUsersRepository.deleteByGuildAndUser(guild, user);
log.trace("Removing user {} from guild {}", user, guild);
guildUsersRepository.findByGuildAndUser(guild, user)
.ifPresent(guildUser -> {
eventPublisher.publishEvent(new GuildUserDeleteEvent(guildUser));
guildUsersRepository.delete(guildUser);
});
}

@Async
public void removeOptional(long guildId, long userId) {
final Optional<User> user = userService.findExistingOptional(userId);
if (user.isEmpty()) {
return;
}
final Optional<Guild> guild = guildService.findExistingOptional(guildId);
if (guild.isEmpty()) {
return;
}

guildUsersRepository.deleteByGuildAndUser(guild.get(), user.get());
}

public Set<String> getApplicationRoles(long userId) {
Set<String> roles = new HashSet<>();

findByUserId(userId)
.forEach(guildUser -> {
final String role = guildUser.getApplicationRole();
if (role == null) {
return;
}
roles.add(buildAuthenticationWithPrefix(role)); //Add role for potential checks
roles.add(buildGuildAuthenticationWithPrefix(role, guildUser.getId().getGuildId()));
});

globalRoleRepository.findAllByUserId(userId).stream()
.map(globalRole -> buildAuthenticationWithPrefix(globalRole.getRole().getApplicationRole()))
.forEach(roles::add);

return roles;
invalidateSession(userId);
guildUsersRepository.deleteById_GuildIdAndId_UserId(guildId, userId);
}

/**
Expand All @@ -110,38 +97,38 @@ public void setRole(long guildId, long userId, Role role) {
final Guild guild = guildService.find(guildId);
final User user = userService.find(userId);

invalidateSession(userId);

log.trace("Setting role {} for user {} in guild {}", role, user, guild);
guildUsersRepository.findByGuildAndUser(guild, user)
.ifPresentOrElse(guildUser -> guildUser.setRole(role),
() -> guildUsersRepository.save(GuildUser.builder().guild(guild).user(user).role(role).build()));
() -> create(guild, user, role));
}

private void invalidateSession(long userId) {
sessionRegistry.getAllPrincipals().stream()
.flatMap(principal -> sessionRegistry.getAllSessions(principal, false).stream())
.filter(sessionInformation -> sessionInformation.getPrincipal() instanceof final DefaultOAuth2User user
&& user.getAttributes().get("id").equals(Long.toString(userId)))
.forEach(SessionInformation::expireNow);
}

@Async
public void onRolesAdded(long guildId, long userId, Set<Long> addedRoles, Set<Long> memberRoles) {
guildService.findByIdAndAnyRoleIn(guildId, addedRoles).ifPresent(guild ->
setRole(guildId, userId, evaluateRole(guild, memberRoles)));
/**
* Checks if one of the given roles is configured for any role in the given guild
*
* @return true if no role matches a configured role
* @see GuildService#existsByIdAndAnyRoleIn(long, Set)
*/
public boolean noRoleConfiguredForGuild(long guildId, Set<Long> roles) {
return !guildService.existsByIdAndAnyRoleIn(guildId, roles);
}

@Async
public void onRolesRemoved(long guildId, long userId, Set<Long> removedRoles, Set<Long> memberRoles) {
guildService.findByIdAndAnyRoleIn(guildId, removedRoles).ifPresent(guild -> {
final Role newRole = evaluateRole(guild, memberRoles);
//If a member role is configured and no role is left, remove user from guild
if (guild.getMemberRole() != null && !memberRoles.contains(guild.getMemberRole()) && newRole == null) {
remove(guildId, userId);
return;
}
setRole(guildId, userId, newRole);
});
/**
* Processes changes in the roles of the given user in the given guild.
*
* @param guildId guild the roles changed in
* @param userId user whose roles changed
* @param memberRoles new set of roles of the user
*/
public void onRolesChanged(long guildId, long userId, Set<Long> memberRoles) {
final Guild guild = guildService.findExisting(guildId);
final Role newRole = evaluateRole(guild, memberRoles);
//If a member role is configured and no role is left, remove user from guild
if (guild.getMemberRole() != null && !memberRoles.contains(guild.getMemberRole()) && newRole == null) {
remove(guildId, userId);
return;
}
setRole(guildId, userId, newRole);
}

/**
Expand All @@ -155,4 +142,51 @@ private Role evaluateRole(@NonNull Guild guild, Set<Long> memberRoles) {
}
return null;
}

@EventListener
@Async
public void onGuildUserCreatedEvent(@NonNull GuildUserCreatedEvent event) {
invalidateSession(event.userId());
}

@EventListener
@Async
public void onGuildUserRoleUpdateEvent(@NonNull GuildUserRoleUpdateEvent event) {
invalidateSession(event.userId());
}

@EventListener
@Async
public void onGuildUserDeleteEvent(@NonNull GuildUserDeleteEvent event) {
invalidateSession(event.userId());
}

private void invalidateSession(long userId) {
log.trace("Invalidating session for user {}", userId);
sessionRegistry.getAllPrincipals().stream()
.flatMap(principal -> sessionRegistry.getAllSessions(principal, false).stream())
.filter(sessionInformation -> sessionInformation.getPrincipal() instanceof final DefaultOAuth2User user
&& user.getAttributes().get("id").equals(Long.toString(userId)))
.forEach(SessionInformation::expireNow);
}

public Set<String> getApplicationRoles(long userId) {
Set<String> roles = new HashSet<>();

findByUserId(userId)
.forEach(guildUser -> {
final String role = guildUser.getApplicationRole();
if (role == null) {
return;
}
roles.add(buildAuthenticationWithPrefix(role)); //Add role for potential checks
roles.add(buildGuildAuthenticationWithPrefix(role, guildUser.getId().getGuildId()));
});

globalRoleRepository.findAllByUserId(userId).stream()
.map(globalRole -> buildAuthenticationWithPrefix(globalRole.getRole().getApplicationRole()))
.forEach(roles::add);

return roles;
}
}

0 comments on commit 9a985c7

Please sign in to comment.