[AB-197] splitting utility maven module into separate maven modules

aligning some package names
removing some unnecessary computed values from liquibase
This commit is contained in:
Sheldan
2021-03-12 17:29:49 +01:00
parent e2da800d84
commit 2ed456c164
835 changed files with 12790 additions and 3310 deletions

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>starboard</artifactId>
<packaging>pom</packaging>
<modules>
<module>starboard-int</module>
<module>starboard-impl</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>starboard-impl</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>starboard-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>metrics-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,73 @@
package dev.sheldan.abstracto.starboard.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.starboard.config.StarboardFeatureDefinition;
import dev.sheldan.abstracto.starboard.model.template.MemberStarStatsModel;
import dev.sheldan.abstracto.starboard.service.StarboardService;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class StarStats extends AbstractConditionableCommand {
public static final String STARSTATS_RESPONSE_TEMPLATE = "starStats_response";
public static final String STARSTATS_SINGLE_MEMBER_RESPONSE_TEMPLATE = "starStats_single_member_response";
@Autowired
private StarboardService starboardService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
if(parameters.isEmpty()) {
return starboardService.retrieveStarStats(commandContext.getGuild().getIdLong())
.thenCompose(starStatsModel ->
FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(STARSTATS_RESPONSE_TEMPLATE, starStatsModel, commandContext.getChannel()))
).thenApply(o -> CommandResult.fromIgnored());
} else {
Member targetMember = (Member) parameters.get(0);
MemberStarStatsModel memberStarStatsModel = starboardService.retrieveStarStatsForMember(targetMember);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(STARSTATS_SINGLE_MEMBER_RESPONSE_TEMPLATE, memberStarStatsModel, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter memberParameter = Parameter.builder().templated(true).name("member").type(Member.class).optional(true).build();
List<Parameter> parameters = Collections.singletonList(memberParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("starStats")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return StarboardFeatureDefinition.STARBOARD;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.starboard.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:starboard-config.properties")
public class StarboardConfig {
}

View File

@@ -0,0 +1,56 @@
package dev.sheldan.abstracto.starboard.converter;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.starboard.model.template.StarStatsUser;
import dev.sheldan.abstracto.starboard.repository.result.StarStatsGuildUserResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class StarStatsUserConverter {
@Autowired
private MemberService memberService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private UserManagementService userManagementService;
@Autowired
private StarStatsUserConverter self;
public List<CompletableFuture<StarStatsUser>> convertToStarStatsUser(List<StarStatsGuildUserResult> users, Long serverId) {
List<CompletableFuture<StarStatsUser>> result = new ArrayList<>();
users.forEach(starStatsUserResult ->
result.add(createStarStatsUser(serverId, starStatsUserResult))
);
return result;
}
private CompletableFuture<StarStatsUser> createStarStatsUser(Long serverId, StarStatsGuildUserResult starStatsGuildUserResult) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(starStatsGuildUserResult.getUserId());
return memberService.getMemberInServerAsync(serverId, aUserInAServer.getUserReference().getId())
.thenApply(member -> self.loadStarStatsUser(starStatsGuildUserResult, member))
.exceptionally(throwable -> self.loadStarStatsUser(starStatsGuildUserResult, null));
}
@Transactional
public StarStatsUser loadStarStatsUser(StarStatsGuildUserResult starStatsGuildUserResult, net.dv8tion.jda.api.entities.Member member) {
return StarStatsUser
.builder()
.starCount(starStatsGuildUserResult.getStarCount())
.member(member)
.user(userInServerManagementService.loadOrCreateUser(starStatsGuildUserResult.getUserId()))
.build();
}
}

View File

@@ -0,0 +1,213 @@
package dev.sheldan.abstracto.starboard.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionAddedListener;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionClearedListener;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionRemovedListener;
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.metric.service.MetricTag;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.cache.CachedReactions;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.starboard.config.StarboardFeature;
import dev.sheldan.abstracto.starboard.config.StarboardFeatureDefinition;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.service.StarboardService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@Slf4j
public class StarboardListener implements AsyncReactionAddedListener, AsyncReactionRemovedListener, AsyncReactionClearedListener {
public static final String FIRST_LEVEL_THRESHOLD_KEY = "starLvl1";
@Autowired
private BotService botService;
@Autowired
private ConfigManagementService configManagementService;
@Autowired
private StarboardService starboardService;
@Autowired
private StarboardPostManagementService starboardPostManagementService;
@Autowired
private StarboardPostReactorManagementService starboardPostReactorManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private EmoteService emoteService;
@Autowired
private MetricService metricService;
public static final String STARBOARD_STARS = "starboard.stars";
public static final String STARBOARD_POSTS = "starboard.posts";
public static final String STAR_ACTION = "action";
private static final CounterMetric STARBOARD_STARS_ADDED = CounterMetric
.builder()
.name(STARBOARD_STARS)
.tagList(Arrays.asList(MetricTag.getTag(STAR_ACTION, "added")))
.build();
private static final CounterMetric STARBOARD_STARS_REMOVED = CounterMetric
.builder()
.name(STARBOARD_STARS)
.tagList(Arrays.asList(MetricTag.getTag(STAR_ACTION, "removed")))
.build();
private static final CounterMetric STARBOARD_STARS_THRESHOLD_REACHED = CounterMetric
.builder()
.name(STARBOARD_POSTS)
.tagList(Arrays.asList(MetricTag.getTag(STAR_ACTION, "threshold.reached")))
.build();
private static final CounterMetric STARBOARD_STARS_THRESHOLD_FELL = CounterMetric
.builder()
.name(STARBOARD_POSTS)
.tagList(Arrays.asList(MetricTag.getTag(STAR_ACTION, "threshold.below")))
.build();
@Override
@Transactional
public void executeReactionAdded(CachedMessage message, CachedReactions cachedReaction, ServerUser serverUser) {
if(serverUser.getUserId().equals(message.getAuthor().getAuthorId())) {
return;
}
Long guildId = message.getServerId();
AEmote aEmote = emoteService.getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, guildId);
if(emoteService.compareCachedEmoteWithAEmote(cachedReaction.getEmote(), aEmote)) {
metricService.incrementCounter(STARBOARD_STARS_ADDED);
log.info("User {} in server {} reacted with star to put a message {} from channel {} on starboard.", serverUser.getUserId(), message.getServerId(), message.getMessageId(), message.getChannelId());
Optional<CachedReactions> reactionOptional = emoteService.getReactionFromMessageByEmote(message, aEmote);
handleStarboardPostChange(message, reactionOptional.orElse(null), serverUser, true);
}
}
private void handleStarboardPostChange(CachedMessage message, CachedReactions reaction, ServerUser serverUser, boolean adding) {
Optional<StarboardPost> starboardPostOptional = starboardPostManagementService.findByMessageId(message.getMessageId());
if(reaction != null) {
AUserInAServer author = userInServerManagementService.loadOrCreateUser(message.getServerId(), message.getAuthor().getAuthorId());
List<AUserInAServer> userExceptAuthor = getUsersExcept(reaction.getUsers(), author);
Long starMinimum = getFromConfig(FIRST_LEVEL_THRESHOLD_KEY, message.getServerId());
AUserInAServer userAddingReaction = userInServerManagementService.loadOrCreateUser(serverUser);
if (userExceptAuthor.size() >= starMinimum) {
log.info("Post reached starboard minimum. Message {} in channel {} in server {} will be starred/updated.",
message.getMessageId(), message.getChannelId(), message.getServerId());
if(starboardPostOptional.isPresent()) {
updateStarboardPost(message, userAddingReaction, adding, starboardPostOptional.get(), userExceptAuthor);
} else {
metricService.incrementCounter(STARBOARD_STARS_THRESHOLD_REACHED);
log.info("Creating starboard post for message {} in channel {} in server {}", message.getMessageId(), message.getChannelId(), message.getServerId());
starboardService.createStarboardPost(message, userExceptAuthor, userAddingReaction, author);
}
} else {
if(starboardPostOptional.isPresent()) {
metricService.incrementCounter(STARBOARD_STARS_THRESHOLD_FELL);
log.info("Removing starboard post for message {} in channel {} in server {}. It fell under the threshold {}", message.getMessageId(), message.getChannelId(), message.getServerId(), starMinimum);
starboardPostOptional.ifPresent(this::completelyRemoveStarboardPost);
}
}
} else {
if(starboardPostOptional.isPresent()) {
log.info("Removing starboard post for message {} in channel {} in server {}", message.getMessageId(), message.getChannelId(), message.getServerId());
starboardPostOptional.ifPresent(this::completelyRemoveStarboardPost);
}
}
}
private void updateStarboardPost(CachedMessage message, AUserInAServer userReacting, boolean adding, StarboardPost starboardPost, List<AUserInAServer> userExceptAuthor) {
starboardPost.setIgnored(false);
// TODO handle futures correctly
starboardService.updateStarboardPost(starboardPost, message, userExceptAuthor);
if(adding) {
log.trace("Adding reactor {} from message {}", userReacting.getUserReference().getId(), message.getMessageId());
starboardPostReactorManagementService.addReactor(starboardPost, userReacting);
} else {
log.trace("Removing reactor {} from message {}", userReacting.getUserReference().getId(), message.getMessageId());
starboardPostReactorManagementService.removeReactor(starboardPost, userReacting);
}
}
private void completelyRemoveStarboardPost(StarboardPost starboardPost) {
starboardService.deleteStarboardMessagePost(starboardPost);
starboardPostManagementService.removePost(starboardPost);
}
@Override
@Transactional
public void executeReactionRemoved(CachedMessage message, CachedReactions removedReaction, ServerUser userRemoving) {
if(message.getAuthor().getAuthorId().equals(userRemoving.getUserId())) {
return;
}
Long guildId = message.getServerId();
AEmote aEmote = emoteService.getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, guildId);
if(emoteService.compareCachedEmoteWithAEmote(removedReaction.getEmote(), aEmote)) {
metricService.incrementCounter(STARBOARD_STARS_REMOVED);
log.info("User {} in server {} removed star reaction from message {} on starboard.",
userRemoving.getUserId(), message.getServerId(), message.getMessageId());
Optional<CachedReactions> reactionOptional = emoteService.getReactionFromMessageByEmote(message, aEmote);
handleStarboardPostChange(message, reactionOptional.orElse(null), userRemoving, false);
}
}
private Long getFromConfig(String key, Long guildId) {
return configManagementService.loadConfig(guildId, key).getLongValue();
}
private List<AUserInAServer> getUsersExcept(List<ServerUser> users, AUserInAServer author) {
return users.stream().filter(user -> !(user.getServerId().equals(author.getServerReference().getId()) && user.getUserId().equals(author.getUserReference().getId()))).map(serverUser -> {
Optional<AUserInAServer> aUserInAServer = userInServerManagementService.loadUserOptional(serverUser.getServerId(), serverUser.getUserId());
return aUserInAServer.orElse(null);
}).filter(Objects::nonNull).collect(Collectors.toList());
}
@Override
public FeatureDefinition getFeature() {
return StarboardFeatureDefinition.STARBOARD;
}
@Override
public void executeReactionCleared(CachedMessage message) {
Optional<StarboardPost> starboardPostOptional = starboardPostManagementService.findByMessageId(message.getMessageId());
starboardPostOptional.ifPresent(starboardPost -> {
log.info("Reactions on message {} in channel {} in server {} were cleared. Completely deleting the starboard post {}.",
message.getMessageId(), message.getChannelId(), message.getServerId(), starboardPost.getId());
starboardPostReactorManagementService.removeReactors(starboardPost);
completelyRemoveStarboardPost(starboardPost);
});
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(STARBOARD_STARS_ADDED, "Star reaction added");
metricService.registerCounter(STARBOARD_STARS_REMOVED, "Star reaction removed");
metricService.registerCounter(STARBOARD_STARS_THRESHOLD_REACHED, "Starboard posts reaching threshold");
metricService.registerCounter(STARBOARD_STARS_THRESHOLD_FELL, "Starboard posts falling below threshold");
}
}

View File

