[AB-132] fixing experience job not creating user experience records in the database

fixing setup command not being considered templated
changing column name for counters
fixing liquibase configuration for creating postgres functions
fixing duration format expcetion model not available in the template
enabling builds for hotfix/bugfix branches
This commit is contained in:
Sheldan
2020-09-27 00:36:07 +02:00
parent 5081c3174f
commit 0f9a0dc143
19 changed files with 376 additions and 268 deletions

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.experience.job;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.models.ServerExperience;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
@@ -32,13 +32,14 @@ public class ExperiencePersistingJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
Map<Long, List<AServer>> runtimeExperience = userExperienceService.getRuntimeExperience();
Map<Long, List<ServerExperience>> 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.");
userExperienceService.handleExperienceGain(runtimeExperience.get(pastMinute));
runtimeExperience.remove(pastMinute);
userExperienceService.handleExperienceGain(runtimeExperience.get(pastMinute)).thenAccept(aVoid ->
runtimeExperience.remove(pastMinute)
);
}
}

View File

@@ -3,15 +3,18 @@ package dev.sheldan.abstracto.experience.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.models.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.models.database.AUserExperience;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
/**
* Repository to manage the access to the table managed by {@link AUserExperience}
@@ -54,4 +57,9 @@ public interface UserExperienceRepository extends JpaRepository<AUserExperience
"FROM user_experience_ranked rank " +
"WHERE rank.id = :userInServerId", nativeQuery = true)
LeaderBoardEntryResult getRankOfUserInServer(@Param("userInServerId") Long id, @Param("serverId") Long serverId);
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<AUserExperience> findById(@NonNull Long aLong);
}

View File

@@ -8,15 +8,13 @@ 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.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.experience.config.features.ExperienceFeatureConfig;
import dev.sheldan.abstracto.experience.models.ExperienceGainResult;
import dev.sheldan.abstracto.experience.models.RoleCalculationResult;
import dev.sheldan.abstracto.experience.models.*;
import dev.sheldan.abstracto.experience.models.database.*;
import dev.sheldan.abstracto.experience.models.LeaderBoard;
import dev.sheldan.abstracto.experience.models.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.models.templates.UserSyncStatusModel;
import dev.sheldan.abstracto.experience.service.management.DisabledExpRoleManagementService;
import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService;
@@ -79,6 +77,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
@Autowired
private RunTimeExperienceService runTimeExperienceService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private AUserExperienceServiceBean self;
@@ -89,27 +90,27 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
@Override
public void addExperience(AUserInAServer userInAServer) {
Long second = Instant.now().getEpochSecond() / 60;
Map<Long, List<AServer>> runtimeExperience = runTimeExperienceService.getRuntimeExperience();
Map<Long, List<ServerExperience>> runtimeExperience = runTimeExperienceService.getRuntimeExperience();
if(runtimeExperience.containsKey(second)) {
List<AServer> existing = runtimeExperience.get(second);
List<ServerExperience> existing = runtimeExperience.get(second);
existing.forEach(server -> {
if(server.getUsers().stream().noneMatch(userInAServer1 -> userInAServer.getUserInServerId().equals(userInAServer1.getUserInServerId()))) {
server.getUsers().add(userInAServer);
if(server.getUserInServerIds().stream().noneMatch(userInAServer1 -> userInAServer.getUserInServerId().equals(userInAServer1))) {
server.getUserInServerIds().add(userInAServer.getUserInServerId());
}
});
} else {
AServer server = AServer
ServerExperience serverExperience = ServerExperience
.builder()
.id(userInAServer.getServerReference().getId())
.serverId(userInAServer.getServerReference().getId())
.build();
server.getUsers().add(userInAServer);
runtimeExperience.put(second, Arrays.asList(server));
serverExperience.getUserInServerIds().add(userInAServer.getUserInServerId());
runtimeExperience.put(second, new ArrayList<>(Arrays.asList(serverExperience)));
}
}
@Override
public Map<Long, List<AServer>> getRuntimeExperience() {
public Map<Long, List<ServerExperience>> getRuntimeExperience() {
return runTimeExperienceService.getRuntimeExperience();
}
@@ -158,47 +159,70 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
*/
@Transactional
@Override
public CompletableFuture<Void> handleExperienceGain(List<AServer> servers) {
public CompletableFuture<Void> handleExperienceGain(List<ServerExperience> servers) {
List<ExperienceGainResult> resultFutures = new ArrayList<>();
List<CompletableFuture<RoleCalculationResult>> futures = new ArrayList<>();
List<AExperienceLevel> levels = experienceLevelManagementService.getLevelConfig();
// TODO what if there are a lot in here...., transaction size etc
servers.forEach(serverExp -> {
log.trace("Handling experience for server {}", serverExp.getId());
int minExp = configService.getLongValue(ExperienceFeatureConfig.MIN_EXP_KEY, serverExp.getId()).intValue();
int maxExp = configService.getLongValue(ExperienceFeatureConfig.MAX_EXP_KEY, serverExp.getId()).intValue();
Double multiplier = configService.getDoubleValue(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverExp.getId());
PrimitiveIterator.OfInt iterator = new Random().ints(serverExp.getUsers().size(), minExp, maxExp + 1).iterator();
AServer server = serverManagementService.loadOrCreate(serverExp.getServerId());
log.trace("Handling experience for server {}", serverExp.getServerId());
int minExp = configService.getLongValue(ExperienceFeatureConfig.MIN_EXP_KEY, serverExp.getServerId()).intValue();
int maxExp = configService.getLongValue(ExperienceFeatureConfig.MAX_EXP_KEY, serverExp.getServerId()).intValue();
Double multiplier = configService.getDoubleValue(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverExp.getServerId());
PrimitiveIterator.OfInt iterator = new Random().ints(serverExp.getUserInServerIds().size(), minExp, maxExp + 1).iterator();
levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded));
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(serverExp);
List<ADisabledExpRole> disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(serverExp);
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
List<ADisabledExpRole> disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(server);
List<ARole> disabledRoles = disabledExpRoles.stream().map(ADisabledExpRole::getRole).collect(Collectors.toList());
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
serverExp.getUsers().forEach(userInAServer -> {
serverExp.getUserInServerIds().forEach(userInAServerId -> {
Integer gainedExperience = iterator.next();
AUserInAServer userInAServer = userInServerManagementService.loadUser(userInAServerId);
gainedExperience = (int) Math.floor(gainedExperience * multiplier);
Member member = botService.getMemberInServer(userInAServer);
if(!roleService.hasAnyOfTheRoles(member, disabledRoles)) {
log.trace("Handling {}. The user gains {}", userInAServer.getUserReference().getId(), gainedExperience);
AUserExperience aUserExperience = userExperienceManagementService.findUserInServer(userInAServer);
if(Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) {
Long newExperienceCount = aUserExperience.getExperience() + gainedExperience.longValue();
AExperienceLevel newLevel = calculateLevel(levels, newExperienceCount);
CompletableFuture<RoleCalculationResult> resultFuture = updateUserRole(aUserExperience, roles, newLevel.getLevel());
Long newMessageCount = aUserExperience.getMessageCount() + 1L;
Optional<AUserExperience> aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServerId);
if(aUserExperienceOptional.isPresent()) {
AUserExperience aUserExperience = aUserExperienceOptional.get();
if(Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) {
Long newExperienceCount = aUserExperience.getExperience() + gainedExperience.longValue();
AExperienceLevel newLevel = calculateLevel(levels, newExperienceCount);
CompletableFuture<RoleCalculationResult> resultFuture = updateUserRole(aUserExperience, roles, newLevel.getLevel());
Long newMessageCount = aUserExperience.getMessageCount() + 1L;
ExperienceGainResult calculationResult =
ExperienceGainResult
.builder()
.calculationResult(resultFuture)
.newExperience(newExperienceCount)
.newMessageCount(newMessageCount)
.newLevel(newLevel.getLevel())
.userInServerId(userInAServer.getUserInServerId())
.build();
resultFutures.add(calculationResult);
futures.add(resultFuture);
} else {
log.trace("Experience gain was disabled. User did not gain any experience.");
}
} else {
log.info("user experience for user {} was not found. Planning to create new instance.", userInAServer.getUserInServerId());
Long newExperience = gainedExperience.longValue();
AExperienceLevel newLevel = calculateLevel(levels, newExperience);
Long newMessageCount = 1L;
CompletableFuture<RoleCalculationResult> resultFuture = applyInitialRole(userInAServer, roles, newLevel.getLevel());
ExperienceGainResult calculationResult =
ExperienceGainResult
.builder()
.builder()
.calculationResult(resultFuture)
.newExperience(newExperienceCount)
.newExperience(newExperience)
.newMessageCount(newMessageCount)
.newLevel(newLevel.getLevel())
.createUserExperience(true)
.userInServerId(userInAServer.getUserInServerId())
.build();
.build();
resultFutures.add(calculationResult);
futures.add(resultFuture);
} else {
log.trace("Experience gain was disabled. User did not gain any experience.");
}
} else {
log.trace("User {} has a role which makes the user unable to gain experience.", userInAServer.getUserInServerId());
@@ -211,13 +235,45 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
);
}
private CompletableFuture<RoleCalculationResult> applyInitialRole(AUserInAServer aUserInAServer, List<AExperienceRole> roles, Integer currentLevel) {
if(!botService.isUserInGuild(aUserInAServer)) {
log.trace("User {} is not in server {} anymore. No role calculation done.", aUserInAServer.getUserInServerId(), aUserInAServer.getServerReference().getId());
return CompletableFuture.completedFuture(RoleCalculationResult
.builder()
.userInServerId(aUserInAServer.getUserInServerId())
.experienceRoleId(null)
.build());
}
AExperienceRole role = experienceRoleService.calculateRole(roles, currentLevel);
if(role == null) {
return CompletableFuture.completedFuture(RoleCalculationResult
.builder()
.userInServerId(aUserInAServer.getUserInServerId())
.experienceRoleId(null)
.build());
}
Long experienceRoleId = role.getId();
Long userInServerId = aUserInAServer.getUserInServerId();
return roleService.addRoleToUserFuture(aUserInAServer, role.getRole()).thenApply(aVoid -> RoleCalculationResult
.builder()
.experienceRoleId(experienceRoleId)
.userInServerId(userInServerId)
.build());
}
@Transactional
public void persistExperienceChanges(List<ExperienceGainResult> resultFutures) {
List<AExperienceLevel> levels = experienceLevelManagementService.getLevelConfig();
HashMap<Long, List<AExperienceRole>> serverRoleMapping = new HashMap<>();
resultFutures.forEach(experienceGainResult -> {
AUserInAServer user = userInServerManagementService.loadUser(experienceGainResult.getUserInServerId());
AUserExperience userExperience = userExperienceManagementService.findUserInServer(user);
AUserExperience userExperience;
if(experienceGainResult.isCreateUserExperience()) {
userExperience = userExperienceManagementService.createUserInServer(user);
log.info("Creating new experience user {}", experienceGainResult.getUserInServerId());
} else {
userExperience = userExperienceManagementService.findByUserInServerId(experienceGainResult.getUserInServerId());
}
userExperience.setMessageCount(experienceGainResult.getNewMessageCount());
userExperience.setExperience(experienceGainResult.getNewExperience());
Optional<AExperienceLevel> foundLevel = levels.stream().filter(level -> level.getLevel().equals(experienceGainResult.getNewLevel())).findFirst();
@@ -233,6 +289,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
List<AExperienceRole> roleConfig = serverRoleMapping.get(server.getId());
AExperienceRole role = experienceRoleService.calculateRole(roleConfig, userExperience.getLevelOrDefault());
userExperience.setCurrentExperienceRole(role);
if(experienceGainResult.isCreateUserExperience()) {
userExperienceManagementService.saveUser(userExperience);
}
});
}

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.models.ServerExperience;
import org.springframework.stereotype.Component;
import java.util.HashMap;
@@ -9,9 +9,9 @@ import java.util.Map;
@Component
public class RunTimeExperienceService {
private Map<Long, List<AServer>> runtimeExperience = new HashMap<>();
private Map<Long, List<ServerExperience>> runtimeExperience = new HashMap<>();
public Map<Long, List<AServer>> getRuntimeExperience() {
public Map<Long, List<ServerExperience>> getRuntimeExperience() {
return runtimeExperience;
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.experience.service.management;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.UserInServerNotFoundException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.models.database.LeaderBoardEntryResult;
@@ -30,6 +31,16 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
return byId.orElseGet(() -> createUserInServer(aUserInAServer));
}
@Override
public Optional<AUserExperience> findByUserInServerIdOptional(Long userInServerId) {
return repository.findById(userInServerId);
}
@Override
public AUserExperience findByUserInServerId(Long userInServerId) {
return findByUserInServerIdOptional(userInServerId).orElseThrow(() -> new UserInServerNotFoundException(userInServerId));
}
/**
* Initializes the {@link AUserExperience} with default values the following: 0 experience, 0 messages and experience gain enabled
* @param aUserInAServer The {@link AUserInAServer} to create the {@link AUserExperience} object for.