added javadoc to experience tracking module

changed persistence configuration for entities, so cascade is more correct
changed the way channels get their server assigned
added feature to only change the exp role of a user, if he doesn't already have the new target role
added the name of the needed feature, to the message indicating that a feature has been disabled
updated unsetExpRole to have a status update message, because there might be a lot of users which need a role update
This commit is contained in:
Sheldan
2020-04-23 23:39:05 +02:00
parent 1a74924850
commit cf37d4adef
69 changed files with 929 additions and 260 deletions

View File

@@ -20,6 +20,7 @@ import java.util.List;
public class ExpScale extends AbstractConditionableCommand {
public static final String EXP_MULTIPLIER_KEY = "expMultiplier";
@Autowired
private ConfigService configService;

View File

@@ -13,7 +13,7 @@ import dev.sheldan.abstracto.experience.converter.LeaderBoardModelConverter;
import dev.sheldan.abstracto.experience.models.LeaderBoard;
import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.templates.LeaderBoardModel;
import dev.sheldan.abstracto.experience.service.ExperienceTrackerService;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
@@ -30,7 +30,7 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
public static final String LEADERBOARD_POST_EMBED_TEMPLATE = "leaderboard_post";
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Autowired
private UserExperienceManagementService userExperienceManagementService;
@@ -48,12 +48,13 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
// parameter is optional, in case its not present, we default to the 0th page
Integer page = parameters.size() > 0 ? (Integer) parameters.get(0) : 0;
LeaderBoard leaderBoard = experienceTrackerService.findLeaderBoardData(commandContext.getUserInitiatedContext().getServer(), page);
LeaderBoard leaderBoard = userExperienceService.findLeaderBoardData(commandContext.getUserInitiatedContext().getServer(), page);
LeaderBoardModel leaderBoardModel = (LeaderBoardModel) ContextConverter.fromCommandContext(commandContext, LeaderBoardModel.class);
leaderBoardModel.setUserExperiences(converter.fromLeaderBoard(leaderBoard));
LeaderBoardEntry userRank = experienceTrackerService.getRankOfUserInServer(commandContext.getUserInitiatedContext().getAUserInAServer());
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(commandContext.getUserInitiatedContext().getAUserInAServer());
leaderBoardModel.setUserExecuting(converter.fromLeaderBoardEntry(userRank));
MessageToSend messageToSend = templateService.renderEmbedTemplate(LEADERBOARD_POST_EMBED_TEMPLATE, leaderBoardModel);
channelService.sendMessageToEndInTextChannel(messageToSend, commandContext.getChannel());

View File

@@ -13,7 +13,7 @@ import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import dev.sheldan.abstracto.experience.models.templates.RankModel;
import dev.sheldan.abstracto.experience.service.ExperienceLevelService;
import dev.sheldan.abstracto.experience.service.ExperienceTrackerService;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +25,7 @@ import java.util.List;
@Component
public class Rank extends AbstractConditionableCommand {
public static final String RANK_POST_EMBED_TEMPLATE = "rank_post";
@Autowired
private LeaderBoardModelConverter converter;
@@ -32,7 +33,7 @@ public class Rank extends AbstractConditionableCommand {
private TemplateService templateService;
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Autowired
private ExperienceLevelService experienceLevelService;
@@ -40,11 +41,11 @@ public class Rank extends AbstractConditionableCommand {
@Override
public CommandResult execute(CommandContext commandContext) {
RankModel rankModel = (RankModel) ContextConverter.fromCommandContext(commandContext, RankModel.class);
LeaderBoardEntry userRank = experienceTrackerService.getRankOfUserInServer(commandContext.getUserInitiatedContext().getAUserInAServer());
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(commandContext.getUserInitiatedContext().getAUserInAServer());
rankModel.setRankUser(converter.fromLeaderBoardEntry(userRank));
AUserExperience experienceObj = userRank.getExperience();
rankModel.setExperienceToNextLevel(experienceLevelService.calculateExperienceToNextLevel(experienceObj.getCurrentLevel().getLevel(), experienceObj.getExperience()));
MessageToSend messageToSend = templateService.renderEmbedTemplate("rank_post", rankModel);
MessageToSend messageToSend = templateService.renderEmbedTemplate(RANK_POST_EMBED_TEMPLATE, rankModel);
channelService.sendMessageToEndInTextChannel(messageToSend, commandContext.getChannel());
return CommandResult.fromSuccess();

View File

@@ -41,11 +41,11 @@ public class SetExpRole extends AbstractConditionableCommand {
Long roleId = (Long) commandContext.getParameters().getParameters().get(1);
ARole role = roleManagementService.findRole(roleId);
AServer server = commandContext.getUserInitiatedContext().getServer();
if(!roleService.isRoleInServer(server, role)) {
if(!roleService.isRoleInServer(role)) {
throw new RoleException("Role not found.");
}
log.info("Setting role {} to be used for level {} on server {}", roleId, level, server.getId());
experienceRoleService.setRoleToLevel(role, level, server);
experienceRoleService.setRoleToLevel(role, level, server, commandContext.getUserInitiatedContext().getChannel());
return CommandResult.fromSuccess();
}

View File

@@ -8,7 +8,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.config.ExperienceFeatures;
import dev.sheldan.abstracto.experience.service.ExperienceTrackerService;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -21,13 +21,13 @@ import java.util.List;
public class SyncRoles extends AbstractConditionableCommand {
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Override
public CommandResult execute(CommandContext commandContext) {
AServer server = commandContext.getUserInitiatedContext().getServer();
log.info("Synchronizing roles on server {}", server.getId());
experienceTrackerService.syncUserRolesWithFeedback(server, commandContext.getUserInitiatedContext().getChannel());
userExperienceService.syncUserRolesWithFeedback(server, commandContext.getUserInitiatedContext().getChannel());
return CommandResult.fromSuccess();
}

View File

@@ -29,7 +29,9 @@ public class UnSetExpRole extends AbstractConditionableCommand {
public CommandResult execute(CommandContext commandContext) {
Long roleId = (Long) commandContext.getParameters().getParameters().get(0);
ARole role = roleManagementService.findRole(roleId);
experienceRoleService.unsetRole(role, commandContext.getUserInitiatedContext().getServer());
// do not check for the existence of the role, because if the role was deleted, users should be able
// to get rid of it in the configuration
experienceRoleService.unsetRole(role, commandContext.getUserInitiatedContext().getServer(), commandContext.getUserInitiatedContext().getChannel());
return CommandResult.fromSuccess();
}

View File

@@ -10,8 +10,23 @@ import org.springframework.stereotype.Component;
@Setter
@ConfigurationProperties(prefix = "abstracto.experience")
public class ExperienceConfig {
/**
* The default min experience range from the properties file. This is used, when the bot joins a new guild.
*/
private Integer minExp;
/**
* The default max experience range from the properties file. This is used, when the bot joins a new guild.
*/
private Integer maxExp;
/**
* The default multiplier from the properties file. This is used, when the bot joins a new guild.
*/
private Integer expMultiplier;
/**
* The defaul maxLevel from the properties file. This configuration applies globally, as the amount of levels does not depend on the server.
*/
private Integer maxLvl;
}

View File

@@ -7,6 +7,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Listener responsible to configure the required experience configurations in case the bot joins a new server.
*/
@Component
@Slf4j
public class ExperienceConfigListener implements ServerConfigListener {

View File

@@ -7,6 +7,10 @@ import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* Component responsible to create the amount of {@link dev.sheldan.abstracto.experience.models.database.AExperienceLevel}
* configured in the {@link ExperienceConfig}. This is executed when the application starts up.
*/
@Component
@Slf4j
public class ExperienceLevelLoader {
@@ -20,12 +24,7 @@ public class ExperienceLevelLoader {
@EventListener
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {
Integer maxLevel = experienceConfig.getMaxLvl();
Long experience = 0L;
log.info("Setting up experience level configuration.");
experienceLevelService.createExperienceLevel(0, 0L);
for (int i = 1; i < maxLevel; i++) {
experience = experience + experienceLevelService.calculateExperienceForLevel(i - 1);
experienceLevelService.createExperienceLevel(i, experience);
}
experienceLevelService.createLevelsUntil(maxLevel);
}
}

View File

@@ -12,12 +12,22 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* Converter used to conver from {@link LeaderBoard} to a list of {@link LeaderBoardEntryModel}
*/
@Component
public class LeaderBoardModelConverter {
@Autowired
private BotService botService;
/**
* Converts the complete {@link LeaderBoard} into a list of {@link LeaderBoardEntryModel} which contain additional
* information available for rendering the leaderboard ({@link Member} reference)
* @param leaderBoard The {@link LeaderBoard} object to be converted
* @return The list of {@link LeaderBoardEntryModel} which contain the fully fledged information provided to the
* leaderboard template
*/
public List<LeaderBoardEntryModel> fromLeaderBoard(LeaderBoard leaderBoard) {
List<LeaderBoardEntryModel> models = new ArrayList<>();
leaderBoard.getEntries().forEach(leaderBoardEntry -> {
@@ -27,6 +37,13 @@ public class LeaderBoardModelConverter {
return models;
}
/**
* Converts the given {@link LeaderBoardEntry} to a {@link LeaderBoardEntryModel}, which provides a reference to the
* {@link Member} object of the given {@link AUserInAServer} for convenience in the template
* @param leaderBoardEntry The {@link LeaderBoardEntry} to be converted
* @return The {@link LeaderBoardEntryModel} accompanied with the {@link Member} reference, might be null, if the
* user left the guild
*/
public LeaderBoardEntryModel fromLeaderBoardEntry(LeaderBoardEntry leaderBoardEntry) {
AUserInAServer entryUser = leaderBoardEntry.getExperience().getUser();
Member entryMember = botService.getMemberInServer(entryUser.getServerReference().getId(), entryUser.getUserReference().getId());

View File

@@ -1,7 +1,7 @@
package dev.sheldan.abstracto.experience.job;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.service.ExperienceTrackerService;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
@@ -16,6 +16,11 @@ import java.util.HashMap;
import java.util.List;
/**
* This {@link QuartzJobBean} is executed regularly and calls the the {@link AUserExperienceService}
* store the tracked experience from runtime. This job also cleans up the already processed entries in the runtime
* experience.
*/
@Slf4j
@DisallowConcurrentExecution
@Component
@@ -23,16 +28,16 @@ import java.util.List;
public class ExperiencePersistingJob extends QuartzJobBean {
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
HashMap<Long, List<AServer>> runtimeExperience = experienceTrackerService.getRuntimeExperience();
HashMap<Long, List<AServer>> runtimeExperience = userExperienceService.getRuntimeExperience();
log.info("Running experience persisting job.");
Long pastMinute = (Instant.now().getEpochSecond() / 60) - 1;
if(runtimeExperience.containsKey(pastMinute)) {
log.info("Found experience to persist.");
experienceTrackerService.handleExperienceGain(runtimeExperience.get(pastMinute));
userExperienceService.handleExperienceGain(runtimeExperience.get(pastMinute));
runtimeExperience.remove(pastMinute);
}
}

View File

@@ -4,18 +4,22 @@ import dev.sheldan.abstracto.core.listener.MessageReceivedListener;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatures;
import dev.sheldan.abstracto.experience.service.ExperienceTrackerService;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* This {@link MessageReceivedListener} is responsible to execute the {@link AUserExperienceService} in order to track
* that a certain user has send a message, and experience should be awarded.
*/
@Component
public class ExperienceTrackerListener implements MessageReceivedListener {
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Autowired
private UserManagementService userManagementService;
@@ -24,7 +28,7 @@ public class ExperienceTrackerListener implements MessageReceivedListener {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void execute(Message message) {
AUserInAServer cause = userManagementService.loadUser(message.getMember());
experienceTrackerService.addExperience(cause);
userExperienceService.addExperience(cause);
}
@Override

View File

@@ -5,7 +5,7 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatures;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import dev.sheldan.abstracto.experience.service.ExperienceTrackerService;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
@@ -13,6 +13,10 @@ import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* If a user joins, this {@link JoinListener} retrieves the previously stored {@link AUserExperience} and gives the
* {@link Member} the necessary {@link net.dv8tion.jda.api.entities.Role} according to the current configuration
*/
@Component
@Slf4j
public class JoiningUserRoleListener implements JoinListener {
@@ -24,14 +28,14 @@ public class JoiningUserRoleListener implements JoinListener {
private UserManagementService userManagementService;
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Override
public void execute(Member member, Guild guild, AUserInAServer aUserInAServer) {
AUserExperience userExperience = userExperienceManagementService.findUserInServer(aUserInAServer);
if(userExperience != null) {
log.info("User {} joined {} with previous experience. Setting up experience role again (if necessary).", member.getUser().getIdLong(), guild.getIdLong());
experienceTrackerService.syncForSingleUser(userExperience);
userExperienceService.syncForSingleUser(userExperience);
}
}

View File

@@ -4,7 +4,9 @@ import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* Repository to manage the access to the table managed by {@link AExperienceLevel}
*/
@Repository
public interface ExperienceLevelRepository extends JpaRepository<AExperienceLevel, Integer> {
AExperienceLevel findTopByExperienceNeededGreaterThanOrderByExperienceNeededAsc(Long experience);
}

View File

@@ -9,9 +9,32 @@ import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repository to manage the access to the table managed by {@link AExperienceRole}
*/
@Repository
public interface ExperienceRoleRepository extends JpaRepository<AExperienceRole, Long> {
/**
* Finds the {@link AExperienceRole} of the given {@link AServer} and {@link ARole}
* @param server The {@link AServer} to retrieve the {@link AExperienceRole} for
* @param role The {@link ARole} to filter for
* @return The {@link AExperienceRole} found or null if the query did not return any results
*/
AExperienceRole findByRoleServerAndRole(AServer server, ARole role);
/**
* Finds a list of {@link AExperienceRole} (if there are multiple ones, because of misconfiguration) of the given
* {@link AExperienceLevel} and {@link AServer}
* @param level The {@link AExperienceLevel} to search for
* @param server The {@link AServer} to search for
* @return The list of {@link AExperienceRole} found by the given parameters
*/
List<AExperienceRole> findByLevelAndRoleServer(AExperienceLevel level, AServer server);
/**
* Finds all {@link AExperienceRole} of the given {@link AServer}
* @param server The {@link AServer} to load the list of {@link AExperienceRole} for
* @return A list of {@link AExperienceRole} configured to be used on the given {@link AServer}
*/
List<AExperienceRole> findByRoleServer(AServer server);
}

View File

@@ -1,7 +1,7 @@
package dev.sheldan.abstracto.experience.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -11,11 +11,34 @@ import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repository to manage the access to the table managed by {@link AUserExperience}
*/
@Repository
public interface UserExperienceRepository extends JpaRepository<AUserExperience, Long> {
/**
* Finds all {@link AUserExperience} of the given {@link AServer}
* @param server The {@link AServer} to retriev ethe {@link AUserExperience} for
* @return A complete list of {@link AUserExperience} of the given {@link AServer}
*/
List<AUserExperience> findByUser_ServerReference(AServer server);
/**
* Retrieves the {@link AUserExperience} ordered by experience, and applies the {@link Pageable} to only filter out certain pages.
* @param server The {@link AServer} to retrieve the {@link AUserExperience} information for
* @param pageable A {@link Pageable} object to indicate the pages which should be retrieved, pagesize is 10
* @return A list of {@link AUserExperience} of the given {@link AServer} ordered by the experience of the users, paginated by the given
* configuration
*/
List<AUserExperience> findTop10ByUser_ServerReferenceOrderByExperienceDesc(AServer server, Pageable pageable);
/**
* This returns the {@link LeaderBoardEntryResult} object containing the information about the rank of a user in a server.
* @param id The {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} id to search for
* @return the {@link LeaderBoardEntryResult} of this {@link dev.sheldan.abstracto.core.models.database.AUserInAServer}
* containing rank and experience information
*/
@Query(value = "WITH user_experience_ranked AS" +
"( " +
" SELECT id, experience, experience_role_id, level_id, message_count, ROW_NUMBER() OVER ( ORDER BY experience DESC ) " +

View File

@@ -3,10 +3,11 @@ package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.experience.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.LeaderBoard;
import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
@@ -19,6 +20,7 @@ import dev.sheldan.abstracto.experience.service.management.UserExperienceManagem
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -27,10 +29,11 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@Component
@Slf4j
public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
public class AUserExperienceServiceBean implements AUserExperienceService {
private HashMap<Long, List<AServer>> runtimeExperience = new HashMap<>();
@@ -40,6 +43,9 @@ public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
@Autowired
private ExperienceLevelService experienceLevelService;
@Autowired
private ExperienceRoleService experienceRoleService;
@Autowired
private ExperienceLevelManagementService experienceLevelManagementService;
@@ -58,6 +64,13 @@ public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
@Autowired
private TemplateService templateService;
@Autowired
private BotService botService;
/**
* Creates the user in the runtime experience, if the user was not in yet. Also creates an entry for the minute, if necessary.
* @param userInAServer The {@link AUserInAServer} to be added to the list of users gaining experience
*/
@Override
public void addExperience(AUserInAServer userInAServer) {
log.trace("Adding experience for user {} in server {}", userInAServer.getUserReference().getId(), userInAServer.getServerReference().getId());
@@ -85,11 +98,17 @@ public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
return runtimeExperience;
}
/**
* Calculates the level of the given {@link AUserExperience} accoring to the given {@link AExperienceLevel} list
* @param experience The {@link AUserExperience} to calculate the level for
* @param levels The list of {@link AExperienceLevel} representing the level configuration
* @return The appropriate level according to the level config
*/
@Override
public Integer calculateLevel(AUserExperience userInAServer, List<AExperienceLevel> levels) {
public Integer calculateLevel(AUserExperience experience, List<AExperienceLevel> levels) {
AExperienceLevel lastLevel = levels.get(0);
for (AExperienceLevel level : levels) {
if(level.getExperienceNeeded() >= userInAServer.getExperience()) {
if(level.getExperienceNeeded() >= experience.getExperience()) {
return lastLevel.getLevel();
} else {
lastLevel = level;
@@ -99,34 +118,26 @@ public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
}
@Override
public AExperienceRole calculateRole(AUserExperience userInAServer, List<AExperienceRole> roles) {
if(roles.size() == 0) {
return null;
}
AExperienceRole lastRole = null;
for (AExperienceRole experienceRole : roles) {
if(userInAServer.getCurrentLevel().getLevel() >= experienceRole.getLevel().getLevel()) {
lastRole = experienceRole;
} else {
return lastRole;
}
}
return lastRole;
}
@Override
public void increaseExpForUser(AUserExperience userInAServer, Long experience, List<AExperienceLevel> levels) {
AUserInAServer user = userInAServer.getUser();
log.trace("Increasing experience for user {} in server {} by {}.", user.getUserReference().getId(), user.getServerReference().getId(), experience);
userInAServer.setExperience(userInAServer.getExperience() + experience);
Integer correctLevel = calculateLevel(userInAServer, levels);
Integer currentLevel = userInAServer.getCurrentLevel() != null ? userInAServer.getCurrentLevel().getLevel() : 0;
public boolean updateUserlevel(AUserExperience userExperience, List<AExperienceLevel> levels) {
AUserInAServer user = userExperience.getUser();
Integer correctLevel = calculateLevel(userExperience, levels);
Integer currentLevel = userExperience.getCurrentLevel() != null ? userExperience.getCurrentLevel().getLevel() : 0;
if(!correctLevel.equals(currentLevel)) {
log.info("User {} leveled from {} to {}", user.getUserReference().getId(), currentLevel, correctLevel);
userInAServer.setCurrentLevel(experienceLevelManagementService.getLevel(correctLevel));
userExperience.setCurrentLevel(experienceLevelManagementService.getLevel(correctLevel));
return true;
}
return false;
}
/**
* Calculates the actually gained experience for every user in the given servers and adds them to the users.
* This method only actually increases the message count, and calls other methods for experience gain
* and role change.
* Loads the level and role configuration for each server and sorts them for them to be used.
* Only actually updates the role, if the user also changed level.
* @param servers The list of {@link AServer} containing the user which need to gain experience
*/
@Transactional
@Override
public void handleExperienceGain(List<AServer> servers) {
@@ -137,93 +148,133 @@ public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
Integer multiplier = configService.getDoubleValue("expMultiplier", serverExp.getId()).intValue();
PrimitiveIterator.OfInt iterator = new Random().ints(serverExp.getUsers().size(), minExp, maxExp + 1).iterator();
List<AExperienceLevel> levels = experienceLevelManagementService.getLevelConfig();
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRoleForServer(serverExp);
levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded));
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(serverExp);
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
serverExp.getUsers().forEach(userInAServer -> {
Integer gainedExperience = iterator.next();
gainedExperience *= multiplier;
log.trace("Handling {}. The user gains {}", userInAServer.getUserReference().getId(), gainedExperience);
AUserExperience userExperience = userExperienceManagementService.findUserInServer(userInAServer);
increaseExpForUser(userExperience, gainedExperience.longValue(), levels);
userExperience.setMessageCount(userExperience.getMessageCount() + 1);
handleExperienceRoleForUser(userExperience, roles);
AUserExperience aUserExperience = userExperienceManagementService.incrementExpForUser(userInAServer, gainedExperience.longValue(), 1L);
updateUserlevel(aUserExperience, levels);
updateUserRole(aUserExperience, roles);
userExperienceManagementService.saveUser(aUserExperience);
});
});
}
/**
* Calculates the appropriate level of the user and changes the role, if the {@link AExperienceLevel} changes.
* This changes the config in the database, and also gives the {@link net.dv8tion.jda.api.entities.Member} the new
* {@link net.dv8tion.jda.api.entities.Role}. If the user does not warrant an {@link AExperienceRole},
* this method also removes it. The role is only changed, if the user does not have
* @param userExperience The {@link AUserExperience} object to recalculate the {@link AExperienceRole} for
* @param roles The list of {@link AExperienceRole} used as a role configuration
*/
@Override
public void handleExperienceRoleForUser(AUserExperience userExperience, List<AExperienceRole> roles) {
public void updateUserRole(AUserExperience userExperience, List<AExperienceRole> roles) {
AUserInAServer user = userExperience.getUser();
log.trace("Updating experience role for user {} in server {}", user.getUserReference().getId(), user.getServerReference().getId());
AExperienceRole role = calculateRole(userExperience, roles);
AExperienceRole role = experienceRoleService.calculateRole(userExperience, roles);
Member member = botService.getMemberInServer(user.getServerReference(), user.getUserReference());
boolean currentlyHasNoExperienceRole = userExperience.getCurrentExperienceRole() == null;
if(role == null) {
if(!currentlyHasNoExperienceRole){
roleService.removeRoleFromUser(user, userExperience.getCurrentExperienceRole().getRole());
}
userExperience.setCurrentExperienceRole(null);
return;
}
if(currentlyHasNoExperienceRole || !role.getRole().getId().equals(userExperience.getCurrentExperienceRole().getRole().getId())) {
log.info("User {} in server {} gets a new role {}", user.getUserReference().getId(), user.getServerReference().getId(), role.getRole().getId());
if(!currentlyHasNoExperienceRole) {
roleService.removeRoleFromUser(user, userExperience.getCurrentExperienceRole().getRole());
boolean userHasRoleAlready = roleService.memberHasRole(member, role.getRole());
if(!userHasRoleAlready) {
if(currentlyHasNoExperienceRole || !role.getRole().getId().equals(userExperience.getCurrentExperienceRole().getRole().getId())) {
log.info("User {} in server {} gets a new role {}", user.getUserReference().getId(), user.getServerReference().getId(), role.getRole().getId());
if(!currentlyHasNoExperienceRole) {
roleService.removeRoleFromUser(user, userExperience.getCurrentExperienceRole().getRole());
}
roleService.addRoleToUser(user, role.getRole());
}
userExperience.setCurrentExperienceRole(role);
roleService.addRoleToUser(user, userExperience.getCurrentExperienceRole().getRole());
}
userExperience.setCurrentExperienceRole(role);
}
/**
* Synchronizes the {@link net.dv8tion.jda.api.entities.Role} of all {@link net.dv8tion.jda.api.entities.Member} in
* the given {@link AServer}. This might take a long time to complete, because there are a lot of role changes.
* @param server The {@link AServer} to update the users for
*/
@Override
public void syncUserRoles(AServer server) {
List<AUserExperience> aUserExperiences = userExperienceManagementService.loadAllUsers(server);
log.info("Found {} users to synchronize", aUserExperiences.size());
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRoleForServer(server);
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
for (int i = 0; i < aUserExperiences.size(); i++) {
AUserExperience userExperience = aUserExperiences.get(i);
log.trace("Synchronizing {} out of {}", i, aUserExperiences.size());
handleExperienceRoleForUser(userExperience, roles);
updateUserRole(userExperience, roles);
}
}
/**
* Synchronizes the roles of all the users and provides feedback to the user executing
* @param server The {@link AServer} to update users for
* @param channel The {@link AChannel} in which the {@link dev.sheldan.abstracto.experience.models.templates.UserSyncStatusModel}
*/
@Override
public void syncUserRolesWithFeedback(AServer server, AChannel channel) {
List<AUserExperience> aUserExperiences = userExperienceManagementService.loadAllUsers(server);
log.info("Found {} users to synchronize", aUserExperiences.size());
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRoleForServer(server);
UserSyncStatusModel statusModel = UserSyncStatusModel.builder().currentCount(0).totalUserCount(aUserExperiences.size()).build();
MessageToSend status = templateService.renderEmbedTemplate("status_message", statusModel);
try {
Message statusMessage = messageService.createStatusMessage(status, channel).get();
int interval = Math.min(aUserExperiences.size() / 10, 100);
for (int i = 0; i < aUserExperiences.size(); i++) {
if((i % interval) == 1) {
log.trace("Updating feedback message with new index {} out of {}", i, aUserExperiences.size());
UserSyncStatusModel incrementalStatusModel = UserSyncStatusModel.builder().currentCount(i).totalUserCount(aUserExperiences.size()).build();
status = templateService.renderEmbedTemplate("status_message", incrementalStatusModel);
messageService.updateStatusMessage(channel, statusMessage.getIdLong(), status);
}
log.trace("Synchronizing {} out of {}", i, aUserExperiences.size());
AUserExperience userExperience = aUserExperiences.get(i);
handleExperienceRoleForUser(userExperience, roles);
}
UserSyncStatusModel incrementalStatusModel = UserSyncStatusModel.builder().currentCount(aUserExperiences.size()).totalUserCount(aUserExperiences.size()).build();
status = templateService.renderEmbedTemplate("status_message", incrementalStatusModel);
messageService.updateStatusMessage(channel, statusMessage.getIdLong(), status);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
executeActionOnUserExperiencesWithFeedBack(aUserExperiences, channel, (AUserExperience experience) -> {
updateUserRole(experience, roles);
});
}
@Override
public void executeActionOnUserExperiencesWithFeedBack(List<AUserExperience> experiences, AChannel channel, Consumer<AUserExperience> toExecute) {
MessageToSend status = getUserSyncStatusUpdateModel(0, experiences.size());
try {
Message statusMessage = messageService.createStatusMessage(status, channel).get();
int interval = Math.min(Math.max(experiences.size() / 10, 1), 100);
for (int i = 0; i < experiences.size(); i++) {
if((i % interval) == 1) {
log.trace("Updating feedback message with new index {} out of {}", i, experiences.size());
status = getUserSyncStatusUpdateModel(i, experiences.size());
messageService.updateStatusMessage(channel, statusMessage.getIdLong(), status);
}
toExecute.accept(experiences.get(i));
log.trace("Synchronizing {} out of {}", i, experiences.size());
}
status = getUserSyncStatusUpdateModel(experiences.size(), experiences.size());
messageService.updateStatusMessage(channel, statusMessage.getIdLong(), status);
} catch (InterruptedException | ExecutionException e) {
log.info("Failed to synchronize users.", e);
}
}
private MessageToSend getUserSyncStatusUpdateModel(Integer current, Integer total) {
UserSyncStatusModel statusModel = UserSyncStatusModel.builder().currentCount(current).totalUserCount(total).build();
return templateService.renderEmbedTemplate("user_sync_status_message", statusModel);
}
/**
* Retrieves the role configuration and executes the method responsible to sync the experience role of the user
* @param userExperience The {@link AUserExperience} to synchronize the role for
*/
@Override
public void syncForSingleUser(AUserExperience userExperience) {
AUserInAServer user = userExperience.getUser();
log.info("Synchronizing for user {} in server {}", user.getUserReference().getId(), user.getServerReference().getId());
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRoleForServer(user.getServerReference());
handleExperienceRoleForUser(userExperience, roles);
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(user.getServerReference());
updateUserRole(userExperience, roles);
}
/**
* Retrieves the leaderboard data for the given page of the given server
* @param server The {@link AServer} to retrieve the leaderboard for
* @param page The desired page on the leaderboard. The pagesize is 10
* @return The {@link LeaderBoard} containing all necessary information concerning the leaderboard
*/
@Override
public LeaderBoard findLeaderBoardData(AServer server, Integer page) {
List<AUserExperience> experiences = userExperienceManagementService.findLeaderboardUsersPaginated(server, page * 10, (page +1) * 10);
@@ -235,6 +286,11 @@ public class ExperienceTrackerServiceBean implements ExperienceTrackerService {
return LeaderBoard.builder().entries(entries).build();
}
/**
* Builds an {@link AUserExperience} and loads the appropriate rank of the passed {@link AUserInAServer}
* @param userInAServer The {@link AUserInAServer} to retrieve the {@link LeaderBoardEntry} for
* @return The {@link LeaderBoardEntry} representing one single row in the leaderboard
*/
@Override
public LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer) {
log.info("Retrieving rank for {}", userInAServer.getUserReference().getId());

View File

@@ -13,16 +13,34 @@ public class ExperienceLevelServiceBean implements ExperienceLevelService {
@Autowired
private ExperienceLevelManagementService experienceLevelManagementService;
@Override
public void createExperienceLevel(Integer level, Long experienceNeeded) {
private void createExperienceLevel(Integer level, Long experienceNeeded) {
if(!experienceLevelManagementService.levelExists(level)) {
log.trace("Creating new experience level {} with experience needed {}.", level, experienceNeeded);
experienceLevelManagementService.createExperienceLevel(level, experienceNeeded);
}
}
/**
* Creates all {@link AExperienceLevel} until (including 0) up until the passed level
* @param level The max level to create {@link dev.sheldan.abstracto.experience.models.database.AExperienceLevel} for
*/
@Override
public Long calculateExperienceForLevel(Integer level) {
public void createLevelsUntil(Integer level) {
createExperienceLevel(0, 0L);
long experience = 0L;
for (int i = 1; i < level; i++) {
experience = experience + calculateExperienceForLevel(i - 1);
createExperienceLevel(i, experience);
}
}
/**
* Calculates the required experience to reach this level. This calculated experience is relative, in the sense
* the returned experience is the increment from the experience requirement from the level before.
* @param level The level to calculate the experience amount for
* @return The needed experience to reach this level, if the user already has the level below the passed one
*/
private Long calculateExperienceForLevel(Integer level) {
return 5L * (level * level) + 50 * level + 100;
}

View File

@@ -1,9 +1,11 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.models.database.AExperienceRole;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService;
import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
@@ -11,7 +13,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Component
@Slf4j
@@ -24,30 +28,79 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
private ExperienceLevelManagementService experienceLevelService;
@Autowired
private ExperienceTrackerService experienceTrackerService;
private AUserExperienceService userExperienceService;
@Autowired
private UserExperienceManagementService userExperienceManagementService;
/**
* Unsets the current configuration for the passed level, and sets the {@link ARole} to be used for this level
* in the given {@link AServer}
* @param role The {@link ARole} to set the level to
* @param level The level the {@link ARole} should be awareded at
* @param server The {@link AServer} for which this configuration should be done
*/
@Override
public void setRoleToLevel(ARole role, Integer level, AServer server) {
public void setRoleToLevel(ARole role, Integer level, AServer server, AChannel feedbackChannel) {
AExperienceLevel experienceLevel = experienceLevelService.getLevel(level);
experienceRoleManagementService.unSetLevelInServer(experienceLevel, server);
unsetRole(role, server, feedbackChannel);
experienceRoleManagementService.removeAllRoleAssignmentsForLevelInServer(experienceLevel, server);
experienceRoleManagementService.setLevelToRole(experienceLevel, role, server);
}
/**
* Deletes the {@link AExperienceRole} and recalculates the experience for all users which currently had the associated
* {@link net.dv8tion.jda.api.entities.Role}.
* @param role The {@link ARole} to remove from the {@link dev.sheldan.abstracto.experience.models.database.AExperienceRole}
* configuration
* @param server The {@link AServer} for which the {@link ARole} should be removed from the configuration
*/
@Override
public void unsetRole(ARole role, AServer server) {
public void unsetRole(ARole role, AServer server, AChannel feedbackChannel) {
AExperienceRole roleInServer = experienceRoleManagementService.getRoleInServer(role, server);
if(roleInServer.getUsers().size() > 0) {
log.info("Recalculating the roles for {} users, because their current role was removed from experience tracking.", roleInServer.getUsers().size());
roleInServer.getUsers().forEach(userExperience -> {
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRoleForServer(server);
if(roleInServer != null) {
if(roleInServer.getUsers().size() > 0) {
log.info("Recalculating the roles for {} users, because their current role was removed from experience tracking.", roleInServer.getUsers().size());
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
roles.removeIf(role1 -> role1.getId().equals(roleInServer.getId()));
experienceTrackerService.handleExperienceRoleForUser(userExperience, roles);
});
userExperienceService.executeActionOnUserExperiencesWithFeedBack(roleInServer.getUsers(), feedbackChannel, (AUserExperience ex) -> {
userExperienceService.updateUserRole(ex, roles);
});
}
experienceRoleManagementService.unsetRole(roleInServer);
}
experienceRoleManagementService.unsetRole(roleInServer);
}
/**
* Finds the best {@link AExperienceRole} for the level of the passed {@link AUserExperience}
* @param userExperience The {@link AUserExperience} containing the level to calculate the {@link AExperienceRole}
* @param roles The role configuration to be used when calculating the appropriate {@link AExperienceRole}
* @return The best fitting {@link AExperienceRole} according to the level of the {@link AUserExperience}
*/
@Override
public AExperienceRole calculateRole(AUserExperience userExperience, List<AExperienceRole> roles) {
if(roles.size() == 0) {
return null;
}
AExperienceRole lastRole = null;
for (AExperienceRole experienceRole : roles) {
if(userExperience.getCurrentLevel().getLevel() >= experienceRole.getLevel().getLevel()) {
lastRole = experienceRole;
} else {
return lastRole;
}
}
return lastRole;
}
@Override
public AExperienceLevel getLevelOfNextRole(AExperienceLevel startLevel, AServer server) {
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
roles = roles.stream().filter(role -> role.getLevel().getLevel() < startLevel.getLevel()).collect(Collectors.toList());
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
AExperienceRole aExperienceRole = roles.stream().findFirst().orElse(null);
return aExperienceRole != null ? aExperienceRole.getLevel() : AExperienceLevel.builder().level(200).build();
}
}

View File

@@ -34,11 +34,6 @@ public class ExperienceLevelManagementServiceBean implements ExperienceLevelMana
return experienceLevelRepository.getOne(level);
}
@Override
public AExperienceLevel getLevelClosestTo(Long experience) {
return experienceLevelRepository.findTopByExperienceNeededGreaterThanOrderByExperienceNeededAsc(experience);
}
@Override
public List<AExperienceLevel> getLevelConfig() {
return experienceLevelRepository.findAll();

View File

@@ -18,8 +18,13 @@ public class ExperienceRoleManagementServiceBean implements ExperienceRoleManage
@Autowired
private ExperienceRoleRepository experienceRoleRepository;
/**
* Removes all assignments of roles for the given level
* @param level The level to remove the roles for
* @param server The server in which this should happen
*/
@Override
public void unSetLevelInServer(AExperienceLevel level, AServer server) {
public void removeAllRoleAssignmentsForLevelInServer(AExperienceLevel level, AServer server) {
log.trace("Removing all role assignments for level {}.", level.getLevel());
List<AExperienceRole> existingExperienceRoles = experienceRoleRepository.findByLevelAndRoleServer(level, server);
existingExperienceRoles.forEach(existingRole -> {
@@ -38,7 +43,7 @@ public class ExperienceRoleManagementServiceBean implements ExperienceRoleManage
}
@Override
public List<AExperienceRole> getExperienceRoleForServer(AServer server) {
public List<AExperienceRole> getExperienceRolesForServer(AServer server) {
return experienceRoleRepository.findByRoleServer(server);
}

View File

@@ -2,7 +2,7 @@ package dev.sheldan.abstracto.experience.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import dev.sheldan.abstracto.experience.repository.UserExperienceRepository;
@@ -23,12 +23,6 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
@Autowired
private ExperienceLevelManagementService experienceLevelManagementService;
@Override
public void setExperienceTo(AUserExperience aUserInAServer, Long experience) {
aUserInAServer.setExperience(experience);
repository.save(aUserInAServer);
}
@Override
public AUserExperience findUserInServer(AUserInAServer aUserInAServer) {
Optional<AUserExperience> byId = repository.findById(aUserInAServer.getUserInServerId());
@@ -38,7 +32,7 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
@Override
public AUserExperience createUserInServer(AUserInAServer aUserInAServer) {
AExperienceLevel startingLevel = experienceLevelManagementService.getLevel(0);
AUserExperience userExperience = AUserExperience
return AUserExperience
.builder()
.experience(0L)
.messageCount(0L)
@@ -46,13 +40,6 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
.id(aUserInAServer.getUserInServerId())
.currentLevel(startingLevel)
.build();
repository.save(userExperience);
return userExperience;
}
@Override
public void saveUser(AUserExperience userExperience) {
repository.save(userExperience);
}
@Override
@@ -60,6 +47,34 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
return repository.findByUser_ServerReference(server);
}
/**
* Creates or updates the {@link AUserExperience} object. Does not change the level or the role.
* @param user The {@link AUserInAServer} to increase the experience for
* @param experience The experience amount to increase by
* @param messageCount The amount of messags to increase the count by
* @return The created/changed {@link AUserExperience} object
*/
@Override
public AUserExperience incrementExpForUser(AUserInAServer user, Long experience, Long messageCount) {
Optional<AUserExperience> byId = repository.findById(user.getUserInServerId());
if(byId.isPresent()) {
AUserExperience userExperience = byId.get();
userExperience.setMessageCount(userExperience.getMessageCount() + messageCount);
userExperience.setExperience(userExperience.getExperience() + experience);
return userExperience;
} else {
AExperienceLevel startingLevel = experienceLevelManagementService.getLevel(0);
return AUserExperience
.builder()
.experience(experience)
.messageCount(messageCount)
.user(user)
.id(user.getUserInServerId())
.currentLevel(startingLevel)
.build();
}
}
@Override
public List<AUserExperience> findLeaderboardUsersPaginated(AServer aServer, Integer start, Integer end) {
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(start, end));
@@ -69,6 +84,11 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
public LeaderBoardEntryResult getRankOfUserInServer(AUserExperience userExperience) {
return repository.getRankOfUserInServer(userExperience.getId());
}
@Override
public void saveUser(AUserExperience userExperience) {
repository.save(userExperience);
}
}

View File

@@ -1,10 +0,0 @@
package dev.sheldan.abstracto.experience;
public interface LeaderBoardEntryResult {
Long getId();
Long getUserInServerId();
Long getExperience();
Integer getLevel();
Long getMessageCount();
Integer getRank();
}

View File

@@ -6,9 +6,15 @@ import lombok.Setter;
import java.util.List;
/**
* Wrapper object containing a list of {@link LeaderBoardEntry} representing a leaderboard.
*/
@Getter
@Setter
@Builder
public class LeaderBoard {
/**
* List of {@link LeaderBoardEntry} representing the leaderboard.
*/
private List<LeaderBoardEntry> entries;
}

View File

@@ -5,10 +5,19 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
/**
* Object containing a {@link AUserExperience} object and the respective rank of the user in the guild.
*/
@Getter
@Setter
@Builder
public class LeaderBoardEntry {
/**
* Object representing the current experience status of a user in a guild.
*/
private AUserExperience experience;
/**
* The rank this user has in the respective guild.
*/
private Integer rank;
}

View File

@@ -6,6 +6,9 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Represents an existing level to reach and the total necessary experience needed to reach that level.
*/
@Builder
@Entity
@NoArgsConstructor
@@ -14,7 +17,13 @@ import javax.persistence.Table;
@Getter
@Setter
public class AExperienceLevel {
/**
* The unique level from 0 to as defined in the configuration. Will be created on startup.
*/
@Id
private Integer level;
/**
* The total amount of experience needed for this level.
*/
private Long experienceNeeded;
}

View File

@@ -8,6 +8,10 @@ import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a role which is given when the user reaches a certain level. These roles are configurable per server and
* roles configured in this table are able to be set to a certain level.
*/
@Builder
@Entity
@NoArgsConstructor
@@ -17,28 +21,40 @@ import java.util.List;
@Setter
public class AExperienceRole {
/**
* The abstracto unique id of this experience role.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "level_id")
/**
* Reference to the {@link AExperienceLevel} at which this role is awarded.
*/
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "level_id", nullable = false)
private AExperienceLevel level;
/**
* Reference to the {@link AServer} at which this role is used as an experience role.
*/
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "server_id")
@JoinColumn(name = "server_id", nullable = false)
private AServer roleServer;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "role_id")
/**
* Reference to the actual {@link ARole} being awarded.
*/
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private ARole role;
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true)
/**
* Current list of {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} which were given this role.
*/
@OneToMany(fetch = FetchType.LAZY)
@Builder.Default
@JoinColumn(name = "experience_role_id")
private List<AUserExperience> users = new ArrayList<>();

View File

@@ -4,7 +4,13 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
/**
* Mapping table responsible for tracking the experience and message count of a user in a specific server.
* For easier lookup also contains the current level and the currently awarded experience role.
*/
@Builder
@Entity
@NoArgsConstructor
@@ -12,24 +18,39 @@ import javax.persistence.*;
@Table(name = "user_experience")
@Getter
@Setter
public class AUserExperience {
public class AUserExperience implements Serializable {
/**
* The {@link AUserInAServer} id which is unique for each user in a server.
*/
@Id
private Long id;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@PrimaryKeyJoinColumn
private AUserInAServer user;
/**
* The total amount of experience the user has in the guild
*/
private Long experience;
/**
* The total amount of messages the user has written in the guild resulting in the experience.
*/
private Long messageCount;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "level_id")
/**
* The {@link AExperienceLevel } which the user currently has.
*/
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "level_id", nullable = false)
private AExperienceLevel currentLevel;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
/**
* The {@link AExperienceRole} the user currently has. Can be null.
*/
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "experience_role_id")
private AExperienceRole currentExperienceRole;
}

View File

@@ -0,0 +1,36 @@
package dev.sheldan.abstracto.experience.models.database;
/**
* The object returned from the rank retrieval query.
*/
public interface LeaderBoardEntryResult {
Long getId();
/**
* The {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} id of the user
* @return
*/
Long getUserInServerId();
/**
* The experience of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer}
*/
Long getExperience();
/**
* The current raw level of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer}
*/
Integer getLevel();
/**
* The amount of messages tracked by the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer}
*/
Long getMessageCount();
/**
* The current position of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} in the leaderboard
* ordered by experience count
*/
Integer getRank();
}

View File

@@ -6,6 +6,11 @@ import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
/**
* Model used in the list of members when rendering the leaderboard command. The reason this is necessary,
* is because we need more than just the {@link AUserExperience} object, we also need the position of the user in this
* guild and the {@link Member} for convenience in the templates.
*/
@Getter
@Setter
@Builder

View File

@@ -7,10 +7,21 @@ import lombok.experimental.SuperBuilder;
import java.util.List;
/**
* Object containing the complete information passed to the leaderboard template. It contains the leaderboard
* information of the requested page of the total users and the leaderboard information of the user executing the
* command.
*/
@Getter
@Setter
@SuperBuilder
public class LeaderBoardModel extends UserInitiatedServerContext {
/**
* List of {@link LeaderBoardEntryModel} containing the information about the users from the requested page.
*/
private List<LeaderBoardEntryModel> userExperiences;
/**
* The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command.
*/
private LeaderBoardEntryModel userExecuting;
}

View File

@@ -5,10 +5,22 @@ import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
/**
* Object containing the provided property to render the rank command template. This includes the
* {@link LeaderBoardEntryModel} object containing the information from the user executing the command, as well as the
* experience needed until next level.
*/
@Getter
@Setter
@SuperBuilder
public class RankModel extends UserInitiatedServerContext {
/**
* The {@link LeaderBoardEntryModel} containing the experience information about the user executing the rank
* command.
*/
private LeaderBoardEntryModel rankUser;
/**
* The necessary experience until the next level up.
*/
private Long experienceToNextLevel;
}

View File

@@ -4,10 +4,21 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
/**
* Object containing the status update information when the user executes the
* command responsible for synchronizing the users with their experience roles. This is a very barebones
* object as it only contains the current count and the total amount.
*/
@Getter
@Setter
@Builder
public class UserSyncStatusModel {
/**
* The amount of users which already have been processed.
*/
private Integer currentCount;
/**
* The total amount of users for which the experience roles are being synchronized.
*/
private Integer totalUserCount;
}

View File

@@ -0,0 +1,128 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.models.LeaderBoard;
import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.models.database.AExperienceRole;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
/**
* Service providing the required mechanisms to provide experience tracking.
* This includes manipulations on the {@link AUserExperience} table, container for the runtime experience, synchronizing the
* user in the guild and retrieving {@link LeaderBoard} data.
*/
public interface AUserExperienceService {
/**
* Adds the given {@link AUserInAServer} to the list of user who gained experience in the current minute.
* Does not add the user to the list of users, if it is already in there.
* @param userInAServer The {@link AUserInAServer} to be added to the list of users gaining experience
*/
void addExperience(AUserInAServer userInAServer);
/**
* The current representation of the run time epxerience. Basically a HashMap of minutes to a list of {@link AServer}
* containing a list of {@link AUserInAServer} which should gain experience in the minute used as key in the HashMap
* @return
*/
HashMap<Long, List<AServer>> getRuntimeExperience();
/**
* Calculates the appropriate level of the given {@link AUserExperience} according to the given {@link AExperienceLevel}
* configuration.
* @param experience The {@link AUserExperience} to calculate the level for
* @param levels The list of {@link AExperienceLevel} representing the level configuration
* @return The appropriate level of {@link AUserExperience} according to the provided {@link AExperienceLevel} configuration
*/
Integer calculateLevel(AUserExperience experience, List<AExperienceLevel> levels);
/**
* Increases the experience of the provided {@link AUserExperience} object and and calculates the new level according
* to the provided list of {@link AExperienceLevel} used as level configuration
* @param userExperience The {@link AUserExperience} to increase the experience for
* @param levels The list of {@link AExperienceLevel} to be used as level configuration
* @return Whether or not the user changed level
*/
boolean updateUserlevel(AUserExperience userExperience, List<AExperienceLevel> levels);
/**
* Iterates through the given list of {@link AServer} and increases the experience of the users contained in the
* {@link AServer} object, also increments the level and changes the role if necessary.
* This uses the respective configurable max/minExp and multiplier for each {@link AServer} and increases the message count
* of each user by 1.
* @param serverExp The list of {@link AServer} containing the users which get experience
*/
void handleExperienceGain(List<AServer> serverExp);
/**
* Calculates the currently appropriate {@link AExperienceRole} for the given user and updates the role on the
* {@link net.dv8tion.jda.api.entities.Member} and ond the {@link AUserExperience}. Effectively synchronizes the
* state in the server and the database.
* @param userExperience The {@link AUserExperience} object to recalculate the {@link AExperienceRole} for
* @param roles The list of {@link AExperienceRole} used as a role configuration
*/
void updateUserRole(AUserExperience userExperience, List<AExperienceRole> roles);
/**
* Synchronizes the state ({@link AExperienceRole}, {@link net.dv8tion.jda.api.entities.Role})
* of all the users provided in the {@link AServer} object in the {@link AUserExperience}
* and on the {@link net.dv8tion.jda.api.entities.Member} according
* to how much experience the user has. Runs completely in the background.
* @param server The {@link AServer} to update the users for
*/
void syncUserRoles(AServer server);
/**
* Synchronizes the state ({@link AExperienceRole}, {@link net.dv8tion.jda.api.entities.Role})
* of all the users provided in the {@link AServer} object in the {@link AUserExperience}
* and on the {@link net.dv8tion.jda.api.entities.Member} according
* to how much experience the user has. This method provides feedback back to the user in the provided {@link AChannel}
* while the process is going own.
* @param server The {@link AServer} to update users for
* @param channel The {@link AChannel} in which the {@link dev.sheldan.abstracto.experience.models.templates.UserSyncStatusModel}
* should be posted to
*/
void syncUserRolesWithFeedback(AServer server, AChannel channel);
/**
* Recalculates the role of a single user in a server and synchronize the {@link net.dv8tion.jda.api.entities.Role}
* in the {@link net.dv8tion.jda.api.entities.Guild}
* @param userExperience The {@link AUserExperience} to synchronize the role for
*/
void syncForSingleUser(AUserExperience userExperience);
/**
* Loads the desired page of the ordered complete leaderboard from the {@link AServer} and returns the information as a {@link LeaderBoard}
* @param server The {@link AServer} to retrieve the leaderboard for
* @param page The desired page on the leaderboard. The pagesize is 10
* @return The {@link LeaderBoard} containing the {@link LeaderBoardEntry} containing information about the {@link AUserExperience}
* from the desired page
*/
LeaderBoard findLeaderBoardData(AServer server, Integer page);
/**
* Retrieves the {@link LeaderBoardEntry} from a specific {@link AUserInAServer} containing information about the
* gained experience
* @param userInAServer The {@link AUserInAServer} to retrieve the {@link LeaderBoardEntry} for
* @return The {@link LeaderBoardEntry} containing information about gained experience from the given {@link AUserInAServer}
* and the rank of the user in the server
*/
LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer);
/**
* Provides a method to execute an action on a list of {@link AUserExperience} and provide feedback in the given {@link AChannel}
* in the form of {@link dev.sheldan.abstracto.experience.models.templates.UserSyncStatusModel} to be rendered with a certain
* template
* @param experiences The list of {@link AUserExperience} to be working on
* @param channel The {@link AChannel} used to provide feedback to the user
* @param toExecute The {@link Consumer} which should be executed on each element of the passed list
*/
void executeActionOnUserExperiencesWithFeedBack(List<AUserExperience> experiences, AChannel channel, Consumer<AUserExperience> toExecute);
}

View File

@@ -1,7 +1,23 @@
package dev.sheldan.abstracto.experience.service;
/**
* Service responsible for operations on {@link dev.sheldan.abstracto.experience.models.database.AExperienceLevel}
* This includes creating and calculations.
*/
public interface ExperienceLevelService {
void createExperienceLevel(Integer level, Long experienceNeeded);
Long calculateExperienceForLevel(Integer level);
/**
* Creates all the levels up until the given level.
* @param level The max level to create {@link dev.sheldan.abstracto.experience.models.database.AExperienceLevel} for
*/
void createLevelsUntil(Integer level);
/**
* Calculates the required experience until the next level is reached according to the current experience and
* current level.
* @param level The current level to base the calculation of
* @param currentExperience The current experience
* @return The amount of experience required necessary to get to one level higher as currently.
*/
Long calculateExperienceToNextLevel(Integer level, Long currentExperience);
}

View File

@@ -1,9 +1,42 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.models.database.AExperienceRole;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import java.util.List;
/**
* Service providing several methods surrounding {@link dev.sheldan.abstracto.experience.models.database.AExperienceRole}.
*/
public interface ExperienceRoleService {
void setRoleToLevel(ARole role, Integer level, AServer server);
void unsetRole(ARole role, AServer server);
/**
* Creates an {@link dev.sheldan.abstracto.experience.models.database.AExperienceRole} according to the given
* parameters
* @param role The {@link ARole} to set the level to
* @param level The level the {@link ARole} should be awareded at
* @param server The {@link AServer} for which this configuration should be done
*/
void setRoleToLevel(ARole role, Integer level, AServer server, AChannel channel);
/**
* Removes the role from the {@link dev.sheldan.abstracto.experience.models.database.AExperienceRole} configuration
* @param role The {@link ARole} to remove from the {@link dev.sheldan.abstracto.experience.models.database.AExperienceRole}
* configuration
* @param server The {@link AServer} for which the {@link ARole} should be removed from the configuration
*/
void unsetRole(ARole role, AServer server, AChannel feedbackChannel);
/**
* Calculates the appropriate {@link AExperienceRole} based on the provided list of {@link AExperienceRole}
* @param userExperience The {@link AUserExperience} containing the level to calculate the {@link AExperienceRole}
* @param roles The role configuration to be used when calculating the appropriate {@link AExperienceRole}
* @return The best matching {@link AExperienceRole} accordign to the experience in the provided {@link AUserExperience}
*/
AExperienceRole calculateRole(AUserExperience userExperience, List<AExperienceRole> roles);
AExperienceLevel getLevelOfNextRole(AExperienceLevel startLevel, AServer server);
}

View File

@@ -1,28 +0,0 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.models.LeaderBoard;
import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.models.database.AExperienceRole;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import java.util.HashMap;
import java.util.List;
public interface ExperienceTrackerService {
void addExperience(AUserInAServer userInAServer);
HashMap<Long, List<AServer>> getRuntimeExperience();
Integer calculateLevel(AUserExperience userInAServer, List<AExperienceLevel> levels);
AExperienceRole calculateRole(AUserExperience userInAServer, List<AExperienceRole> roles);
void increaseExpForUser(AUserExperience userInAServer, Long experience, List<AExperienceLevel> levels);
void handleExperienceGain(List<AServer> serverExp);
void handleExperienceRoleForUser(AUserExperience userExperience, List<AExperienceRole> roles);
void syncUserRoles(AServer server);
void syncUserRolesWithFeedback(AServer server, AChannel channel);
void syncForSingleUser(AUserExperience userExperience);
LeaderBoard findLeaderBoardData(AServer server, Integer page);
LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer);
}

View File

@@ -4,10 +4,35 @@ import dev.sheldan.abstracto.experience.models.database.AExperienceLevel;
import java.util.List;
/**
* Service responsible to create and retrieve {@link AExperienceLevel} objects in the database.
*/
public interface ExperienceLevelManagementService {
/**
* Creates the level referenced by the level and the needed experience in the database.
* @param level The unique level this level should reprsent.
* @param neededExperience The total amount of experience required to reach this level.
* @return A newly created {@link AExperienceLevel} instance.
*/
AExperienceLevel createExperienceLevel(Integer level, Long neededExperience);
/**
* Checks if a {@link AExperienceLevel} level indicated by the level exists in the database. Returns true if it does.
* @param level The integer of the level to check for.
* @return A boolean indicating whether or not the level exists in the database.
*/
boolean levelExists(Integer level);
/**
* Retrieves a {@link AExperienceLevel} according to the given level.
* @param level The level of the wanted {@link AExperienceLevel} to look for
* @return If the level exists, returns the {@link AExperienceLevel}, if it does not exists, returns null.
*/
AExperienceLevel getLevel(Integer level);
AExperienceLevel getLevelClosestTo(Long experience);
/**
* Loads the complete level configuration and returns all found {@link AExperienceLevel} objects from the database.
* @return A list of {@link AExperienceLevel} objects representing the currently active configuration.
*/
List<AExperienceLevel> getLevelConfig();
}

View File

@@ -7,10 +7,47 @@ import dev.sheldan.abstracto.experience.models.database.AExperienceRole;
import java.util.List;
/**
* Service responsible to manage the {@link AExperienceRole} configuration of a server. This contains functionality to
* set/unset a level to a certain role, retrieve {@link AExperienceRole} of a certain role and load all for a given
* server.
*/
public interface ExperienceRoleManagementService {
/**
* Sets the given {@link AExperienceLevel} to the given {@link ARole} in the {@link AServer}. This will create an
* {@link AExperienceRole} instance and store it. If the role was already set in the server, this sets this role to
* the new level.
* @param level The {@link AExperienceLevel} to set the role for
* @param role The {@link ARole} to set to
* @param server The {@link AServer} in which this should happen.
*/
void setLevelToRole(AExperienceLevel level, ARole role, AServer server);
void unSetLevelInServer(AExperienceLevel level, AServer server);
/**
* Deletes *all* (if there are multiple by some chance) roles which were set to be given at the provided {@link AExperienceLevel} in the {@link AServer}
* @param level The level to remove the roles for
* @param server The server in which this should happen
*/
void removeAllRoleAssignmentsForLevelInServer(AExperienceLevel level, AServer server);
/**
* Deletes a singular {@link AExperienceRole} directly.
* @param role The {@link AExperienceRole} to delete.
*/
void unsetRole(AExperienceRole role);
/**
* Retrieves the {@link AExperienceRole} which uses the given {@link ARole} in the {@link AServer}
* @param role The {@link ARole} to search for
* @param server The {@link AServer} in which to search in
* @return
*/
AExperienceRole getRoleInServer(ARole role, AServer server);
List<AExperienceRole> getExperienceRoleForServer(AServer server);
/**
* Retrives all {@link AExperienceRole} configured in the given {@link AServer}
* @param server The server to retrieve the list of {@link AExperienceRole} for
* @return A list of {@link AExperienceRole} which are currently configured for the {@link AServer}
*/
List<AExperienceRole> getExperienceRolesForServer(AServer server);
}

View File

@@ -3,19 +3,66 @@ package dev.sheldan.abstracto.experience.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* Service used to manage the record in the {@link AUserExperience} table
*/
public interface UserExperienceManagementService {
void setExperienceTo(AUserExperience aUserInAServer, Long experience);
/**
* Retrieves the {@link AUserExperience} object for the given {@link AUserInAServer}
* @param aUserInAServer The record in the table referenced by the given {@link AUserInAServer}, if none exists, creates one.
* @return The {@link AUserExperience} object representing the {@link AUserInAServer}
*/
AUserExperience findUserInServer(AUserInAServer aUserInAServer);
/**
* Creates a {@link AUserExperience} object with the default values (0 xp, 0 messages) for the given {@link AUserInAServer} object.
* @param aUserInAServer The {@link AUserInAServer} to create the {@link AUserExperience} object for.
* @return The newly created {@link AUserExperience} object
*/
AUserExperience createUserInServer(AUserInAServer aUserInAServer);
void saveUser(AUserExperience userExperience);
/**
* Loads a list of all {@link AUserExperience} objects for a given {@link AServer}.
* @param server The {@link AServer} to retrieve the list of {@link AUserExperience} for
* @return A list of {@link AUserExperience} objects associated with the given {@link AServer}
*/
List<AUserExperience> loadAllUsers(AServer server);
/**
* Increments the experience of the {@link AUserInAServer} by the given experience and the message count.
* {@link dev.sheldan.abstracto.experience.models.database.AExperienceLevel} or {@link dev.sheldan.abstracto.experience.models.database.AExperienceRole}
* are not changed.
* @param user The {@link AUserInAServer} to increase the experience for
* @param experience The experience amount to increase by
* @param messageCount The amount of messags to increase the count by
* @return The changed/creates {@link AUserExperience} object containing the values.
*/
AUserExperience incrementExpForUser(AUserInAServer user, Long experience, Long messageCount);
/**
* Retrieves a list of {@link AUserExperience} ordered by {@link AUserExperience.experience} and only returns the positions between {@code start} and @{code end}.
* @param server The {@link AServer} to retrieve the users for
* @param start The start index in the complete ordered list to return the {@link AUserExperience} elements for
* @param end The end index for which to return a sublist of {@link AUserExperience} elements for
* @return A list desc ordered by {@link AUserExperience.experience} only containing the elements between {@code start} and @{code end}
*/
List<AUserExperience> findLeaderboardUsersPaginated(AServer server, Integer start, Integer end);
/**
* Returns the {@link LeaderBoardEntryResult} of the given {@link AUserExperience}.
* @param userExperience The {@link AUserExperience} to retrieve the information for
* @return The {@link LeaderBoardEntryResult} containing the experience, message count and rank of the given userExperience.
*/
LeaderBoardEntryResult getRankOfUserInServer(AUserExperience userExperience);
/**
* Persists the {@link AUserExperience} in the database. Required when creating it
* @param userExperience The {@link AUserExperience} to persist
*/
void saveUser(AUserExperience userExperience);
}

View File

@@ -23,12 +23,12 @@ public class Warning {
@Getter
@ManyToOne
@JoinColumn(name = "warnedUserId")
@JoinColumn(name = "warnedUserId", nullable = false)
private AUserInAServer warnedUser;
@Getter
@ManyToOne
@JoinColumn(name = "warningUserId")
@JoinColumn(name = "warningUserId", nullable = false)
private AUserInAServer warningUser;
@Getter

View File

@@ -18,22 +18,22 @@ public class EmbeddedMessage {
@Getter
@ManyToOne
@JoinColumn(name = "embeddedUser")
@JoinColumn(name = "embeddedUser", nullable = false)
private AUserInAServer embeddedUser;
@Getter
@ManyToOne
@JoinColumn(name = "embeddingUser")
@JoinColumn(name = "embeddingUser", nullable = false)
private AUserInAServer embeddingUser;
@Getter
@ManyToOne
@JoinColumn(name = "originalServer")
@JoinColumn(name = "originalServer", nullable = false)
private AServer embeddedServer;
@Getter
@ManyToOne
@JoinColumn(name = "originalChannel")
@JoinColumn(name = "originalChannel", nullable = false)
private AChannel embeddedChannel;
@Column
@@ -41,12 +41,12 @@ public class EmbeddedMessage {
@Getter
@ManyToOne
@JoinColumn(name = "embeddingServer")
@JoinColumn(name = "embeddingServer", nullable = false)
private AServer embeddingServer;
@Getter
@ManyToOne
@JoinColumn(name = "embeddingChannel")
@JoinColumn(name = "embeddingChannel", nullable = false)
private AChannel embeddingChannel;
@Column

View File

@@ -24,7 +24,7 @@ public class Reminder {
@Getter
@ManyToOne
@JoinColumn(name = "remindedUser")
@JoinColumn(name = "remindedUser", nullable = false)
private AUserInAServer remindedUser;
@Getter
@@ -32,12 +32,12 @@ public class Reminder {
@Getter
@ManyToOne
@JoinColumn(name = "channelId")
@JoinColumn(name = "channelId", nullable = false)
private AChannel channel;
@Getter
@ManyToOne
@JoinColumn(name = "serverId")
@JoinColumn(name = "serverId", nullable = false)
private AServer server;
@Getter

View File

@@ -22,7 +22,7 @@ public class StarboardPost {
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "poster")
@JoinColumn(name = "poster", nullable = false)
private AUser author;
@Column
@@ -32,11 +32,11 @@ public class StarboardPost {
private Long postMessageId;
@ManyToOne
@JoinColumn(name = "channelId")
@JoinColumn(name = "channelId", nullable = false)
private AChannel starboardChannel;
@ManyToOne
@JoinColumn(name = "sourceChannelId")
@JoinColumn(name = "sourceChannelId", nullable = false)
private AChannel sourceChanel;
@Transient
@@ -48,7 +48,9 @@ public class StarboardPost {
}
@Getter
@OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
@OneToMany(fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name="postId")
private List<StarboardPostReaction> reactions;

View File

@@ -19,11 +19,11 @@ public class StarboardPostReaction {
private Long id;
@ManyToOne
@JoinColumn(name = "reactorId")
@JoinColumn(name = "reactorId", nullable = false)
private AUser reactor;
@ManyToOne
@JoinColumn(name = "postId")
@JoinColumn(name = "postId", nullable = false)
private StarboardPost starboardPost;
}

View File

@@ -24,7 +24,8 @@ public class Suggestion {
@Getter
@ManyToOne
@JoinColumn(name = "suggesterId")
@JoinColumn(name = "suggesterId",
nullable = false)
private AUserInAServer suggester;
@Getter

View File

@@ -44,7 +44,6 @@ public class ChannelListener extends ListenerAdapter {
AServer serverObject = serverRepository.getOne(event.getGuild().getIdLong());
TextChannel createdChannel = event.getChannel();
AChannelType type = AChannel.getAChannelType(createdChannel.getType());
AChannel newChannel = channelManagementService.createChannel(createdChannel.getIdLong(), type);
serverManagementService.addChannelToServer(serverObject, newChannel);
channelManagementService.createChannel(createdChannel.getIdLong(), type, serverObject);
}
}

View File

@@ -4,6 +4,8 @@ import dev.sheldan.abstracto.core.exception.ChannelException;
import dev.sheldan.abstracto.core.exception.GuildException;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
@@ -69,6 +71,11 @@ public class BotServiceBean implements BotService {
}
}
@Override
public Member getMemberInServer(AServer server, AUser member) {
return getMemberInServer(server.getId(), member.getId());
}
@Override
public CompletableFuture<Void> deleteMessage(Long serverId, Long channelId, Long messageId) {
Optional<TextChannel> textChannelOptional = getTextChannelFromServer(serverId, channelId);

View File

@@ -3,11 +3,11 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.GuildException;
import dev.sheldan.abstracto.core.exception.RoleException;
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 lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -72,12 +72,27 @@ public class RoleServiceBean implements RoleService {
}
@Override
public boolean isRoleInServer(AServer server, ARole role) {
Optional<Guild> guildById = botService.getGuildById(server.getId());
public Role getRoleFromGuild(ARole role) {
Optional<Guild> guildById = botService.getGuildById(role.getServer().getId());
if(guildById.isPresent()) {
return guildById.get().getRoleById(role.getId()) != null;
return guildById.get().getRoleById(role.getId());
} else {
throw new GuildException(String.format("Failed to load guild %s.", server.getId()));
throw new GuildException(String.format("Failed to load guild %s.", role.getServer().getId()));
}
}
@Override
public boolean memberHasRole(Member member, Role role) {
return member.getRoles().stream().anyMatch(role1 -> role1.getIdLong() == role.getIdLong());
}
@Override
public boolean memberHasRole(Member member, ARole role) {
return member.getRoles().stream().anyMatch(role1 -> role1.getIdLong() == role.getId());
}
@Override
public boolean isRoleInServer(ARole role) {
return getRoleFromGuild(role) != null;
}
}

View File

@@ -104,8 +104,7 @@ public class StartupServiceBean implements Startup {
GuildChannel channel1 = available.stream().filter(channel -> channel.getIdLong() == aLong).findFirst().get();
log.trace("Adding new channel: {}", aLong);
AChannelType type = AChannel.getAChannelType(channel1.getType());
AChannel newChannel = channelManagementService.createChannel(channel1.getIdLong(), type);
serverManagementService.addChannelToServer(existingServer, newChannel);
channelManagementService.createChannel(channel1.getIdLong(), type, existingServer);
});
Set<Long> noLongAvailable = SetUtils.difference(knownChannelsIds, existingChannelsIds);

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelType;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.repository.ChannelRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -20,9 +21,16 @@ public class ChannelManagementServiceBean implements ChannelManagementService {
}
@Override
public AChannel createChannel(Long id, AChannelType type) {
public AChannel createChannel(Long id, AChannelType type, AServer server) {
log.info("Creating channel {} with type {}", id, type);
return repository.save(AChannel.builder().id(id).type(type).deleted(false).build());
AChannel build = AChannel
.builder()
.id(id)
.type(type)
.server(server)
.deleted(false)
.build();
return repository.save(build);
}
@Override

View File

@@ -22,7 +22,6 @@ public class ConfigManagementServiceBean implements ConfigManagementService {
createConfig(serverId, name, value);
} else {
config.setStringValue(value);
configRepository.save(config);
}
}
@@ -33,7 +32,6 @@ public class ConfigManagementServiceBean implements ConfigManagementService {
createConfig(serverId, name, value);
} else {
config.setDoubleValue(value);
configRepository.save(config);
}
}
@@ -95,7 +93,6 @@ public class ConfigManagementServiceBean implements ConfigManagementService {
public void setDoubleValue(Long serverId, String name, Double value) {
AConfig config = loadConfig(serverId, name);
config.setDoubleValue(value);
configRepository.save(config);
}
}

View File

@@ -14,7 +14,13 @@ public class RoleManagementServiceBean implements RoleManagementService {
@Override
public ARole createRole(Long id, AServer server) {
return repository.save(ARole.builder().id(id).server(server).deleted(false).build());
ARole build = ARole
.builder()
.id(id)
.server(server)
.deleted(false)
.build();
return repository.save(build);
}
@Override
@@ -25,6 +31,5 @@ public class RoleManagementServiceBean implements RoleManagementService {
@Override
public void markDeleted(ARole role) {
role.setDeleted(true);
repository.save(role);
}
}

View File

@@ -42,7 +42,6 @@ public class ServerManagementServiceBean implements ServerManagementService {
public void addChannelToServer(AServer server, AChannel channel) {
server.getChannels().add(channel);
channel.setServer(server);
repository.save(server);
}
@Override

View File

@@ -19,7 +19,7 @@ public class FeatureEnabledCondition implements CommandCondition {
String reason = "";
if(featureName != null) {
featureFlagValue = featureFlagManagementService.getFeatureFlagValue(featureName, context.getGuild().getIdLong());
reason = "Feature has been disabled.";
reason = "Feature has been disabled. Necessary feature is: " + featureName;
}
return ConditionResult.builder().reason(reason).result(featureFlagValue).build();
}

View File

@@ -21,7 +21,7 @@ public class ACommand {
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "module_id")
@JoinColumn(name = "module_id", nullable = false)
private AModule module;
}

View File

@@ -26,7 +26,7 @@ public class AModule {
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
@Builder.Default
@JoinColumn(name = "module_id")

View File

@@ -26,7 +26,7 @@ public class AChannel implements SnowFlake {
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "server_id")
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@Getter

View File

@@ -25,7 +25,7 @@ public class AChannelGroup {
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "group_server")
@JoinColumn(name = "group_server", nullable = false)
private AServer server;
@ManyToMany

View File

@@ -36,6 +36,7 @@ public class AEmote {
private Boolean custom;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "emote_server_id", nullable = false)
private AServer serverRef;

View File

@@ -21,7 +21,7 @@ public class AFeatureFlag implements SnowFlake {
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "server_id")
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@Getter

View File

@@ -13,13 +13,14 @@ import javax.persistence.*;
public class ARole implements SnowFlake {
@Id
@Getter @Setter
@Getter
@Setter
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "role_server_id")
@JoinColumn(name = "role_server_id", nullable = false)
private AServer server;
@Getter

View File

@@ -23,7 +23,6 @@ public class AServer implements SnowFlake {
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true)
@Builder.Default
@JoinColumn(name = "role_server_id")
@@ -31,7 +30,7 @@ public class AServer implements SnowFlake {
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
@Builder.Default
@JoinColumn(name = "server_id")
@@ -39,7 +38,7 @@ public class AServer implements SnowFlake {
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
@Builder.Default
@JoinColumn(name = "group_server")
@@ -47,15 +46,17 @@ public class AServer implements SnowFlake {
@OneToMany(
fetch = FetchType.LAZY,
mappedBy = "serverReference",
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
@JoinColumn(name = "serverReference")
@Builder.Default
private List<AUserInAServer> users = new ArrayList<>();
@OneToMany(
fetch = FetchType.LAZY,
mappedBy = "serverRef",
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
@JoinColumn(name = "emote_server_id")
@Builder.Default
private List<AEmote> emotes = new ArrayList<>();

View File

@@ -18,7 +18,6 @@ public class AUser {
@OneToMany(
fetch = FetchType.LAZY,
mappedBy = "userReference",
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<AUserInAServer> servers;
}

View File

@@ -17,10 +17,10 @@ public class AUserInAServer {
private Long userInServerId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "userReference")
@JoinColumn(name = "userReference", nullable = false)
private AUser userReference;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "serverReference")
@JoinColumn(name = "serverReference", nullable = false)
private AServer serverReference;
}

View File

@@ -2,11 +2,10 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Emote;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.*;
import org.springframework.stereotype.Service;
import javax.security.auth.login.LoginException;
@@ -19,6 +18,7 @@ public interface BotService {
JDA getInstance();
GuildChannelMember getServerChannelUser(Long serverId, Long channelId, Long userId);
Member getMemberInServer(Long serverId, Long memberId);
Member getMemberInServer(AServer server, AUser member);
CompletableFuture<Void> deleteMessage(Long serverId, Long channelId, Long messageId);
Optional<Emote> getEmote(Long serverId, AEmote emote);
Optional<TextChannel> getTextChannelFromServer(Guild serverId, Long textChannelId);

View File

@@ -1,8 +1,8 @@
package dev.sheldan.abstracto.core.service;
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 net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
public interface RoleService {
@@ -10,5 +10,8 @@ public interface RoleService {
void removeRoleFromUser(AUserInAServer aUserInAServer, ARole role);
void markDeleted(Role role);
void markDeleted(Long id);
boolean isRoleInServer(AServer server, ARole role);
Role getRoleFromGuild(ARole role);
boolean memberHasRole(Member member, Role role);
boolean memberHasRole(Member member, ARole role);
boolean isRoleInServer(ARole role);
}

View File

@@ -2,10 +2,11 @@ package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelType;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface ChannelManagementService {
AChannel loadChannel(Long id);
AChannel createChannel(Long id, AChannelType type);
AChannel createChannel(Long id, AChannelType type, AServer server);
void markAsDeleted(Long id);
void removeChannel(Long id);
}