@@ -0,0 +1,38 @@
package dev.sheldan.abstracto.starboard.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageDeletedListener;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.starboard.config.StarboardFeatureDefinition;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class StarboardPostDeletedListener implements AsyncMessageDeletedListener {
@Autowired
private StarboardPostManagementService starboardPostManagementService;
@Override
public void execute(CachedMessage messageBefore) {
Optional<StarboardPost> byStarboardPostId = starboardPostManagementService.findByStarboardPostId(messageBefore.getMessageId());
if(byStarboardPostId.isPresent()) {
StarboardPost post = byStarboardPostId.get();
log.info("Removing starboard post: message {}, channel {}, server {}, because the message was deleted",
post.getPostMessageId(), post.getSourceChannel().getId(), messageBefore.getServerId());
starboardPostManagementService.setStarboardPostIgnored(messageBefore.getMessageId(), true);
}
}
@Override
public FeatureDefinition getFeature() {
return StarboardFeatureDefinition.STARBOARD;
}
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.starboard.repository;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.repository.result.StarStatsGuildUserResult;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface StarboardPostReactionRepository extends JpaRepository<StarboardPostReaction, Long> {
void deleteByReactorAndStarboardPost(AUserInAServer user, StarboardPost post);
void deleteByStarboardPost(StarboardPost post);
@Query(value = "SELECT r.reactor_user_in_server_id as userId, COUNT(*) AS starCount \n" +
"FROM starboard_post_reaction r \n" +
"INNER JOIN starboard_post p ON p.id = r.post_id\n" +
"WHERE p.server_id = :serverId\n" +
" AND p.ignored = false\n" +
"GROUP BY r.reactor_user_in_server_id \n" +
"ORDER BY starCount DESC \n" +
"LIMIT :count", nativeQuery = true)
List<StarStatsGuildUserResult> findTopStarGiverInServer(Long serverId, Integer count);
@Query(value = "SELECT COUNT(*) \n" +
"FROM starboard_post_reaction r \n" +
"INNER JOIN starboard_post p ON p.id = r.post_id\n" +
"WHERE p.server_id = :serverId\n"
, nativeQuery = true)
Integer getReactionCountByServer(Long serverId);
@Query(value = "SELECT p.author_user_in_server_id as userId, COUNT(*) AS starCount \n" +
"FROM starboard_post_reaction r \n" +
"INNER JOIN starboard_post p ON p.id = r.post_id\n" +
"WHERE p.server_id = :serverId\n" +
" AND p.ignored = false\n" +
"GROUP BY p.author_user_in_server_id \n" +
"ORDER BY starCount DESC \n" +
"LIMIT :count", nativeQuery = true)
List<StarStatsGuildUserResult> retrieveTopStarReceiverInServer(Long serverId, Integer count);
}

View File

@@ -0,0 +1,52 @@
package dev.sheldan.abstracto.starboard.repository;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface StarboardPostRepository extends JpaRepository<StarboardPost, Long> {
StarboardPost findByPostMessageId(Long messageId);
StarboardPost findByStarboardMessageId(Long messageId);
boolean existsByStarboardMessageId(Long messageId);
List<StarboardPost> findByServer_Id(Long serverId);
@Query(value = "SELECT p.id, COUNT(*) AS starCount \n" +
" FROM starboard_post p \n" +
" INNER JOIN starboard_post_reaction r ON p.id = r.post_id\n" +
" INNER JOIN user_in_server usi ON usi.user_in_server_id = p.author_user_in_server_id\n" +
" WHERE p.server_id = :serverId\n" +
" AND p.ignored = false\n" +
" AND usi.user_id = :userId\n" +
" GROUP BY p.id \n" +
" ORDER BY starCount DESC \n" +
" LIMIT :count", nativeQuery = true)
List<Long> getTopStarboardPostsForUser(Long serverId, Long userId, Integer count);
@Query(value = "SELECT COUNT(*) AS starCount\n" +
"FROM starboard_post_reaction r \n" +
" INNER JOIN starboard_post p ON p.id = r.post_id \n" +
" INNER JOIN user_in_server usi ON usi.user_in_server_id = r.reactor_user_in_server_id \n" +
" WHERE usi.user_id = :userId \n" +
" AND p.ignored = false\n" +
" AND r.server_id = :serverId", nativeQuery = true)
Long getGivenStarsOfUserInServer(Long serverId, Long userId);
@Query(value = "SELECT COUNT(*) AS starCount\n" +
" FROM starboard_post_reaction r \n" +
" INNER JOIN starboard_post p ON p.id = r.post_id \n" +
" INNER JOIN user_in_server usi ON usi.user_in_server_id = p.author_user_in_server_id \n" +
" WHERE p.author_user_in_server_id = usi.user_in_server_id \n" +
" AND usi.user_id = :userId \n" +
" AND p.ignored = false\n" +
" AND r.server_id = :serverId", nativeQuery = true)
Long getReceivedStarsOfUserInServer(Long serverId, Long userId);
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.starboard.repository.result;
public interface StarStatsGuildUserResult {
// this is the User in Server Id
Long getUserId();
Integer getStarCount();
}

View File

@@ -0,0 +1,264 @@
package dev.sheldan.abstracto.starboard.service;
import dev.sheldan.abstracto.core.exception.UserInServerNotFoundException;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.models.property.SystemConfigProperty;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.starboard.config.StarboardFeature;
import dev.sheldan.abstracto.starboard.config.StarboardPostTarget;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.template.*;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService;
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.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
@Component
@Slf4j
public class StarboardServiceBean implements StarboardService {
public static final String STARBOARD_POST_TEMPLATE = "starboard_post";
@Autowired
private MemberService memberService;
@Autowired
private GuildService guildService;
@Autowired
private ChannelService channelService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private TemplateService templateService;
@Autowired
private ConfigService configService;
@Autowired
private StarboardPostManagementService starboardPostManagementService;
@Autowired
private StarboardPostReactorManagementService starboardPostReactorManagementService;
@Autowired
private PostTargetManagement postTargetManagement;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private EmoteService emoteService;
@Autowired
private DefaultConfigManagementService defaultConfigManagementService;
@Autowired
private MessageService messageService;
@Autowired
private StarboardServiceBean self;
@Override
public CompletableFuture<Void> createStarboardPost(CachedMessage message, List<AUserInAServer> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser) {
Long starredUserId = starredUser.getUserInServerId();
List<Long> userExceptAuthorIds = userExceptAuthor.stream().map(AUserInAServer::getUserInServerId).collect(Collectors.toList());
return buildStarboardPostModel(message, userExceptAuthor.size()).thenCompose(starboardPostModel ->
self.sendStarboardPostAndStore(message, starredUserId, userExceptAuthorIds, starboardPostModel)
);
}
@Transactional
public CompletionStage<Void> sendStarboardPostAndStore(CachedMessage message, Long starredUserId, List<Long> userExceptAuthorIds, StarboardPostModel starboardPostModel) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(STARBOARD_POST_TEMPLATE, starboardPostModel);
PostTarget starboard = postTargetManagement.getPostTarget(StarboardPostTarget.STARBOARD.getKey(), message.getServerId());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, StarboardPostTarget.STARBOARD, message.getServerId());
Long starboardChannelId = starboard.getChannelReference().getId();
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).thenAccept(aVoid ->
self.persistPost(message, userExceptAuthorIds, completableFutures, starboardChannelId, starredUserId)
);
}
@Transactional
public void persistPost(CachedMessage message, List<Long> userExceptAuthorIds, List<CompletableFuture<Message>> completableFutures, Long starboardChannelId, Long starredUserId) {
AUserInAServer innerStarredUser = userInServerManagementService.loadUserOptional(starredUserId).orElseThrow(() -> new UserInServerNotFoundException(starredUserId));
AChannel starboardChannel = channelManagementService.loadChannel(starboardChannelId);
Message message1 = completableFutures.get(0).join();
AServerAChannelMessage aServerAChannelMessage = AServerAChannelMessage
.builder()
.messageId(message1.getIdLong())
.channel(starboardChannel)
.server(starboardChannel.getServer())
.build();
StarboardPost starboardPost = starboardPostManagementService.createStarboardPost(message, innerStarredUser, aServerAChannelMessage);
log.info("Persisting starboard post in channel {} with message {} with {} reactors.", message1.getId(),starboardChannelId, userExceptAuthorIds.size());
if(userExceptAuthorIds.isEmpty()) {
log.warn("There are no user ids except the author for the reactions in post {} in guild {} for message {} in channel {}.", starboardPost.getId(), message.getChannelId(), message.getMessageId(), message.getChannelId());
}
userExceptAuthorIds.forEach(aLong -> {
AUserInAServer user = userInServerManagementService.loadUserOptional(aLong).orElseThrow(() -> new UserInServerNotFoundException(aLong));
starboardPostReactorManagementService.addReactor(starboardPost, user);
});
}
private CompletableFuture<StarboardPostModel> buildStarboardPostModel(CachedMessage message, Integer starCount) {
return memberService.getMemberInServerAsync(message.getServerId(), message.getAuthor().getAuthorId()).thenApply(member -> {
Optional<TextChannel> channel = channelService.getTextChannelFromServerOptional(message.getServerId(), message.getChannelId());
Optional<Guild> guild = guildService.getGuildByIdOptional(message.getServerId());
// TODO use model objects instead of building entity models
AChannel aChannel = AChannel.builder().id(message.getChannelId()).build();
AUser user = AUser.builder().id(message.getAuthor().getAuthorId()).build();
String starLevelEmote = getAppropriateEmote(message.getServerId(), starCount);
return StarboardPostModel
.builder()
.message(message)
.author(member)
.channel(channel.orElse(null))
.aChannel(aChannel)
.starCount(starCount)
.guild(guild.orElse(null))
.user(user)
.starLevelEmote(starLevelEmote)
.build();
});
}
@Override
public CompletableFuture<Void> updateStarboardPost(StarboardPost post, CachedMessage message, List<AUserInAServer> userExceptAuthor) {
int starCount = userExceptAuthor.size();
log.info("Updating starboard post {} in server {} with reactors {}.", post.getId(), post.getSourceChannel().getServer().getId(), starCount);
return buildStarboardPostModel(message, starCount).thenCompose(starboardPostModel -> {
MessageToSend messageToSend = templateService.renderEmbedTemplate(STARBOARD_POST_TEMPLATE, starboardPostModel);
List<CompletableFuture<Message>> futures = postTargetService.editOrCreatedInPostTarget(post.getStarboardMessageId(), messageToSend, StarboardPostTarget.STARBOARD, message.getServerId());
Long starboardPostId = post.getId();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(aVoid -> {
Optional<StarboardPost> innerPost = starboardPostManagementService.findByStarboardPostId(starboardPostId);
innerPost.ifPresent(starboardPost -> starboardPostManagementService.setStarboardPostMessageId(starboardPost, futures.get(0).join().getIdLong()));
});
});
}
@Override
public void deleteStarboardMessagePost(StarboardPost message) {
AChannel starboardChannel = message.getStarboardChannel();
log.info("Deleting starboard post {} in server {}", message.getId(), message.getSourceChannel().getServer().getId());
messageService.deleteMessageInChannelInServer(starboardChannel.getServer().getId(), starboardChannel.getId(), message.getStarboardMessageId());
}
@Override
public CompletableFuture<GuildStarStatsModel> retrieveStarStats(Long serverId) {
int count = 3;
List<CompletableFuture<StarStatsUser>> topStarGiverFutures = starboardPostReactorManagementService.retrieveTopStarGiver(serverId, count);
List<CompletableFuture<StarStatsUser>> topStarReceiverFutures = starboardPostReactorManagementService.retrieveTopStarReceiver(serverId, count);
List<CompletableFuture> allFutures = new ArrayList<>();
allFutures.addAll(topStarGiverFutures);
allFutures.addAll(topStarReceiverFutures);
return FutureUtils.toSingleFuture(allFutures).thenApply(aVoid -> {
List<StarboardPost> starboardPosts = starboardPostManagementService.retrieveTopPosts(serverId, count);
List<StarStatsPost> starStatsPosts = starboardPosts.stream().map(this::fromStarboardPost).collect(Collectors.toList());
Integer postCount = starboardPostManagementService.getPostCount(serverId);
Integer reactionCount = starboardPostReactorManagementService.getStarCount(serverId);
List<String> emotes = new ArrayList<>();
for (int i = 1; i < count + 1; i++) {
emotes.add(getStarboardRankingEmote(serverId, i));
}
List<StarStatsUser> topStarGivers = topStarGiverFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
List<StarStatsUser> topStarReceiver = topStarReceiverFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
return GuildStarStatsModel
.builder()
.badgeEmotes(emotes)
.starGiver(topStarGivers)
.starReceiver(topStarReceiver)
.topPosts(starStatsPosts)
.starredMessages(postCount)
.totalStars(reactionCount)
.build();
});
}
@Override
public MemberStarStatsModel retrieveStarStatsForMember(Member member) {
int count = 3;
Long receivedStars = starboardPostManagementService.retrieveReceivedStarsOfUserInServer(member.getGuild().getIdLong(), member.getIdLong());
Long givenStars = starboardPostManagementService.retrieveGivenStarsOfUserInServer(member.getGuild().getIdLong(), member.getIdLong());
List<StarboardPost> topPosts = starboardPostManagementService.retrieveTopPostsForUserInServer(member.getGuild().getIdLong(), member.getIdLong(), count);
List<StarStatsPost> starStatsPosts = topPosts.stream().map(this::fromStarboardPost).collect(Collectors.toList());
List<String> emotes = new ArrayList<>();
for (int i = 1; i < count + 1; i++) {
emotes.add(getStarboardRankingEmote(member.getGuild().getIdLong(), i));
}
return MemberStarStatsModel
.builder()
.member(member)
.topPosts(starStatsPosts)
.badgeEmotes(emotes)
.receivedStars(receivedStars)
.givenStars(givenStars)
.build();
}
public StarStatsPost fromStarboardPost(StarboardPost starboardPost) {
AChannel channel = starboardPost.getStarboardChannel();
return StarStatsPost
.builder()
.serverId(starboardPost.getServer().getId())
.channelId(channel.getId())
.messageId(starboardPost.getPostMessageId())
.starCount(starboardPost.getReactions().size())
.build();
}
private String getStarboardRankingEmote(Long serverId, Integer position) {
return emoteService.getUsableEmoteOrDefault(serverId, buildBadgeName(position));
}
private String buildBadgeName(Integer position) {
return StarboardFeature.STAR_BADGE_EMOTE_PREFIX + position;
}
private String getAppropriateEmote(Long serverId, Integer starCount) {
int maxLevels = defaultConfigManagementService.getDefaultConfig(StarboardFeature.STAR_LEVELS_CONFIG_KEY).getLongValue().intValue();
for(int i = maxLevels; i > 0; i--) {
String key = StarboardFeature.STAR_LVL_CONFIG_PREFIX + i;
SystemConfigProperty defaultStars = defaultConfigManagementService.getDefaultConfig(key);
Long starMinimum = configService.getLongValue(key, serverId, defaultStars.getLongValue());
if(starCount >= starMinimum) {
return emoteService.getUsableEmoteOrDefault(serverId, StarboardFeature.STAR_EMOTE_PREFIX + i);
}
}
return emoteService.getUsableEmoteOrDefault(serverId, StarboardFeature.STAR_EMOTE_PREFIX);
}
}

