Compare commits

..

24 Commits

Author SHA1 Message Date
release-bot
6b13958ac0 [maven-release-plugin] prepare release v1.5.34 2024-04-05 12:29:20 +00:00
Sheldan
3142daafd3 [AB-xxx] renaming leaderboard url property 2024-04-05 01:58:43 +02:00
Sheldan
4bef78f847 [AB-xxx] adding link to the leaderboard to the leaderboard command response 2024-04-05 01:40:06 +02:00
Sheldan
82be86e086 [AB-xxx] adding locking mechanism for role assignments to work around discord lack of role update locking 2024-04-04 22:54:18 +02:00
Sheldan
bff505ef25 [AB-xxx] fixing not using the ban reason for moderation actions 2024-03-27 23:17:06 +01:00
release-bot
533f5671c2 [maven-release-plugin] prepare for next development iteration 2024-03-27 21:26:56 +00:00
release-bot
8c7547b485 [maven-release-plugin] prepare release v1.5.33 2024-03-27 21:26:54 +00:00
Sheldan
741c194bb8 [AB-xxx] changing styling for smaller screens for member display to truncate the name
prepare for release
2024-03-27 22:24:38 +01:00
release-bot
d2bdfd8dac [maven-release-plugin] prepare for next development iteration 2024-03-26 22:54:50 +00:00
release-bot
36c67fbe20 [maven-release-plugin] prepare release v1.5.32 2024-03-26 22:54:47 +00:00
Sheldan
8fd1aede2a [AB-xxx] changing style of leaderboard table
preparing for release
2024-03-26 23:48:22 +01:00
release-bot
287ae1f0b1 [maven-release-plugin] prepare for next development iteration 2024-03-26 21:40:02 +00:00
release-bot
903361cb58 [maven-release-plugin] prepare release v1.5.31 2024-03-26 21:39:59 +00:00
Sheldan
c8cf412a4a [AB-xxx] changing style of leaderboard table
preparing for release
2024-03-26 22:37:47 +01:00
release-bot
affc249012 [maven-release-plugin] prepare for next development iteration 2024-03-26 21:17:03 +00:00
release-bot
653671ea79 [maven-release-plugin] prepare release v1.5.30 2024-03-26 21:17:00 +00:00
Sheldan
7185908682 [AB-xxx] prepare for release 2024-03-26 22:13:42 +01:00
Sheldan
675da8d5d8 [AB-xxx] adding rank to leaderboard page
changing design of leaderboard page
fixing role not having an id
2024-03-26 22:13:18 +01:00
release-bot
e91becee0d [maven-release-plugin] prepare for next development iteration 2024-03-26 01:10:22 +00:00
release-bot
18732efe75 [maven-release-plugin] prepare release v1.5.29 2024-03-26 01:10:20 +00:00
Sheldan
63897fd914 [AB-xxx] prepare for release 2024-03-26 02:08:06 +01:00
Sheldan
9b865af43b [AB-xxx] fixing not serving static files 2024-03-26 02:07:25 +01:00
release-bot
b78275734c [maven-release-plugin] prepare for next development iteration 2024-03-26 00:36:30 +00:00
release-bot
00a6b0d1f8 [maven-release-plugin] prepare release v1.5.28 2024-03-26 00:36:28 +00:00
100 changed files with 267 additions and 156 deletions