View File

@@ -0,0 +1,125 @@
package dev.sheldan.abstracto.starboard.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.repository.StarboardPostRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@Component
@Slf4j
public class StarboardPostManagementServiceBean implements StarboardPostManagementService {
@Autowired
private StarboardPostRepository repository;
@Autowired
private ChannelManagementService channelManagementService;
@Override
public StarboardPost createStarboardPost(CachedMessage starredMessage, AUserInAServer starredUser, AServerAChannelMessage starboardPost) {
AChannel build = channelManagementService.loadChannel(starredMessage.getChannelId());
StarboardPost post = StarboardPost
.builder()
.author(starredUser)
.postMessageId(starredMessage.getMessageId())
.sourceChannel(build)
.ignored(false)
.server(starboardPost.getServer())
.starboardMessageId(starboardPost.getMessageId())
.starboardChannel(starboardPost.getChannel())
.starredDate(Instant.now())
.build();
log.info("Persisting starboard post for message {} in channel {} in server {} on starboard at message {} in channel {} and server {} of user {}.",
starredMessage.getMessageId(), starredMessage.getChannelId(), starredMessage.getServerId(),
starboardPost.getMessageId(), starboardPost.getChannel().getId(), starboardPost.getServer().getId(),
starredUser.getUserReference().getId());
repository.save(post);
return post;
}
@Override
public StarboardPost createStarboardPost(StarboardPost post) {
return repository.save(post);
}
@Override
public void setStarboardPostMessageId(StarboardPost post, Long messageId) {
post.setStarboardMessageId(messageId);
repository.save(post);
}
@Override
public List<StarboardPost> retrieveTopPosts(Long serverId, Integer count) {
List<StarboardPost> posts = retrieveAllPosts(serverId);
posts.sort(Comparator.comparingInt(o -> o.getReactions().size()));
Collections.reverse(posts);
return posts.subList(0, Math.min(count, posts.size()));
}
@Override
public List<StarboardPost> retrieveTopPostsForUserInServer(Long serverId, Long userId, Integer count) {
List<Long> topPostIds = repository.getTopStarboardPostsForUser(serverId, userId, count);
return repository.findAllById(topPostIds);
}
@Override
public Long retrieveGivenStarsOfUserInServer(Long serverId, Long userId) {
return repository.getGivenStarsOfUserInServer(serverId, userId);
}
@Override
public Long retrieveReceivedStarsOfUserInServer(Long serverId, Long userId) {
return repository.getReceivedStarsOfUserInServer(serverId, userId);
}
@Override
public List<StarboardPost> retrieveAllPosts(Long serverId) {
return repository.findByServer_Id(serverId);
}
@Override
public Integer getPostCount(Long serverId) {
return retrieveAllPosts(serverId).size();
}
@Override
public Optional<StarboardPost> findByMessageId(Long messageId) {
return Optional.ofNullable(repository.findByPostMessageId(messageId));
}
@Override
public Optional<StarboardPost> findByStarboardPostId(Long postId) {
return Optional.ofNullable(repository.findByStarboardMessageId(postId));
}
@Override
public void setStarboardPostIgnored(Long messageId, Boolean newValue) {
StarboardPost post = repository.findByStarboardMessageId(messageId);
post.setIgnored(newValue);
repository.save(post);
}
@Override
public boolean isStarboardPost(Long messageId) {
return repository.existsByStarboardMessageId(messageId);
}
@Override
public void removePost(StarboardPost starboardPost) {
starboardPost.getReactions().clear();
repository.delete(starboardPost);
}
}

View File

@@ -0,0 +1,69 @@
package dev.sheldan.abstracto.starboard.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.starboard.converter.StarStatsUserConverter;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.model.template.StarStatsUser;
import dev.sheldan.abstracto.starboard.repository.result.StarStatsGuildUserResult;
import dev.sheldan.abstracto.starboard.repository.StarboardPostReactionRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class StarboardPostReactorManagementServiceBean implements StarboardPostReactorManagementService {
@Autowired
private StarboardPostReactionRepository repository;
@Autowired
private StarStatsUserConverter converter;
@Override
public StarboardPostReaction addReactor(StarboardPost post, AUserInAServer user) {
StarboardPostReaction reactor = StarboardPostReaction
.builder()
.starboardPost(post)
.reactor(user)
.server(user.getServerReference())
.build();
log.info("Persisting the reactor {} for starboard post {} in server {}.", user.getUserReference().getId(), post.getId(), user.getServerReference().getId());
repository.save(reactor);
return reactor;
}
@Override
public void removeReactor(StarboardPost post, AUserInAServer user) {
log.info("Removing reactor {} from post {} in server {}.", user.getUserReference().getId(), post.getId(), user.getServerReference().getId());
repository.deleteByReactorAndStarboardPost(user, post);
}
@Override
public void removeReactors(StarboardPost post) {
log.info("Removing all {} reactors from starboard post {}", post.getReactions().size(), post.getId());
repository.deleteByStarboardPost(post);
}
@Override
public Integer getStarCount(Long serverId) {
return repository.getReactionCountByServer(serverId);
}
@Override
public List<CompletableFuture<StarStatsUser>> retrieveTopStarGiver(Long serverId, Integer count) {
List<StarStatsGuildUserResult> starGivers = repository.findTopStarGiverInServer(serverId, count);
return converter.convertToStarStatsUser(starGivers, serverId);
}
@Override
public List<CompletableFuture<StarStatsUser>> retrieveTopStarReceiver(Long serverId, Integer count) {
List<StarStatsGuildUserResult> starReceivers = repository.retrieveTopStarReceiverInServer(serverId, count);
return converter.convertToStarStatsUser(starReceivers, serverId);
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../dbchangelog-3.8.xsd" >
<include file="starboard-tables/tables.xml" relativeToChangelogFile="true"/>
<include file="starboard-seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,21 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="starboardFeature" value="(SELECT id FROM feature WHERE key = 'starboard')"/>
<changeSet author="Sheldan" id="starboard-commands">
<insert tableName="command">
<column name="name" value="starStats"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${starboardFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,12 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<include file="default_emote.xml" relativeToChangelogFile="true"/>
<include file="feature.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,43 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard_default_emote-insert">
<insert tableName="default_emote">
<column name="emote_key" value="star"/>
<column name="name" value="⭐"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="star1"/>
<column name="name" value="⭐"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="star2"/>
<column name="name" value="🌟"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="star3"/>
<column name="name" value="💫"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="star4"/>
<column name="name" value="🌠"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="starboardBadge1"/>
<column name="name" value="🥇"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="starboardBadge2"/>
<column name="name" value="🥈"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="starboardBadge3"/>
<column name="name" value="🥉"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard_feature-insertion">
<insert tableName="feature">
<column name="key" value="starboard"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,69 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard-table">
<createTable tableName="starboard_post">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="starboard_post_pkey"/>
</column>
<column name="ignored" type="BOOLEAN">
<constraints nullable="true"/>
</column>
<column name="post_message_id" type="BIGINT">
<constraints nullable="true"/>
</column>
<column name="starboard_message_id" type="BIGINT">
<constraints nullable="true"/>
</column>
<column name="starred_date" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="true"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="true"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="author_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="source_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<createIndex indexName="idx_starboard_post_message" tableName="starboard_post">
<column name="post_message_id"/>
</createIndex>
<createIndex indexName="idx_starboard_starboard_message" tableName="starboard_post">
<column name="starboard_message_id"/>
</createIndex>
<createIndex indexName="idx_starboard_server" tableName="starboard_post">
<column name="server_id"/>
</createIndex>
<addForeignKeyConstraint baseColumnNames="author_user_in_server_id" baseTableName="starboard_post" constraintName="fk_starboard_post_author_user_in_server_id" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="channel_id" baseTableName="starboard_post" constraintName="fk_starboard_post_channel" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="source_channel_id" baseTableName="starboard_post" constraintName="fk_starboard_post_source_channel" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="starboard_post" constraintName="fk_starboard_post_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS starboard_post_update_trigger ON starboard_post;
CREATE TRIGGER starboard_post_update_trigger BEFORE UPDATE ON starboard_post FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS starboard_insert_trigger ON starboard_post;
CREATE TRIGGER starboard_post_insert_trigger BEFORE INSERT ON starboard_post FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,42 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard_post_reaction-table">
<createTable tableName="starboard_post_reaction">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="starboard_post_reaction_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="true"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="reactor_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="post_id" type="BIGINT">
<constraints nullable="true"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="reactor_user_in_server_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_reactor" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="post_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_post" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="starboard_post" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_server" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS starboard_post_reaction_update_trigger ON starboard_post_reaction;
CREATE TRIGGER starboard_post_reaction_update_trigger BEFORE UPDATE ON starboard_post_reaction FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS starboard_post_reaction_insert_trigger ON starboard_post_reaction;
CREATE TRIGGER starboard_post_reaction_insert_trigger BEFORE INSERT ON starboard_post_reaction FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<include file="starboard_post.xml" relativeToChangelogFile="true"/>
<include file="starboard_post_reaction.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog-3.8.xsd" >
<include file="1.0-starboard/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,19 @@
abstracto.systemConfigs.starLvl1.name=starLvl1
abstracto.systemConfigs.starLvl1.longValue=5
abstracto.systemConfigs.starLvl2.name=starLvl2
abstracto.systemConfigs.starLvl2.longValue=8
abstracto.systemConfigs.starLvl3.name=starLvl3
abstracto.systemConfigs.starLvl3.longValue=13
abstracto.systemConfigs.starLvl4.name=starLvl4
abstracto.systemConfigs.starLvl4.longValue=17
abstracto.systemConfigs.starLvls.name=starLvls
abstracto.systemConfigs.starLvls.longValue=4
abstracto.featureFlags.starboard.featureName=starboard
abstracto.featureFlags.starboard.enabled=false
abstracto.postTargets.starboard.name=starboard

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.starboard.command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.starboard.model.template.GuildStarStatsModel;
import dev.sheldan.abstracto.starboard.model.template.MemberStarStatsModel;
import dev.sheldan.abstracto.starboard.service.StarboardService;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarStatsTest {
@InjectMocks
private StarStats testUnit;
@Mock
private StarboardService starboardService;
@Mock
private ChannelService channelService;
@Test
public void executeCommand() {
CommandContext noParameters = CommandTestUtilities.getNoParameters();
GuildStarStatsModel guildStarStatsModel = Mockito.mock(GuildStarStatsModel.class);
when(starboardService.retrieveStarStats(noParameters.getGuild().getIdLong())).thenReturn(CompletableFuture.completedFuture(guildStarStatsModel));
CompletableFuture<CommandResult> result = testUnit.executeAsync(noParameters);
verify(channelService, times(1)).sendEmbedTemplateInTextChannelList(StarStats.STARSTATS_RESPONSE_TEMPLATE, guildStarStatsModel, noParameters.getChannel());
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void executeCommandForMember() {
Member member = Mockito.mock(Member.class);
CommandContext memberParameter = CommandTestUtilities.getWithParameters(Arrays.asList(member));
MemberStarStatsModel model = Mockito.mock(MemberStarStatsModel.class);
when(starboardService.retrieveStarStatsForMember(member)).thenReturn(model);
CompletableFuture<CommandResult> result = testUnit.executeAsync(memberParameter);
verify(channelService, times(1)).sendEmbedTemplateInTextChannelList(StarStats.STARSTATS_SINGLE_MEMBER_RESPONSE_TEMPLATE, model, memberParameter.getChannel());
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());
}
}

View File

@@ -0,0 +1,93 @@
package dev.sheldan.abstracto.starboard.converter;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.starboard.model.template.StarStatsUser;
import dev.sheldan.abstracto.starboard.repository.result.StarStatsGuildUserResult;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarStatsUserConverterTest {
@InjectMocks
private StarStatsUserConverter testUnit;
@Mock
private MemberService memberService;
@Mock
private UserInServerManagementService userInServerManagementService;
@Mock
private StarStatsUserConverter self;
@Test
public void testConversionOfMultipleItems() {
Long serverId = 5L;
Long firstUserId = 5L;
Long secondUserId = 9L;
List<StarStatsGuildUserResult> results = new ArrayList<>();
StarStatsGuildUserResult firstResult = Mockito.mock(StarStatsGuildUserResult.class);
Member firstMember = Mockito.mock(Member.class);
AUserInAServer firstUser = Mockito.mock(AUserInAServer.class);
AUser firstAUser = Mockito.mock(AUser.class);
when(firstAUser.getId()).thenReturn(firstUserId);
when(firstUser.getUserReference()).thenReturn(firstAUser);
when(userInServerManagementService.loadOrCreateUser(firstUserId)).thenReturn(firstUser);
when(memberService.getMemberInServerAsync(serverId, firstUserId)).thenReturn(CompletableFuture.completedFuture(firstMember));
when(firstResult.getUserId()).thenReturn(firstUserId);
results.add(firstResult);
StarStatsGuildUserResult secondResult = Mockito.mock(StarStatsGuildUserResult.class);
Member secondMember = Mockito.mock(Member.class);
AUserInAServer secondUser = Mockito.mock(AUserInAServer.class);
AUser secondAUser = Mockito.mock(AUser.class);
when(secondAUser.getId()).thenReturn(secondUserId);
when(secondUser.getUserReference()).thenReturn(secondAUser);
when(userInServerManagementService.loadOrCreateUser(secondUserId)).thenReturn(secondUser);
when(memberService.getMemberInServerAsync(serverId, secondUserId)).thenReturn(CompletableFuture.completedFuture(secondMember));
when(secondResult.getUserId()).thenReturn(secondUserId);
results.add(secondResult);
testUnit.convertToStarStatsUser(results, serverId);
ArgumentCaptor<StarStatsGuildUserResult> resultArgumentCaptor = ArgumentCaptor.forClass(StarStatsGuildUserResult.class);
ArgumentCaptor<Member> memberArgumentCaptor = ArgumentCaptor.forClass(Member.class);
verify(self, times(2)).loadStarStatsUser(resultArgumentCaptor.capture(), memberArgumentCaptor.capture());
List<StarStatsGuildUserResult> resultCaptorValues = resultArgumentCaptor.getAllValues();
Assert.assertEquals(2, resultCaptorValues.size());
Assert.assertEquals(firstResult, resultCaptorValues.get(0));
Assert.assertEquals(secondResult, resultCaptorValues.get(1));
List<Member> memberCaptorValues = memberArgumentCaptor.getAllValues();
Assert.assertEquals(2, memberCaptorValues.size());
Assert.assertEquals(firstMember, memberCaptorValues.get(0));
Assert.assertEquals(secondMember, memberCaptorValues.get(1));
}
@Test
public void testConversionOfEmptyList() {
Long serverId = 5L;
List<StarStatsGuildUserResult> results = new ArrayList<>();
List<CompletableFuture<StarStatsUser>> starStatsUsers = testUnit.convertToStarStatsUser(results, serverId);
verify(memberService, times(0)).getMemberInServer(eq(serverId), anyLong());
Assert.assertEquals(0, starStatsUsers.size());
}
}

View File

@@ -0,0 +1,297 @@
package dev.sheldan.abstracto.starboard.listener;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.cache.CachedAuthor;
import dev.sheldan.abstracto.core.models.cache.CachedEmote;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.cache.CachedReactions;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.starboard.config.StarboardFeature;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.service.StarboardService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarboardListenerTest {
@InjectMocks
private StarboardListener testUnit;
@Mock
private ConfigManagementService configManagementService;
@Mock
private StarboardService starboardService;
@Mock
private StarboardPostManagementService starboardPostManagementService;
@Mock
private StarboardPostReactorManagementService starboardPostReactorManagementService;
@Mock
private UserInServerManagementService userInServerManagementService;
@Mock
private EmoteService emoteService;
@Mock
private MetricService metricService;
@Mock
private CachedReactions cachedReaction;
@Mock
private CachedMessage cachedMessage;
@Mock
private ServerUser serverUserActing;
@Mock
private CachedAuthor cachedAuthor;
@Mock
private AUserInAServer userInServerActing;
@Mock
private AUser userActing;
@Mock
private AUserInAServer userInAServer;
@Mock
private AUser aUser;
@Mock
private AServer server;
@Mock
private StarboardPost post;
@Mock
private CachedEmote cachedEmote;
@Mock
private AEmote starEmote;
private static final Long MESSAGE_ID = 5L;
private static final Long SERVER_ID = 6L;
private static final Long AUTHOR_ID = 4L;
private static final Long USER_ACTING_ID = 7L;
@Test
public void testAuthorAddingStar() {
when(cachedAuthor.getAuthorId()).thenReturn(AUTHOR_ID);
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
when(serverUserActing.getUserId()).thenReturn(AUTHOR_ID);
testUnit.executeReactionAdded(cachedMessage, cachedReaction, serverUserActing);
verify(emoteService, times(0)).getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID);
}
@Test
public void testAddingWrongEmote() {
when(serverUserActing.getUserId()).thenReturn(USER_ACTING_ID);
setupWrongEmote(SERVER_ID, AUTHOR_ID, starEmote);
when(cachedReaction.getEmote()).thenReturn(cachedEmote);
when(emoteService.compareCachedEmoteWithAEmote(cachedEmote, starEmote)).thenReturn(false);
testUnit.executeReactionAdded(cachedMessage, cachedReaction, serverUserActing);
verify(emoteService, times(1)).getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID);
verify(emoteService, times(0)).getReactionFromMessageByEmote(any(CachedMessage.class), eq(starEmote));
}
@Test
public void testAddingEmoteToExistingPostButNowBelowThreshold() {
Long requiredStars = 5L;
setupActingAndAuthor();
executeAddingTest(requiredStars, post);
verify(starboardService, times(1)).deleteStarboardMessagePost(post);
verify(starboardPostManagementService, times(1)).removePost(post);
}
@Test
public void testAddingEmoteBelowThreshold() {
Long requiredStars = 5L;
setupActingAndAuthor();
executeAddingTest(requiredStars, null);
verify(starboardService, times(0)).deleteStarboardMessagePost(any(StarboardPost.class));
verify(starboardPostManagementService, times(0)).removePost(any(StarboardPost.class));
}
@Test
public void testAddingEmoteReachingThreshold() {
Long requiredStars = 1L;
setupActingAndAuthor();
executeAddingTest(requiredStars, null);
verify(metricService, times(2)).incrementCounter(any());
verify(starboardService, times(1)).createStarboardPost(any(CachedMessage.class), anyList(), eq(userInServerActing), eq(userInAServer));
}
@Test
public void testAddingEmoteToExistingPost() {
Long requiredStars = 1L;
setupActingAndAuthor();
executeAddingTest(requiredStars, post);
verify(metricService, times(1)).incrementCounter(any());
verify(starboardService, times(1)).updateStarboardPost(eq(post), any(CachedMessage.class), anyList());
verify(starboardPostReactorManagementService, times(1)).addReactor(post, userInServerActing);
}
@Test
public void testAuthorRemovingReaction() {
when(cachedAuthor.getAuthorId()).thenReturn(AUTHOR_ID);
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
when(serverUserActing.getUserId()).thenReturn(AUTHOR_ID);
testUnit.executeReactionRemoved(cachedMessage, cachedReaction, serverUserActing);
verify(emoteService, times(0)).getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID);
}
@Test
public void testRemovingWrongEmote() {
when(serverUserActing.getUserId()).thenReturn(USER_ACTING_ID);
setupWrongEmote(SERVER_ID, AUTHOR_ID, starEmote);
testUnit.executeReactionRemoved(cachedMessage, cachedReaction, serverUserActing);
verify(emoteService, times(1)).getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID);
verify(emoteService, times(0)).getReactionFromMessageByEmote(any(CachedMessage.class), eq(starEmote));
}
@Test
public void testRemoveReactionFromExistingPostBelowThreshold() {
Long requiredStars = 5L;
List<ServerUser> remainingUsers = Arrays.asList(serverUserActing);
setupActingAndAuthor();
executeRemovalTest(requiredStars, remainingUsers);
verify(starboardService, times(1)).deleteStarboardMessagePost(eq(post));
verify(starboardPostManagementService, times(1)).removePost(eq(post));
}
@Test
public void testRemoveReactionFromExistingPostAboveThreshold() {
Long requiredStars = 0L;
List<ServerUser> remainingUsers = Arrays.asList(serverUserActing);
setupActingAndAuthor();
when(userInServerManagementService.loadOrCreateUser(serverUserActing)).thenReturn(userInServerActing);
executeRemovalTest(requiredStars, remainingUsers);
verify(metricService, times(1)).incrementCounter(any());
verify(starboardService, times(0)).deleteStarboardMessagePost(eq(post));
verify(starboardPostManagementService, times(0)).removePost(eq(post));
}
@Test
public void testRemoveReactionFromExistingPostTriggeringThreshold() {
Long requiredStars = 1L;
ArrayList<ServerUser> usersRemaining = new ArrayList<>();
setupActingAndAuthor();
executeRemovalTest(requiredStars, usersRemaining);
verify(metricService, times(2)).incrementCounter(any());
verify(starboardService, times(1)).deleteStarboardMessagePost(eq(post));
verify(starboardPostManagementService, times(1)).removePost(eq(post));
}
@Test
public void testReactionsClearedOnStarredMessage() {
executeClearingTest(Mockito.mock(StarboardPost.class));
}
@Test
public void testReactionsClearedOnNotStarredMessage() {
executeClearingTest(null);
}
private void setupActingAndAuthor() {
when(userInServerActing.getUserReference()).thenReturn(userActing);
when(userActing.getId()).thenReturn(USER_ACTING_ID);
when(userInAServer.getServerReference()).thenReturn(server);
when(server.getId()).thenReturn(SERVER_ID);
when(userInAServer.getUserReference()).thenReturn(aUser);
when(aUser.getId()).thenReturn(AUTHOR_ID);
}
private void executeClearingTest(StarboardPost post) {
when(cachedMessage.getMessageId()).thenReturn(MESSAGE_ID);
when(starboardPostManagementService.findByMessageId(MESSAGE_ID)).thenReturn(Optional.ofNullable(post));
testUnit.executeReactionCleared(cachedMessage);
int callCount = post != null ? 1 : 0;
verify(starboardPostReactorManagementService, times(callCount)).removeReactors(post);
verify(starboardService, times(callCount)).deleteStarboardMessagePost(eq(post));
verify(starboardPostManagementService, times(callCount)).removePost(eq(post));
}
private void executeRemovalTest(Long requiredStars, List<ServerUser> remainingUsers) {
when(cachedMessage.getServerId()).thenReturn(SERVER_ID);
when(cachedMessage.getMessageId()).thenReturn(MESSAGE_ID);
when(cachedAuthor.getAuthorId()).thenReturn(AUTHOR_ID);
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
when(cachedReaction.getEmote()).thenReturn(cachedEmote);
when(emoteService.compareCachedEmoteWithAEmote(cachedEmote, starEmote)).thenReturn(true);
when(emoteService.getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID)).thenReturn(starEmote);
CachedReactions reaction = Mockito.mock(CachedReactions.class);
when(reaction.getUsers()).thenReturn(remainingUsers);
when(emoteService.getReactionFromMessageByEmote(cachedMessage, starEmote)).thenReturn(Optional.of(reaction));
when(starboardPostManagementService.findByMessageId(MESSAGE_ID)).thenReturn(Optional.ofNullable(post));
when(userInServerManagementService.loadOrCreateUser(SERVER_ID, AUTHOR_ID)).thenReturn(userInAServer);
when(serverUserActing.getUserId()).thenReturn(USER_ACTING_ID);
when(serverUserActing.getServerId()).thenReturn(SERVER_ID);
if(!remainingUsers.isEmpty()) {
when(userInServerManagementService.loadUserOptional(SERVER_ID, USER_ACTING_ID)).thenReturn(Optional.of(userInServerActing));
}
AConfig starRequirementConfig = Mockito.mock(AConfig.class);
when(starRequirementConfig.getLongValue()).thenReturn(requiredStars);
when(configManagementService.loadConfig(SERVER_ID, StarboardListener.FIRST_LEVEL_THRESHOLD_KEY)).thenReturn(starRequirementConfig);
testUnit.executeReactionRemoved(cachedMessage, cachedReaction, serverUserActing);
verify(emoteService, times(1)).getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID);
verify(emoteService, times(1)).getReactionFromMessageByEmote(cachedMessage, starEmote);
}
private void executeAddingTest(Long requiredStars, StarboardPost postToUse) {
when(cachedMessage.getServerId()).thenReturn(SERVER_ID);
when(cachedMessage.getMessageId()).thenReturn(MESSAGE_ID);
when(cachedAuthor.getAuthorId()).thenReturn(AUTHOR_ID);
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
when(cachedReaction.getEmote()).thenReturn(cachedEmote);
when(emoteService.compareCachedEmoteWithAEmote(cachedEmote, starEmote)).thenReturn(true);
when(emoteService.getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID)).thenReturn(starEmote);
CachedReactions reaction = Mockito.mock(CachedReactions.class);
when(serverUserActing.getUserId()).thenReturn(USER_ACTING_ID);
when(serverUserActing.getServerId()).thenReturn(SERVER_ID);
when(reaction.getUsers()).thenReturn(Arrays.asList(serverUserActing));
when(emoteService.getReactionFromMessageByEmote(cachedMessage, starEmote)).thenReturn(Optional.of(reaction));
when(starboardPostManagementService.findByMessageId(MESSAGE_ID)).thenReturn(Optional.ofNullable(postToUse));
when(userInServerManagementService.loadOrCreateUser(SERVER_ID, AUTHOR_ID)).thenReturn(userInAServer);
when(userInServerManagementService.loadOrCreateUser(serverUserActing)).thenReturn(userInServerActing);
when(userInServerManagementService.loadUserOptional(SERVER_ID, USER_ACTING_ID)).thenReturn(Optional.of(userInServerActing));
AConfig starRequirementConfig = Mockito.mock(AConfig.class);
when(starRequirementConfig.getLongValue()).thenReturn(requiredStars);
when(configManagementService.loadConfig(SERVER_ID, StarboardListener.FIRST_LEVEL_THRESHOLD_KEY)).thenReturn(starRequirementConfig);
testUnit.executeReactionAdded(cachedMessage, cachedReaction, serverUserActing);
verify(emoteService, times(1)).getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, SERVER_ID);
verify(emoteService, times(1)).getReactionFromMessageByEmote(cachedMessage, starEmote);
}
private void setupWrongEmote(Long serverId, Long authorId, AEmote starEmote) {
when(cachedMessage.getServerId()).thenReturn(serverId);
when(cachedAuthor.getAuthorId()).thenReturn(authorId);
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
when(cachedReaction.getEmote()).thenReturn(cachedEmote);
when(emoteService.getEmoteOrDefaultEmote(StarboardFeature.STAR_EMOTE, serverId)).thenReturn(starEmote);
}
}