2
.env
View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.28
VERSION=1.5.33

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>dynamic-activity</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -44,11 +44,12 @@ public class LeaderboardController {
Pageable pageable) {
AServer server = serverManagementService.loadServer(serverId);
Guild guild = guildService.getGuildById(serverId);
return userExperienceManagementService.loadAllUsersPaginated(server, pageable)
.map(userExperience -> convertFromUser(guild, userExperience));
Page<AUserExperience> allElements = userExperienceManagementService.loadAllUsersPaginated(server, pageable);
return allElements
.map(userExperience -> convertFromUser(guild, userExperience, pageable, allElements));
}
private UserExperienceDisplay convertFromUser(Guild guild, AUserExperience aUserExperience) {
private UserExperienceDisplay convertFromUser(Guild guild, AUserExperience aUserExperience, Pageable pageable, Page<AUserExperience> page) {
Long userId = aUserExperience.getUser().getUserReference().getId();
Member member = guild.getMember(UserSnowflake.fromId(userId));
AExperienceRole experienceRole = aUserExperience.getCurrentExperienceRole();
@@ -70,6 +71,7 @@ public class LeaderboardController {
.id(userId)
.messages(aUserExperience.getMessageCount())
.level(aUserExperience.getLevelOrDefault())
.rank((int) pageable.getOffset() + page.getContent().indexOf(aUserExperience) + 1)
.experience(aUserExperience.getExperience())
.role(roleDisplay)
.member(userDisplay)

View File

@@ -28,7 +28,9 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@@ -70,6 +72,9 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Value("${abstracto.experience.leaderboard.externalUrl}")
private String leaderboardExternalURL;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -91,11 +96,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
futures.add(userRankFuture);
String leaderboardUrl;
if(!StringUtils.isBlank(leaderboardExternalURL)) {
leaderboardUrl = String.format("%s/experience/leaderboards/%s", leaderboardExternalURL, actorUser.getGuild().getIdLong());
} else {
leaderboardUrl = null;
}
return FutureUtils.toSingleFuture(futures).thenCompose(aVoid -> {
List<LeaderBoardEntryModel> finalModels = completableFutures.join();
LeaderBoardModel leaderBoardModel = LeaderBoardModel
.builder()
.userExperiences(finalModels)
.leaderboardUrl(leaderboardUrl)
.userExecuting(userRankFuture.join().get(0))
.build();
return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong()));

View File

@@ -11,6 +11,7 @@ import lombok.Getter;
public class UserExperienceDisplay {
private UserDisplay member;
private Long id;
private Integer rank;
private Integer level;
private Long experience;
private Long messages;

View File

@@ -17,4 +17,6 @@ abstracto.featureModes.levelUpNotification.enabled=false
abstracto.featureModes.levelAction.featureName=experience
abstracto.featureModes.levelAction.mode=levelAction
abstracto.featureModes.levelAction.enabled=false
abstracto.featureModes.levelAction.enabled=false
abstracto.experience.leaderboard.externalUrl=${FRONTEND_BASE:}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -24,4 +24,5 @@ public class LeaderBoardModel extends SlimUserInitiatedServerContext {
* The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command.
*/
private LeaderBoardEntryModel userExecuting;
private String leaderboardUrl;
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>giveaway-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>giveaway-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>giveaway</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>image-generation-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>image-generation-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>image-generation</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -76,7 +76,7 @@ public class BanModerationActionModalListener implements ModalInteractionListene
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>profanity-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>sticky-roles</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>sticky-roles-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>sticky-roles</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<artifactId>sticky-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>twitch</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>twitch</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@@ -7,10 +7,12 @@ import dev.sheldan.abstracto.core.exception.RoleNotFoundInGuildException;
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.metric.service.MetricTag;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.core.utils.LockByKeyService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
@@ -46,6 +48,9 @@ public class RoleServiceBean implements RoleService {
@Autowired
private MetricService metricService;
@Autowired
private LockByKeyService<ServerUser> roleLockService;
public static final CounterMetric ROLE_ASSIGNED_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
@@ -113,12 +118,31 @@ public class RoleServiceBean implements RoleService {
.map(guild::getRoleById)
.toList();
Member member = memberService.getMemberInServer(aUserInAServer);
return guild.modifyMemberRoles(member, rolesObjToAdd, rolesObjToRemove).submit();
return updateRolesObj(member, rolesObjToRemove, rolesObjToAdd);
}
@Override
public CompletableFuture<Void> updateRolesObj(Member member, List<Role> rolesToRemove, List<Role> rolesToAdd) {
return member.getGuild().modifyMemberRoles(member, rolesToAdd, rolesToRemove).submit();
ServerUser serverUser = ServerUser.fromId(member.getGuild().getIdLong(), member.getIdLong()); // only use ids, so its completely comparable with the other server users
try {
roleLockService.lock(serverUser);
return member.getGuild().modifyMemberRoles(member, rolesToAdd, rolesToRemove).submit()
.whenComplete((unused, throwable) -> roleLockService.unlock(serverUser));
/*
the intended reason why we only have it in a catch block:
the "finally" block runs before the whenComplete block, which would lead
to be possibility of another request being done immediately, and also doing it twice
We only unlock synchronously in case of an exception, because then something _before_ the future
actually failed. The future (whenComplete) is _never_ evaluated in such a case
this is also the case in addRoleToMemberAsync and removeRoleFromUserAsync
*/
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(interruptedException);
} catch (Exception e) {
roleLockService.unlock(serverUser);
throw e;
}
}
@Override
@@ -164,7 +188,7 @@ public class RoleServiceBean implements RoleService {
if(role == null) {
throw new RoleNotFoundInGuildException(roleId, member.getGuild().getIdLong());
}
return member.getGuild().removeRoleFromMember(member, role).submit();
return removeRoleFromUserAsync(member.getGuild(), member.getIdLong(), role);
}
private CompletableFuture<Void> addRoleToUserAsync(Guild guild, Long userId, ARole role) {
@@ -180,13 +204,35 @@ public class RoleServiceBean implements RoleService {
@Override
public CompletableFuture<Void> addRoleToMemberAsync(Guild guild, Long userId, Role roleById) {
metricService.incrementCounter(ROLE_ASSIGNED_METRIC);
return guild.addRoleToMember(UserSnowflake.fromId(userId), roleById).submit();
ServerUser serverUser = ServerUser.fromId(guild.getIdLong(), userId);
try {
roleLockService.lock(serverUser);
return guild.addRoleToMember(UserSnowflake.fromId(userId), roleById).submit()
.whenComplete((unused, ex) -> roleLockService.unlock(serverUser));
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(interruptedException);
} catch (Exception e){ // see updateRolesObj for why
roleLockService.unlock(serverUser);
throw e;
}
}
@Override
public CompletableFuture<Void> removeRoleFromUserAsync(Guild guild, Long userId, Role roleById) {
metricService.incrementCounter(ROLE_REMOVED_METRIC);
return guild.removeRoleFromMember(UserSnowflake.fromId(userId), roleById).submit();
ServerUser serverUser = ServerUser.fromId(guild.getIdLong(), userId);
try {
roleLockService.lock(serverUser);
return guild.removeRoleFromMember(UserSnowflake.fromId(userId), roleById).submit()
.whenComplete((unused, ex) -> roleLockService.unlock(serverUser));
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(interruptedException);
} catch (Exception e) { // see updateRolesObj for why
roleLockService.unlock(serverUser);
throw e;
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@@ -1,10 +1,7 @@
package dev.sheldan.abstracto.core.models;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import net.dv8tion.jda.api.entities.Member;
import java.io.Serializable;
@@ -12,10 +9,12 @@ import java.io.Serializable;
@Getter
@Setter
@Builder
@ToString
@EqualsAndHashCode
public class ServerUser implements Serializable {
private Long serverId;
private Long userId;
@EqualsAndHashCode.Exclude
private Boolean isBot;
public static ServerUser fromAUserInAServer(AUserInAServer aUserInAServer) {

View File

@@ -18,7 +18,8 @@ public class RoleDisplay {
public static RoleDisplay fromRole(Role role) {
RoleDisplayBuilder builder = builder()
.name(role.getName());
.name(role.getName())
.id(role.getIdLong());
Color roleColor = role.getColor();
if(roleColor != null) {
builder.r(roleColor.getRed()).

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.core.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@Slf4j
// https://www.baeldung.com/java-acquire-lock-by-key
public class LockByKeyService<Key> {
private static class LockWrapper {
private final Semaphore lock = new Semaphore(1);
private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1);
private LockWrapper addThreadInQueue() {
numberOfThreadsInQueue.incrementAndGet();
return this;
}
private int removeThreadFromQueue() {
return numberOfThreadsInQueue.decrementAndGet();
}
}
private final ConcurrentHashMap<Key, LockWrapper> locks = new ConcurrentHashMap<>();
public void lock(Key key) throws InterruptedException {
LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue());
lockWrapper.lock.acquire();
}
public void unlock(Key key) {
LockWrapper lockWrapper = locks.get(key);
lockWrapper.lock.release();
if (lockWrapper.removeThreadFromQueue() == 0) {
locks.remove(key, lockWrapper);
}
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>core</artifactId>
<groupId>dev.sheldan.abstracto.core</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>core</artifactId>
<groupId>dev.sheldan.abstracto.core</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-application</artifactId>
<groupId>dev.sheldan.abstracto</groupId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -10,7 +10,7 @@
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
<packaging>pom</packaging>
<modules>
@@ -33,7 +33,7 @@
<connection>scm:git:${project.scm.url}</connection>
<developerConnection>scm:git:${project.scm.url}</developerConnection>
<url>https://github.com/Sheldan/abstracto.git</url>
<tag>HEAD</tag>
<tag>v1.5.34</tag>
</scm>
<repositories>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling</artifactId>
<version>1.5.28-SNAPSHOT</version>
<version>1.5.34</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@@ -7,7 +7,7 @@ import importlib
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logging.basicConfig(encoding='utf-8', level=logging.INFO, format=FORMAT)
template_dir = os.path.abspath('resources/templates')
app = Flask(__name__, template_folder=template_dir)
app = Flask(__name__, template_folder=template_dir, static_url_path='', static_folder=template_dir)
import sys
sys.path.append("..")

View File

@@ -6,6 +6,7 @@ export const ExperienceConfigDisplay = ({serverId}: { serverId: bigint }) => {
const [roles, setRoles] = useState<ExperienceRole[]>([])
const [hasError, setError] = useState(false)
const [isOpen, setOpen] = useState(false)
async function loadConfig() {
try {
const configResponse = await fetch(`/experience/v1/leaderboards/${serverId}/config`)
@@ -23,37 +24,37 @@ export const ExperienceConfigDisplay = ({serverId}: { serverId: bigint }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
},[])
function toggleOpen() {
setOpen(!isOpen)
}
return (
<>
{!hasError ?
<div className="py-10">
<h2 className="text-4xl font-extrabold leading-none tracking-tight text-white">Role
config</h2>
<table className="w-full text-gray-400">
<thead
className="text-xs uppercase bg-gray-700 text-gray-400">
<tr>
<th className="px-6 py-3 w-1/2">Role</th>
<th className="px-6 py-3 w-1/8">Level</th>
</tr>
</thead>
<tbody>
{!hasError && roles.length !== 0 ?
<div className="bg-gray-800 p-4 mx-auto w-4/5 rounded-lg">
<button className="w-full flex justify-between items-center p-2 px-4" onClick={toggleOpen}>
<h2 className="text-xl font-extrabold leading-none tracking-tight text-gray-100">Role config</h2>
<div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className={`w-6 h-6 stroke-white stroke-2 duration-500 ${isOpen ? "rotate-180" : ""}`}>
<path strokeLinecap="round" strike-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
</svg>
</div>
</button>
<div className={`grid grid-cols-1 ${isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'} overflow-hidden duration-500`}>
<div className="w-full min-h-0 overflow-hidden">
<div className="p-4 flex flex-col gap-3 items-start">
{roles.map(role =>
<tr key={role.role.id} className="border-b bg-gray-800 border-gray-700">
<td className="px-6 py-4">
<div key={role.role.id} className="border-gray-700 flex gap-4 items-center flex-row-reverse">
<p>
<RoleDisplay role={role.role}/>
</td>
<td className="px-6 py-4 text-center">
</p>
<p className="font-bold text-gray-200">
{role.level}
</td>
</tr>)}
</tbody>
</table>
{roles.length === 0 ?
<div className="w-full flex items-center justify-center">
<span className="text-gray-400">No roles</span>
</div> : ''}
</p>
</div>)}
</div>
</div>
</div>
</div>
: ''}
</>

View File

@@ -51,7 +51,7 @@ export function Leaderboard({serverId}: { serverId: bigint }) {
function loadMore() {
loadLeaderboard(pageCount + 1, pageSize)
}
let loadMoreButton = <button className="w-full bg-gray-500 hover:bg-gray-700 text-white" onClick={loadMore}>Load more</button>;
let loadMoreButton = <button className="w-full h-10 bg-gray-500 hover:bg-gray-700 text-white mt-4" onClick={loadMore}>Load more</button>;
return (
<>
{!hasError ?
@@ -67,42 +67,42 @@ export function Leaderboard({serverId}: { serverId: bigint }) {
alt="Icon"
className="w-24"/>
: ''}
<h1 className="text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">{'Leaderboard for ' + guildInfo.name}</h1>
<h1 className="text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white px-5">{guildInfo.name + ' Leaderboard'}</h1>
</div>
</div>
<div className="flex">
<div className="text-sm text-left w-3/4 ">
<div className="flex flex-col">
<div className="mt-4">
<ExperienceConfigDisplay serverId={serverId}/>
</div>
<div className="text-sm text-left w-full mt-4">
<table className="w-full text-gray-400">
<thead
className="text-xs uppercase bg-gray-700 text-gray-400">
<tr>
<th scope="col" className="px-6 py-3 w-1/3">
<th scope="col" className="px-2 py-3 w-5">
Rank
</th>
<th scope="col" className="px-1 py-3 w-1/2 sm:w-1/2">
Member
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
<th scope="col" className="px-1 py-3 w-1/5 sm:w-1/5 text-center">
Experience
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
<th scope="col" className="px-1 py-3 w-1/5 sm:w-1/6 text-center">
Messages
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
<th scope="col" className="px-1 py-3 w-1/5 text-center">
Level
</th>
<th scope="col" className="px-6 py-3 w-1/3 text-center">
Role
</th>
</tr>
</thead>
<tbody>
{members.map(member => <LeaderboardEntry key={member.id} member={member}/>)}
{members.map((member, index) => <LeaderboardEntry key={member.id} index={index} member={member}/>)}
</tbody>
</table>
{hasMore ? loadMoreButton : ''}
</div>
<div className="w-1/4 px-3">
<ExperienceConfigDisplay serverId={serverId}/>
</div>
</div>
</>
: <ErrorDisplay/>}

View File

@@ -1,35 +1,37 @@
import {ExperienceMember} from "../data/leaderboard";
import {RoleDisplay} from "./RoleDisplay";
import createStyle from "../utils/styleUtils";
export const LeaderboardEntry = ({member}: { member: ExperienceMember }) => {
export const LeaderboardEntry = ({member, index}: { member: ExperienceMember, index: number }) => {
const userHasRole = member.role !== null;
const memberExists = member.member !== null;
const nameColor = userHasRole ? createStyle(member.role!) : ''
let memberDisplay = memberExists ? <>
<img alt={member.member!.name} src={member.member!.avatarUrl}
className="object-contain h-16 w-16 rounded-full"/>
<span className="align-middle" style={{color: nameColor}}>{member.member!.name}</span>
</> : <>{member.id}</>;
<span className="align-middle max-[480px]:max-w-48 truncate" style={{color: nameColor}}>{member.member!.name}</span>
</> :
<span className="inline-flex items-center h-16">
{member.id.toString()}
</span>;
return (
<>
<tr className="border-b bg-gray-800 border-gray-700">
<tr className={`${index % 2 === 0 ? "bg-gray-600" : "bg-gray-800"} h-full`} style={{ minHeight: 64}}>
<td
className="px-2 py-4 font-medium whitespace-nowrap text-white flex items-center gap-3">
className="text-center">
{member.rank}
</td>
<td className="px-2 py-3 font-medium whitespace-nowrap text-white flex items-center gap-2">
{memberDisplay}
</td>
<td className="px-6 py-4 text-center">
<td className="px-1 py-3 text-center">
{member.experience.toLocaleString()}
</td>
<td className="px-6 py-4 text-center">
<td className="px-1 py-3 text-center">
{member.messages.toLocaleString()}
</td>
<td className="px-6 py-4 text-center">
<td className="px-2 py-3 text-center">
{member.level.toString()}
</td>
<td className="px-6 py-4 text-center">
{userHasRole ? <RoleDisplay role={member.role!}/> : 'No role'}
</td>
</tr>
</>

View File

@@ -1,5 +1,6 @@
export interface ExperienceMember {
experience: bigint;
rank: number;
id: bigint;
level: number;
messages: bigint;