View File

@@ -0,0 +1,54 @@
package dev.sheldan.abstracto.starboard.listener;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarboardPostDeletedListenerTest {
@InjectMocks
private StarboardPostDeletedListener testUnit;
@Mock
private StarboardPostManagementService starboardPostManagementService;
@Test
public void deleteNonStarboardPost() {
Long messageId = 4L;
when(starboardPostManagementService.findByStarboardPostId(messageId)).thenReturn(Optional.empty());
CachedMessage cachedMessage = Mockito.mock(CachedMessage.class);
when(cachedMessage.getMessageId()).thenReturn(messageId);
testUnit.execute(cachedMessage);
verify( starboardPostManagementService, times(0)).setStarboardPostIgnored(messageId, true);
}
@Test
public void deleteStarboardPost() {
Long messageId = 4L;
Long postMessageId = 5L;
Long serverId = 3L;
AChannel sourceChannel = Mockito.mock(AChannel.class);
StarboardPost post = Mockito.mock(StarboardPost.class);
when(post.getSourceChannel()) .thenReturn(sourceChannel);
when(post.getPostMessageId()).thenReturn(postMessageId);
when(starboardPostManagementService.findByStarboardPostId(messageId)).thenReturn(Optional.of(post));
CachedMessage cachedMessage = Mockito.mock(CachedMessage.class);
when(cachedMessage.getServerId()).thenReturn(serverId);
when(cachedMessage.getMessageId()).thenReturn(messageId);
testUnit.execute(cachedMessage);
verify( starboardPostManagementService, times(1)).setStarboardPostIgnored(messageId, true);
}
}

View File

@@ -0,0 +1,342 @@
package dev.sheldan.abstracto.starboard.service;
import dev.sheldan.abstracto.core.exception.UserInServerNotFoundException;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.property.SystemConfigProperty;
import dev.sheldan.abstracto.core.models.cache.CachedAuthor;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.starboard.config.StarboardFeature;
import dev.sheldan.abstracto.starboard.config.StarboardPostTarget;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.model.template.*;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.starboard.service.management.StarboardPostReactorManagementService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarboardServiceBeanTest {
@InjectMocks
private StarboardServiceBean testUnit;
@Mock
private GuildService guildService;
@Mock
private ChannelService channelService;
@Mock
private MemberService memberService;
@Mock
private PostTargetService postTargetService;
@Mock
private TemplateService templateService;
@Mock
private ConfigService configService;
@Mock
private StarboardPostManagementService starboardPostManagementService;
@Mock
private StarboardPostReactorManagementService starboardPostReactorManagementService;
@Mock
private DefaultConfigManagementService defaultConfigManagementService;
@Mock
private PostTargetManagement postTargetManagement;
@Mock
private ChannelManagementService channelManagementService;
@Mock
private UserInServerManagementService userInServerManagementService;
@Mock
private MessageService messageService;
@Mock
private EmoteService emoteService;
@Mock
private StarboardServiceBean self;
@Mock
private Guild guild;
@Mock
private Message sendPost;
@Mock
private TextChannel mockedTextChannel;
@Mock
private Member starredMember;
@Mock
private AChannel starboardChannel;
@Mock
private MessageToSend messageToSend;
@Mock
private AServer server;
private static final Long STARRED_USER_ID = 5L;
private static final Long STARRED_SERVER_USER_ID = 2L;
private static final Long SERVER_ID = 6L;
private static final Long STARBOARD_CHANNEL_ID = 8L;
private static final Long FIRST_USER_IN_SERVER_ID = 3L;
private static final Long SECOND_USER_IN_SERVER_ID = 9L;
private static final Long CHANNEL_ID = 10L;
private static final Long MESSAGE_ID = 11L;
private static final Long SECOND_MESSAGE_ID = 12L;
@Captor
private ArgumentCaptor<AUserInAServer> userInAServerArgumentCaptor;
@Captor
private ArgumentCaptor<StarboardPostModel> starboardPostModelArgumentCaptor;
@Test
public void testCreateStarboardPost() {
List<AUserInAServer> userExceptAuthor = new ArrayList<>();
AUserInAServer firstUserExceptAuthor = Mockito.mock(AUserInAServer.class);
userExceptAuthor.add(firstUserExceptAuthor);
AUserInAServer secondUserExceptAuthor = Mockito.mock(AUserInAServer.class);
userExceptAuthor.add(secondUserExceptAuthor);
AUserInAServer userReacting = Mockito.mock(AUserInAServer.class);
AUserInAServer starredUser = Mockito.mock(AUserInAServer.class);
when(starredUser.getUserInServerId()).thenReturn(STARRED_SERVER_USER_ID);
CachedAuthor cachedAuthor = Mockito.mock(CachedAuthor.class);
when(cachedAuthor.getAuthorId()).thenReturn(STARRED_USER_ID);
CachedMessage message = Mockito.mock(CachedMessage.class);
when(message.getAuthor()).thenReturn(cachedAuthor);
when(message.getServerId()).thenReturn(SERVER_ID);
when(message.getChannelId()).thenReturn(CHANNEL_ID);
Member authorMember = Mockito.mock(Member.class);
when(memberService.getMemberInServerAsync(SERVER_ID, STARRED_USER_ID)).thenReturn(CompletableFuture.completedFuture(authorMember));
when(channelService.getTextChannelFromServerOptional(SERVER_ID, CHANNEL_ID)).thenReturn(Optional.of(mockedTextChannel));
when(guildService.getGuildByIdOptional(SERVER_ID)).thenReturn(Optional.of(guild));
SystemConfigProperty config = Mockito.mock(SystemConfigProperty.class);
Long defaultValue = 3L;
when(config.getLongValue()).thenReturn(defaultValue);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeature.STAR_LEVELS_CONFIG_KEY)).thenReturn(config);
when(configService.getLongValue(StarboardFeature.STAR_LVL_CONFIG_PREFIX + "2", SERVER_ID, defaultValue)).thenReturn(2L);
when(configService.getLongValue(StarboardFeature.STAR_LVL_CONFIG_PREFIX + "3", SERVER_ID, defaultValue)).thenReturn(3L);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeature.STAR_LVL_CONFIG_PREFIX + "2")).thenReturn(config);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeature.STAR_LVL_CONFIG_PREFIX + "3")).thenReturn(config);
when(emoteService.getUsableEmoteOrDefault(SERVER_ID, StarboardFeature.STAR_EMOTE_PREFIX + "2")).thenReturn("b");
when(self.sendStarboardPostAndStore(eq(message), eq(STARRED_SERVER_USER_ID), anyList(), any())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<Void> createPostFuture = testUnit.createStarboardPost(message, userExceptAuthor, userReacting, starredUser);
createPostFuture.join();
Assert.assertFalse(createPostFuture.isCompletedExceptionally());
}
@Test
public void testSendStarboard() {
CachedMessage message = Mockito.mock(CachedMessage.class);
when(message.getServerId()).thenReturn(SERVER_ID);
StarboardPostModel model = Mockito.mock(StarboardPostModel.class);
when(templateService.renderEmbedTemplate(StarboardServiceBean.STARBOARD_POST_TEMPLATE, model)).thenReturn(messageToSend);
PostTarget postTarget = Mockito.mock(PostTarget.class);
when(postTarget.getChannelReference()).thenReturn(starboardChannel);
when(starboardChannel.getId()).thenReturn(STARBOARD_CHANNEL_ID);
when(postTargetManagement.getPostTarget(StarboardPostTarget.STARBOARD.getKey(), SERVER_ID)).thenReturn(postTarget);
when(postTargetService.sendEmbedInPostTarget(messageToSend, StarboardPostTarget.STARBOARD, SERVER_ID)).thenReturn(Arrays.asList(CompletableFuture.completedFuture(null)));
ArrayList<Long> userExceptAuthorIds = new ArrayList<>();
testUnit.sendStarboardPostAndStore(message, STARRED_USER_ID, userExceptAuthorIds, model);
verify(self, times(1)).persistPost(eq(message), eq(userExceptAuthorIds), any(), eq(STARBOARD_CHANNEL_ID), eq(STARRED_USER_ID));
}
@Test
public void testPersistPostWithTwoReactors() {
AUserInAServer userReacting = Mockito.mock(AUserInAServer.class);
AUserInAServer starredUser = Mockito.mock(AUserInAServer.class);
CachedMessage message = Mockito.mock(CachedMessage.class);
List<Long> userExceptAuthorIds = Arrays.asList(FIRST_USER_IN_SERVER_ID, SECOND_USER_IN_SERVER_ID);
List<CompletableFuture<Message>> futures = Arrays.asList(CompletableFuture.completedFuture(sendPost));
when(userInServerManagementService.loadUserOptional(STARRED_SERVER_USER_ID)).thenReturn(Optional.of(starredUser));
when(userInServerManagementService.loadUserOptional(FIRST_USER_IN_SERVER_ID)).thenReturn(Optional.of(userReacting));
when(userReacting.getUserInServerId()).thenReturn(FIRST_USER_IN_SERVER_ID);
AChannel channel = Mockito.mock(AChannel.class);
when(channelManagementService.loadChannel(CHANNEL_ID)).thenReturn(channel);
StarboardPost post = Mockito.mock(StarboardPost.class);
when(starboardPostManagementService.createStarboardPost(eq(message), eq(starredUser), any(AServerAChannelMessage.class))).thenReturn(post);
AUserInAServer secondStarrerUserObj = Mockito.mock(AUserInAServer.class);
when(userInServerManagementService.loadUserOptional(SECOND_USER_IN_SERVER_ID)).thenReturn(Optional.of(secondStarrerUserObj));
when(secondStarrerUserObj.getUserInServerId()).thenReturn(SECOND_USER_IN_SERVER_ID);
testUnit.persistPost(message, userExceptAuthorIds, futures, CHANNEL_ID, STARRED_SERVER_USER_ID);
verify(starboardPostReactorManagementService, times(2)).addReactor(eq(post), userInAServerArgumentCaptor.capture());
List<AUserInAServer> addedReactors = userInAServerArgumentCaptor.getAllValues();
Assert.assertEquals(FIRST_USER_IN_SERVER_ID, addedReactors.get(0).getUserInServerId());
Assert.assertEquals(SECOND_USER_IN_SERVER_ID, addedReactors.get(1).getUserInServerId());
Assert.assertEquals(2, addedReactors.size());
}
@Test
public void testUpdateStarboardPost() {
Long newPostId = 37L;
Long oldPostId = 36L;
AChannel sourceChannel = Mockito.mock(AChannel.class);
when(sourceChannel.getServer()).thenReturn(server);
CachedMessage message = Mockito.mock(CachedMessage.class);
when(message.getChannelId()).thenReturn(CHANNEL_ID);
when(message.getServerId()).thenReturn(SERVER_ID);
CachedAuthor author = Mockito.mock(CachedAuthor.class);
when(author.getAuthorId()).thenReturn(STARRED_USER_ID);
when(message.getAuthor()).thenReturn(author);
Long starboardPostId = 47L;
StarboardPost post = Mockito.mock(StarboardPost.class);
when(post.getStarboardMessageId()).thenReturn(oldPostId);
when(post.getSourceChannel()).thenReturn(sourceChannel);
when(post.getId()).thenReturn(starboardPostId);
MessageToSend postMessage = Mockito.mock(MessageToSend.class);
when(templateService.renderEmbedTemplate(eq(StarboardServiceBean.STARBOARD_POST_TEMPLATE), starboardPostModelArgumentCaptor.capture())).thenReturn(postMessage);
when(postTargetService.editOrCreatedInPostTarget(oldPostId, postMessage, StarboardPostTarget.STARBOARD, SERVER_ID)).thenReturn(Arrays.asList(CompletableFuture.completedFuture(sendPost)));
when(sendPost.getIdLong()).thenReturn(newPostId);
SystemConfigProperty config = Mockito.mock(SystemConfigProperty.class);
when(config.getLongValue()).thenReturn(1L);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeature.STAR_LEVELS_CONFIG_KEY)).thenReturn(config);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeature.STAR_LVL_CONFIG_PREFIX + 1)).thenReturn(config);
when(starboardPostManagementService.findByStarboardPostId(starboardPostId)).thenReturn(Optional.of(post));
when(memberService.getMemberInServerAsync(SERVER_ID, STARRED_USER_ID)).thenReturn(CompletableFuture.completedFuture(starredMember));
List<AUserInAServer > userExceptAuthor = new ArrayList<>();
CompletableFuture<Void> future = testUnit.updateStarboardPost(post, message, userExceptAuthor);
future.join();
Assert.assertFalse(future.isCompletedExceptionally());
verify(postTargetService, times(1)).editOrCreatedInPostTarget(oldPostId, postMessage, StarboardPostTarget.STARBOARD, SERVER_ID);
verify(starboardPostManagementService, times(1)).setStarboardPostMessageId(post, newPostId);
}
@Test
public void testDeleteStarboardMessagePost() {
StarboardPost post = Mockito.mock(StarboardPost.class);
when(post.getStarboardMessageId()).thenReturn(MESSAGE_ID);
AChannel channel = Mockito.mock(AChannel.class);
when(channel.getServer()).thenReturn(server);
when(server.getId()).thenReturn(SERVER_ID);
when(channel.getId()).thenReturn(CHANNEL_ID);
when(post.getSourceChannel()).thenReturn(channel);
when(post.getStarboardChannel()).thenReturn(channel);
testUnit.deleteStarboardMessagePost(post);
verify(messageService, times(1)).deleteMessageInChannelInServer(SERVER_ID, CHANNEL_ID, MESSAGE_ID);
}
@Test(expected = UserInServerNotFoundException.class)
public void testPersistingOfNotFoundStarredUser() {
when(userInServerManagementService.loadUserOptional(SECOND_USER_IN_SERVER_ID)).thenReturn(Optional.empty());
CachedMessage message = Mockito.mock(CachedMessage.class);
List<Long> userExceptAuthorIds = Arrays.asList(FIRST_USER_IN_SERVER_ID);
List<CompletableFuture<Message>> futures = Arrays.asList(CompletableFuture.completedFuture(sendPost));
testUnit.persistPost(message, userExceptAuthorIds, futures, CHANNEL_ID, SECOND_USER_IN_SERVER_ID);
}
@Test
public void testRetrieveStarStats() {
Integer limit = 3;
AChannel channel = Mockito.mock(AChannel.class);
when(server.getId()).thenReturn(SERVER_ID);
StarboardPostReaction reaction = Mockito.mock(StarboardPostReaction.class);
StarboardPost post1 = Mockito.mock(StarboardPost.class);
when(post1.getReactions()).thenReturn(Arrays.asList(reaction));
when(post1.getPostMessageId()).thenReturn(MESSAGE_ID);
when(post1.getServer()).thenReturn(server);
when(post1.getStarboardChannel()).thenReturn(channel);
StarboardPost post2 = Mockito.mock(StarboardPost.class);
when(post2.getPostMessageId()).thenReturn(SECOND_MESSAGE_ID);
when(post2.getReactions()).thenReturn(new ArrayList<>());
when(post2.getServer()).thenReturn(server);
when(post2.getStarboardChannel()).thenReturn(channel);
List<StarboardPost> topPosts = Arrays.asList(post1, post2);
when(starboardPostManagementService.retrieveTopPosts(SERVER_ID, limit)).thenReturn(topPosts);
CompletableFuture<StarStatsUser> statsUser = CompletableFuture.completedFuture(Mockito.mock(StarStatsUser.class));
CompletableFuture<StarStatsUser> statsUser2 = CompletableFuture.completedFuture(Mockito.mock(StarStatsUser.class));
List<CompletableFuture<StarStatsUser>> topGiver = Arrays.asList(statsUser, statsUser2);
when(starboardPostReactorManagementService.retrieveTopStarGiver(SERVER_ID, limit)).thenReturn(topGiver);
when(starboardPostReactorManagementService.retrieveTopStarReceiver(SERVER_ID, limit)).thenReturn(topGiver);
when(starboardPostManagementService.getPostCount(SERVER_ID)).thenReturn(50);
when(starboardPostReactorManagementService.getStarCount(SERVER_ID)).thenReturn(500);
when(emoteService.getUsableEmoteOrDefault(SERVER_ID, "starboardBadge1")).thenReturn("1");
when(emoteService.getUsableEmoteOrDefault(SERVER_ID, "starboardBadge2")).thenReturn("2");
when(emoteService.getUsableEmoteOrDefault(SERVER_ID, "starboardBadge3")).thenReturn("3");
CompletableFuture<GuildStarStatsModel> modelFuture = testUnit.retrieveStarStats(SERVER_ID);
GuildStarStatsModel model = modelFuture.join();
List<String> badgeEmotes = model.getBadgeEmotes();
Assert.assertEquals(limit.intValue(), badgeEmotes.size());
Assert.assertEquals("1", badgeEmotes.get(0));
Assert.assertEquals("2", badgeEmotes.get(1));
Assert.assertEquals("3", badgeEmotes.get(2));
Assert.assertEquals(500, model.getTotalStars().intValue());
Assert.assertEquals(50, model.getStarredMessages().intValue());
StarStatsPost topPost = model.getTopPosts().get(0);
Assert.assertEquals(SERVER_ID, topPost.getServerId());
Assert.assertEquals(channel.getId(), topPost.getChannelId());
Assert.assertEquals(MESSAGE_ID, topPost.getMessageId());
Assert.assertEquals(1, topPost.getStarCount().intValue());
StarStatsPost secondTopPost = model.getTopPosts().get(1);
Assert.assertEquals(SERVER_ID, secondTopPost.getServerId());
Assert.assertEquals(channel.getId(), secondTopPost.getChannelId());
Assert.assertEquals(SECOND_MESSAGE_ID, secondTopPost.getMessageId());
Assert.assertEquals(0, secondTopPost.getStarCount().intValue());
}
@Test
public void testRetrieveStarStatsForMember() {
when(starredMember.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(starredMember.getIdLong()).thenReturn(STARRED_USER_ID);
Long receivedStars = 3L;
Long givenStars = 3L;
when(starboardPostManagementService.retrieveReceivedStarsOfUserInServer(SERVER_ID, STARRED_USER_ID)).thenReturn(receivedStars);
when(starboardPostManagementService.retrieveGivenStarsOfUserInServer(SERVER_ID, STARRED_USER_ID)).thenReturn(givenStars);
StarboardPost post = Mockito.mock(StarboardPost.class);
AChannel starboardChannel = Mockito.mock(AChannel.class);
when(post.getStarboardChannel()).thenReturn(starboardChannel);
AServer server = Mockito.mock(AServer.class);
when(server.getId()).thenReturn(SERVER_ID);
when(post.getServer()).thenReturn(server);
StarboardPostReaction reaction = Mockito.mock(StarboardPostReaction.class);
when(post.getReactions()).thenReturn(Collections.singletonList(reaction));
when(starboardChannel.getId()).thenReturn(STARBOARD_CHANNEL_ID);
when(post.getPostMessageId()).thenReturn(MESSAGE_ID);
when(starboardPostManagementService.retrieveTopPostsForUserInServer(SERVER_ID, STARRED_USER_ID, 3)).thenReturn(Collections.singletonList(post));
MemberStarStatsModel returnedModel = testUnit.retrieveStarStatsForMember(starredMember);
Assert.assertEquals(receivedStars, returnedModel.getReceivedStars());
Assert.assertEquals(givenStars, returnedModel.getGivenStars());
Assert.assertEquals(starredMember, returnedModel.getMember());
Assert.assertEquals(3, returnedModel.getBadgeEmotes().size());
Assert.assertEquals(1, returnedModel.getTopPosts().size());
StarStatsPost starStatsPost = returnedModel.getTopPosts().get(0);
Assert.assertEquals(STARBOARD_CHANNEL_ID, starStatsPost.getChannelId());
Assert.assertEquals(SERVER_ID, starStatsPost.getServerId());
Assert.assertEquals(1, starStatsPost.getStarCount().longValue());
Assert.assertEquals(MESSAGE_ID, starStatsPost.getMessageId());
}
}

View File

@@ -0,0 +1,204 @@
package dev.sheldan.abstracto.starboard.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.repository.StarboardPostRepository;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarboardPostManagementServiceBeanTest {
@InjectMocks
private StarboardPostManagementServiceBean testUnit;
@Mock
private StarboardPostRepository repository;
@Mock
private ChannelManagementService channelManagementService;
@Mock
private AServer server;
@Mock
private AUserInAServer aUserInAServer;
@Mock
private AChannel sourceChannel;
@Mock
private AChannel starboardChannel;
private static final Long SOURCE_CHANNEL_ID = 5L;
private static final Long SERVER_ID = 7L;
@Test
public void testCreateStarboardPost() {
Long starboardPostId = 5L;
Long starredMessageId = 8L;
CachedMessage starredMessage = Mockito.mock(CachedMessage.class);
when(starredMessage.getServerId()).thenReturn(SERVER_ID);
when(starredMessage.getChannelId()).thenReturn(SOURCE_CHANNEL_ID);
when(starredMessage.getMessageId()).thenReturn(starredMessageId);
AServerAChannelMessage postInStarboard = Mockito.mock(AServerAChannelMessage.class);
when(postInStarboard.getServer()).thenReturn(server);
when(postInStarboard.getChannel()).thenReturn(starboardChannel);
when(postInStarboard.getMessageId()).thenReturn(starboardPostId);
when(channelManagementService.loadChannel(SOURCE_CHANNEL_ID)).thenReturn(sourceChannel);
AUser aUser = Mockito.mock(AUser.class);
when(aUserInAServer.getUserReference()).thenReturn(aUser);
StarboardPost createdStarboardPost = testUnit.createStarboardPost(starredMessage, aUserInAServer, postInStarboard);
verify(repository, times(1)).save(createdStarboardPost);
Assert.assertEquals(starboardChannel, createdStarboardPost.getStarboardChannel());
Assert.assertEquals(starboardPostId, createdStarboardPost.getStarboardMessageId());
Assert.assertEquals(starredMessageId, createdStarboardPost.getPostMessageId());
Assert.assertEquals(aUserInAServer, createdStarboardPost.getAuthor());
Assert.assertEquals(sourceChannel, createdStarboardPost.getSourceChannel());
Assert.assertFalse(createdStarboardPost.isIgnored());
}
@Test
public void setStarboardMessageId(){
StarboardPost post = Mockito.mock(StarboardPost.class);
Long messageId = 6L;
testUnit.setStarboardPostMessageId(post, messageId);
verify(post, times(1)).setStarboardMessageId(messageId);
verify(repository, times(1)).save(post);
}
@Test
public void testRetrieveTopPosts() {
Integer count = 2;
StarboardPost starboardPost1 = Mockito.mock(StarboardPost.class);
when(starboardPost1.getReactions()).thenReturn(Arrays.asList(Mockito.mock(StarboardPostReaction.class), Mockito.mock(StarboardPostReaction.class)));
StarboardPost starboardPost2 = Mockito.mock(StarboardPost.class);
when(starboardPost2.getReactions()).thenReturn(Arrays.asList(Mockito.mock(StarboardPostReaction.class)));
StarboardPost starboardPost3 = Mockito.mock(StarboardPost.class);
when(starboardPost3.getReactions()).thenReturn(new ArrayList<>());
List<StarboardPost> posts = Arrays.asList(starboardPost1, starboardPost2, starboardPost3);
when(repository.findByServer_Id(SERVER_ID)).thenReturn(posts);
List<StarboardPost> topPosts = testUnit.retrieveTopPosts(SERVER_ID, count);
Assert.assertEquals(count.intValue(), topPosts.size());
StarboardPost topMostPost = topPosts.get(0);
StarboardPost secondTop = topPosts.get(1);
Assert.assertEquals(starboardPost1, topPosts.get(0));
Assert.assertEquals(starboardPost2, secondTop);
Assert.assertTrue(topMostPost.getReactions().size() > secondTop.getReactions().size());
}
@Test
public void testRetrieveMoreThanAvailable() {
Integer count = 5;
StarboardPost starboardPost1 = Mockito.mock(StarboardPost.class);
when(starboardPost1.getReactions()).thenReturn(Arrays.asList(Mockito.mock(StarboardPostReaction.class), Mockito.mock(StarboardPostReaction.class)));
StarboardPost starboardPost2 = Mockito.mock(StarboardPost.class);
when(starboardPost2.getReactions()).thenReturn(Arrays.asList(Mockito.mock(StarboardPostReaction.class)));
StarboardPost starboardPost3 = Mockito.mock(StarboardPost.class);
when(starboardPost3.getReactions()).thenReturn(new ArrayList<>());
List<StarboardPost> posts = Arrays.asList(starboardPost1, starboardPost2, starboardPost3);
when(repository.findByServer_Id(SERVER_ID)).thenReturn(posts);
List<StarboardPost> topPosts = testUnit.retrieveTopPosts(SERVER_ID, count);
StarboardPost topMostPost = topPosts.get(0);
StarboardPost secondTop = topPosts.get(1);
StarboardPost thirdTopMostPost = topPosts.get(2);
Assert.assertEquals(3, topPosts.size());
Assert.assertEquals(starboardPost1, topPosts.get(0));
Assert.assertEquals(starboardPost2, secondTop);
Assert.assertEquals(starboardPost3, thirdTopMostPost);
Assert.assertTrue(topMostPost.getReactions().size() > secondTop.getReactions().size());
Assert.assertTrue(secondTop.getReactions().size() > thirdTopMostPost.getReactions().size());
}
@Test
public void testRemovePost() {
StarboardPost starboardPost1 = Mockito.mock(StarboardPost.class);
when(starboardPost1.getReactions()).thenReturn(new ArrayList<>(Arrays.asList(Mockito.mock(StarboardPostReaction.class), Mockito.mock(StarboardPostReaction.class))));
testUnit.removePost(starboardPost1);
verify(repository, times(1)).delete(any(StarboardPost.class));
}
@Test
public void testSetStarboardPostIgnored() {
Long messageId = 5L;
Boolean ignoredValue = true;
StarboardPost post = Mockito.mock(StarboardPost.class);
when(repository.findByStarboardMessageId(messageId)).thenReturn(post);
testUnit.setStarboardPostIgnored(messageId, ignoredValue);
verify(post, times(1)).setIgnored(true);
verify(repository, times(1)).save(post);
}
@Test
public void testIsStarboardPost() {
Long starboardPostId = 5L;
when(repository.existsByStarboardMessageId(starboardPostId)).thenReturn(true);
boolean starboardPost = testUnit.isStarboardPost(starboardPostId);
Assert.assertTrue(starboardPost);
}
@Test
public void testFindByMessageId() {
Long messageId = 5L;
StarboardPost post = Mockito.mock(StarboardPost.class);
when(repository.findByPostMessageId(messageId)).thenReturn(post);
Optional<StarboardPost> postOptional = testUnit.findByMessageId(messageId);
Assert.assertTrue(postOptional.isPresent());
postOptional.ifPresent(starboardPost -> Assert.assertEquals(starboardPost, post));
}
@Test
public void testFindByMessageIdMissing() {
Long messageId = 5L;
when(repository.findByPostMessageId(messageId)).thenReturn(null);
Optional<StarboardPost> postOptional = testUnit.findByMessageId(messageId);
Assert.assertFalse(postOptional.isPresent());
}
@Test
public void testFindByStarboardPostId() {
Long postId = 5L;
StarboardPost post = Mockito.mock(StarboardPost.class);
when(repository.findByStarboardMessageId(postId)).thenReturn(post);
Optional<StarboardPost> postOptional = testUnit.findByStarboardPostId(postId);
Assert.assertTrue(postOptional.isPresent());
postOptional.ifPresent(starboardPost -> Assert.assertEquals(starboardPost, post));
}
@Test
public void testFindByStarboardPostIdMissing() {
Long postId = 5L;
when(repository.findByStarboardMessageId(postId)).thenReturn(null);
Optional<StarboardPost> postOptional = testUnit.findByStarboardPostId(postId);
Assert.assertFalse(postOptional.isPresent());
}
@Test
public void testRetrievePostCount() {
StarboardPost starboardPost1 = Mockito.mock(StarboardPost.class);
StarboardPost starboardPost2 = Mockito.mock(StarboardPost.class);
List<StarboardPost> posts = Arrays.asList(starboardPost1, starboardPost2);
when(repository.findByServer_Id(SERVER_ID)).thenReturn(posts);
Integer retrievedPostCount = testUnit.getPostCount(SERVER_ID);
Assert.assertEquals(posts.size(), retrievedPostCount.intValue());
}
}

View File

@@ -0,0 +1,166 @@
package dev.sheldan.abstracto.starboard.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.starboard.converter.StarStatsUserConverter;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.model.template.StarStatsUser;
import dev.sheldan.abstracto.starboard.repository.StarboardPostReactionRepository;
import dev.sheldan.abstracto.starboard.repository.result.StarStatsGuildUserResult;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StarboardPostReactorManagementServiceBeanTest {
@InjectMocks
private StarboardPostReactorManagementServiceBean testUnit;
@Mock
private StarboardPostReactionRepository repository;
@Mock
private StarStatsUserConverter converter;
@Captor
private ArgumentCaptor<StarboardPostReaction> reactorCaptor;
@Mock
private AUserInAServer aUserInAServer;
@Mock
private AServer server;
@Mock
private AUser aUser;
private static final Long SERVER_ID = 4L;
@Test
public void testAddReactor() {
StarboardPost post = Mockito.mock(StarboardPost.class);
when(aUserInAServer.getUserReference()).thenReturn(aUser);
when(aUserInAServer.getServerReference()).thenReturn(server);
testUnit.addReactor(post, aUserInAServer);
verify(repository, times(1)).save(reactorCaptor.capture());
StarboardPostReaction reaction = reactorCaptor.getValue();
Assert.assertEquals(post, reaction.getStarboardPost());
Assert.assertEquals(aUserInAServer, reaction.getReactor());
}
@Test
public void testRemoveReactor() {
StarboardPost post = Mockito.mock(StarboardPost.class);
when(aUserInAServer.getUserReference()).thenReturn(aUser);
when(aUserInAServer.getServerReference()).thenReturn(server);
testUnit.removeReactor(post, aUserInAServer);
verify(repository, times(1)).deleteByReactorAndStarboardPost(aUserInAServer, post);
}
@Test
public void testRemoveReactors() {
StarboardPost post = Mockito.mock(StarboardPost.class);
testUnit.removeReactors(post);
verify(repository, times(1)).deleteByStarboardPost(post);
}
@Test
public void testRetrieveStarCount() {
Integer stars = 5;
when(repository.getReactionCountByServer(SERVER_ID)).thenReturn(stars);
Integer starCount = testUnit.getStarCount(SERVER_ID);
Assert.assertEquals(stars, starCount);
}
@Test
public void testRetrieveTopStarGiverAllAreAvailable() {
testTopStarGiver(2, 2);
}
@Test
public void testRetrieveTopStarGiverNotAllAreAvailable() {
testTopStarGiver(2, 3);
}
@Test
public void testRetrieveTopStarGiverMoreAreAvailable() {
testTopStarGiver(1, 1);
}
@Test
public void testRetrieveTopStarReceiverAllAreAvailable() {
testTopStarReceiver(2, 2);
}
@Test
public void testRetrieveTopStarReceiverNotAllAreAvailable() {
testTopStarReceiver(2, 3);
}
@Test
public void testRetrieveTopStarReceiverMoreAreAvailable() {
testTopStarReceiver(1, 1);
}
private void testTopStarReceiver(int expectedAmount, Integer amountToRetrieve) {
StarStatsUser user1 = Mockito.mock(StarStatsUser.class);
StarStatsUser user2 = Mockito.mock(StarStatsUser.class);
setupStarStatsReceiverResult(amountToRetrieve, SERVER_ID, user1, user2);
List<CompletableFuture<StarStatsUser>> starStatsUsers = testUnit.retrieveTopStarReceiver(SERVER_ID, amountToRetrieve);
Assert.assertEquals(expectedAmount, starStatsUsers.size());
Assert.assertEquals(user1, starStatsUsers.get(0).join());
if(amountToRetrieve > 1) {
Assert.assertEquals(user2, starStatsUsers.get(1).join());
}
}
private void testTopStarGiver(int expectedAmount, Integer amountToRetrieve) {
StarStatsUser user1 = Mockito.mock(StarStatsUser.class);
StarStatsUser user2 = Mockito.mock(StarStatsUser.class);
setupStarStatsGiverResult(amountToRetrieve, SERVER_ID, user1, user2);
List<CompletableFuture<StarStatsUser>> starStatsUsers = testUnit.retrieveTopStarGiver(SERVER_ID, amountToRetrieve);
Assert.assertEquals(expectedAmount, starStatsUsers.size());
Assert.assertEquals(user1, starStatsUsers.get(0).join());
if(amountToRetrieve > 1) {
Assert.assertEquals(user2, starStatsUsers.get(1).join());
}
}
private void setupStarStatsGiverResult(Integer amountToRetrieve, Long serverId, StarStatsUser user1, StarStatsUser user2) {
StarStatsGuildUserResult result1 = Mockito.mock(StarStatsGuildUserResult.class);
StarStatsGuildUserResult result2 = Mockito.mock(StarStatsGuildUserResult.class);
List<StarStatsGuildUserResult> results = Arrays.asList(result1, result2);
when(repository.findTopStarGiverInServer(serverId, amountToRetrieve)).thenReturn(results);
List<CompletableFuture<StarStatsUser>> statsUser = new ArrayList<>();
statsUser.add(CompletableFuture.completedFuture(user1));
if (amountToRetrieve > 1) {
statsUser.add(CompletableFuture.completedFuture(user2));
}
when(converter.convertToStarStatsUser(results, serverId)).thenReturn(statsUser);
}
private void setupStarStatsReceiverResult(Integer amountToRetrieve, Long serverId, StarStatsUser user1, StarStatsUser user2) {
StarStatsGuildUserResult result1 = Mockito.mock(StarStatsGuildUserResult.class);
StarStatsGuildUserResult result2 = Mockito.mock(StarStatsGuildUserResult.class);
List<StarStatsGuildUserResult> results = Arrays.asList(result1, result2);
when(repository.retrieveTopStarReceiverInServer(serverId, amountToRetrieve)).thenReturn(results);
List<CompletableFuture<StarStatsUser>> statsUser = new ArrayList<>();
statsUser.add(CompletableFuture.completedFuture(user1));
if (amountToRetrieve > 1) {
statsUser.add(CompletableFuture.completedFuture(user2));
}
when(converter.convertToStarStatsUser(results, serverId)).thenReturn(statsUser);
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>starboard-int</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,62 @@
package dev.sheldan.abstracto.starboard.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
public class StarboardFeature implements FeatureConfig {
public static final String STAR_LVL_CONFIG_PREFIX = "starLvl";
public static final String STAR_EMOTE_PREFIX = "star";
public static final String STAR_BADGE_EMOTE_PREFIX = "starboardBadge";
public static final String STAR_LEVELS_CONFIG_KEY = "starLvls";
public static final String STAR_EMOTE = "star";
@Autowired
private DefaultConfigManagementService defaultConfigManagementService;
@Override
public FeatureDefinition getFeature() {
return StarboardFeatureDefinition.STARBOARD;
}
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(StarboardPostTarget.STARBOARD);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
List<String> configKeys = new ArrayList<>();
int maxLevels = getMaxLevels();
for(int i = maxLevels; i > 0; i--) {
configKeys.add(StarboardFeature.STAR_LVL_CONFIG_PREFIX + i);
}
return configKeys;
}
@Override
public List<String> getRequiredEmotes() {
List<String> emoteNames = new ArrayList<>();
int maxLevels = getMaxLevels();
for(int i = maxLevels; i > 0; i--) {
emoteNames.add(StarboardFeature.STAR_EMOTE_PREFIX + i);
}
emoteNames.add(StarboardFeature.STAR_BADGE_EMOTE_PREFIX + 1);
emoteNames.add(StarboardFeature.STAR_BADGE_EMOTE_PREFIX + 2);
emoteNames.add(StarboardFeature.STAR_BADGE_EMOTE_PREFIX + 3);
return emoteNames;
}
private int getMaxLevels() {
return defaultConfigManagementService.getDefaultConfig(STAR_LEVELS_CONFIG_KEY).getLongValue().intValue();
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.starboard.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum StarboardFeatureDefinition implements FeatureDefinition {
STARBOARD("starboard");
private String key;
StarboardFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.starboard.config;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import lombok.Getter;
@Getter
public enum StarboardPostTarget implements PostTargetEnum {
STARBOARD("starboard");
private String key;
StarboardPostTarget(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,85 @@
package dev.sheldan.abstracto.starboard.model.database;
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 lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
@Entity
@Table(name="starboard_post")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class StarboardPost implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_user_in_server_id", nullable = false)
private AUserInAServer author;
@Column(name = "starboard_message_id")
private Long starboardMessageId;
@Column(name = "post_message_id")
private Long postMessageId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "channel_id", nullable = false)
private AChannel starboardChannel;
@Getter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_channel_id", nullable = false)
private AChannel sourceChannel;
@Transient
private Integer reactionCount;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
@PostLoad
private void onLoad() {
this.reactionCount = this.reactions.size();
}
@Getter
@OneToMany(fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE},
mappedBy = "starboardPost")
private List<StarboardPostReaction> reactions;
@Column(name = "starred_date")
private Instant starredDate;
@Column(name = "ignored")
private boolean ignored;
public int getReactionCount() {
if(this.reactions == null) {
return 0;
}
return this.reactions.size();
}
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.starboard.model.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
@Entity
@Table(name="starboard_post_reaction")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class StarboardPostReaction implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reactor_user_in_server_id", nullable = false)
private AUserInAServer reactor;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private StarboardPost starboardPost;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.starboard.model.template;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class GuildStarStatsModel {
private List<StarStatsPost> topPosts;
private List<StarStatsUser> starReceiver;
private List<StarStatsUser> starGiver;
private Integer totalStars;
private List<String> badgeEmotes;
private Integer starredMessages;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.starboard.model.template;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
@Getter
@Setter
@Builder
public class MemberStarStatsModel {
private List<StarStatsPost> topPosts;
private Long receivedStars;
private Long givenStars;
private List<String> badgeEmotes;
private Member member;
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.starboard.model.template;
import dev.sheldan.abstracto.core.utils.MessageUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class StarStatsPost {
private Long serverId;
private Long channelId;
private Long messageId;
private Integer starCount;
public String getMessageUrl() {
return MessageUtils.buildMessageUrl(serverId ,channelId, messageId);
}
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.starboard.model.template;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
@Getter
@Setter
@Builder
public class StarStatsUser {
private AUserInAServer user;
private Member member;
private Integer starCount;
}

View File

@@ -0,0 +1,24 @@
package dev.sheldan.abstracto.starboard.model.template;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.context.ServerContext;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
@Getter
@Setter
@SuperBuilder
public class StarboardPostModel extends ServerContext {
private Member author;
private TextChannel channel;
private AUser user;
private AChannel aChannel;
private CachedMessage message;
private Integer starCount;
private String starLevelEmote;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.starboard.service;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.template.GuildStarStatsModel;
import dev.sheldan.abstracto.starboard.model.template.MemberStarStatsModel;
import dev.sheldan.abstracto.starboard.model.template.StarStatsPost;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface StarboardService {
CompletableFuture<Void> createStarboardPost(CachedMessage message, List<AUserInAServer> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser);
CompletableFuture<Void> updateStarboardPost(StarboardPost post, CachedMessage message, List<AUserInAServer> userExceptAuthor);
void deleteStarboardMessagePost(StarboardPost message);
CompletableFuture<GuildStarStatsModel> retrieveStarStats(Long serverId);
MemberStarStatsModel retrieveStarStatsForMember(Member member);
StarStatsPost fromStarboardPost(StarboardPost starboardPost);
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.abstracto.starboard.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import java.util.List;
import java.util.Optional;
public interface StarboardPostManagementService {
StarboardPost createStarboardPost(CachedMessage starredMessage, AUserInAServer starredUser, AServerAChannelMessage starboardPost);
StarboardPost createStarboardPost(StarboardPost post);
void setStarboardPostMessageId(StarboardPost post, Long messageId);
List<StarboardPost> retrieveTopPosts(Long serverId, Integer count);
List<StarboardPost> retrieveTopPostsForUserInServer(Long serverId, Long userId, Integer count);
Long retrieveGivenStarsOfUserInServer(Long serverId, Long userId);
Long retrieveReceivedStarsOfUserInServer(Long serverId, Long userId);
List<StarboardPost> retrieveAllPosts(Long serverId);
Integer getPostCount(Long serverId);
Optional<StarboardPost> findByMessageId(Long messageId);
Optional<StarboardPost> findByStarboardPostId(Long postId);
void setStarboardPostIgnored(Long starboardPostId, Boolean newValue);
boolean isStarboardPost(Long starboardPostId);
void removePost(StarboardPost starboardPost);
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.starboard.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.starboard.model.database.StarboardPost;
import dev.sheldan.abstracto.starboard.model.database.StarboardPostReaction;
import dev.sheldan.abstracto.starboard.model.template.StarStatsUser;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface StarboardPostReactorManagementService {
StarboardPostReaction addReactor(StarboardPost post, AUserInAServer user);
void removeReactor(StarboardPost post, AUserInAServer user);
void removeReactors(StarboardPost post);
Integer getStarCount(Long serverId);
List<CompletableFuture<StarStatsUser>> retrieveTopStarGiver(Long serverId, Integer count);
List<CompletableFuture<StarStatsUser>> retrieveTopStarReceiver(Long serverId, Integer count);
}