[RAB-1] rename project to Sissi

This commit is contained in:
Sheldan
2022-06-07 00:54:43 +02:00
parent a2e304fdd8
commit d06fb7ff30
91 changed files with 138 additions and 138 deletions

View File

@@ -0,0 +1,18 @@
<?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>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sissi-modules</artifactId>
<packaging>pom</packaging>
<modules>
<module>quotes</module>
</modules>
</project>

View File

@@ -0,0 +1,35 @@
<?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>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>quotes</artifactId>
<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>
</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,100 @@
package dev.sheldan.sissi.module.quotes.command;
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.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteCommand extends AbstractConditionableCommand {
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private QuoteCommand self;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Optional<Quote> foundQuote;
if(parameters.isEmpty()) {
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
foundQuote = quoteServiceBean.getRandomQuote(server);
} else {
Member targetMember = (Member) parameters.get(0);
AUserInAServer user = userInServerManagementService.loadOrCreateUser(targetMember);
foundQuote = quoteServiceBean.getRandomQuoteForMember(user);
}
if(foundQuote.isPresent()) {
Quote quoteToDisplay = foundQuote.get();
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
} else {
throw new QuoteNotFoundException();
}
}
@Transactional
public CompletableFuture<Void> sendMessageToChannel(MessageToSend messageToSend, MessageChannel messageChannel) {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, messageChannel));
}
@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("quote")
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,61 @@
package dev.sheldan.sissi.module.quotes.command;
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.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@Component
public class QuoteDelete extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private QuoteServiceBean quoteServiceBean;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long quoteId = (Long) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
quoteServiceBean.deleteQuote(quoteId, server);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter quoteIdParameter = Parameter.builder().templated(true).name("quoteId").type(Long.class).build();
List<Parameter> parameters = Collections.singletonList(quoteIdParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("quoteDelete")
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.async(false)
.requiresConfirmation(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,89 @@
package dev.sheldan.sissi.module.quotes.command;
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.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteGet extends AbstractConditionableCommand {
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private QuoteGet self;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long quoteId = (Long) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
Optional<Quote> possibleQuote = quoteServiceBean.getQuote(quoteId, server);
if(possibleQuote.isPresent()) {
Quote quoteToDisplay = possibleQuote.get();
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
} else {
throw new QuoteNotFoundException();
}
}
@Transactional
public CompletableFuture<Void> sendMessageToChannel(MessageToSend messageToSend, MessageChannel messageChannel) {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, messageChannel));
}
@Override
public CommandConfiguration getConfiguration() {
Parameter quoteIdParameter = Parameter.builder().templated(true).name("quoteId").type(Long.class).build();
List<Parameter> parameters = Collections.singletonList(quoteIdParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("quoteGet")
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,88 @@
package dev.sheldan.sissi.module.quotes.command;
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.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class QuoteSearch extends AbstractConditionableCommand {
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private QuoteSearch self;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String query = (String) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
Optional<Quote> possibleQuote = quoteServiceBean.searchQuote(query, server);
if(possibleQuote.isPresent()) {
Quote quoteToDisplay = possibleQuote.get();
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
} else {
throw new QuoteNotFoundException();
}
}
@Transactional
public CompletableFuture<Void> sendMessageToChannel(MessageToSend messageToSend, MessageChannel messageChannel) {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, messageChannel));
}
@Override
public CommandConfiguration getConfiguration() {
Parameter searchParameter = Parameter.builder().templated(true).name("query").type(String.class).build();
List<Parameter> parameters = Collections.singletonList(searchParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("quoteSearch")
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,70 @@
package dev.sheldan.sissi.module.quotes.command;
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.sissi.module.quotes.config.QuotesFeatureDefinition;
import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.model.command.QuoteStatsModel;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
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 QuoteStats extends AbstractConditionableCommand {
@Autowired
private QuoteServiceBean quoteServiceBean;
@Autowired
private ChannelService channelService;
private static final String QUOTE_STATS_RESPONSE_TEMPLATE_KEY = "quoteStats_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Member targetMember;
if(parameters.isEmpty()) {
targetMember = commandContext.getAuthor();
} else {
targetMember = (Member) parameters.get(0);
}
QuoteStatsModel model = quoteServiceBean.getQuoteStats(targetMember);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannelList(QUOTE_STATS_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter searchParameter = Parameter.builder().templated(true).name("member").type(Member.class).optional(true).build();
List<Parameter> parameters = Collections.singletonList(searchParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("quoteStats")
.module(QuotesModuleDefinition.QUOTES)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.quotes.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import org.springframework.stereotype.Component;
@Component
public class QuotesFeatureConfig implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return QuotesFeatureDefinition.QUOTES;
}
}

View File

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

View File

@@ -0,0 +1,25 @@
package dev.sheldan.sissi.module.quotes.config;
import dev.sheldan.abstracto.core.command.config.ModuleDefinition;
import dev.sheldan.abstracto.core.command.config.ModuleInfo;
import org.springframework.stereotype.Component;
@Component
public class QuotesModuleDefinition implements ModuleDefinition {
public static final String QUOTES = "quotes";
@Override
public ModuleInfo getInfo() {
return ModuleInfo
.builder()
.name(QUOTES)
.templated(true)
.build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.sissi.module.quotes.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:quotes.properties")
public class QuotesProperties {
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.sissi.module.quotes.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class QuoteNotFoundException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "quote_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,24 @@
package dev.sheldan.sissi.module.quotes.model.command;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import lombok.Builder;
import lombok.Getter;
import java.time.Instant;
import java.util.List;
@Getter
@Builder
public class QuoteResponseModel {
private Long quoteId;
private String authorAvatarURL;
private String authorName;
private ServerChannelMessage quotedMessage;
private String quoteContent;
private List<String> imageAttachmentURLs;
private List<String> fileAttachmentURLs;
private String adderAvatarURL;
private String adderName;
private Instant creationDate;
private String sourceChannelName;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.quotes.model.command;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class QuoteStatsModel {
private Long authorCount;
private String userName;
private Long quoteCount;
private Long userId;
private Long serverId;
}

View File

@@ -0,0 +1,66 @@
package dev.sheldan.sissi.module.quotes.model.database;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
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.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "quote")
@Getter
@Setter
@EqualsAndHashCode
public class Quote {
@EmbeddedId
@Getter
private ServerSpecificId id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("serverId")
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_user_in_server_id", nullable = false)
private AUserInAServer author;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "adder_user_in_server_id", nullable = false)
private AUserInAServer adder;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_channel_id", nullable = false)
private AChannel sourceChannel;
@Getter
@Column(name = "message_id")
private Long messageId;
@OneToMany(
fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "quote")
@Builder.Default
private List<QuoteAttachment> attachments = new ArrayList<>();
@Getter
@Column(name = "text")
private String text;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.sissi.module.quotes.model.database;
import lombok.*;
import javax.persistence.*;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "quote_attachment")
@Getter
@Setter
@EqualsAndHashCode
public class QuoteAttachment {
@Id
@Getter
@Setter
@Column(name = "id", nullable = false)
private Long id;
@Getter
@Setter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(
{
@JoinColumn(updatable = false, insertable = false, name = "quote_id", referencedColumnName = "id"),
@JoinColumn(updatable = false, insertable = false, name = "server_id", referencedColumnName = "server_id")
})
private Quote quote;
@Getter
@Setter
@Column(name = "url", nullable = false)
private String url;
@Getter
@Setter
@Column(name = "is_image", nullable = false)
@Builder.Default
private Boolean isImage = false;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.sissi.module.quotes.repository;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface QuoteRepository extends JpaRepository<Quote, ServerSpecificId> {
List<Quote> findByTextContainingAndServer(String text, AServer server);
List<Quote> findByServer(AServer server);
List<Quote> findByAuthor(AUserInAServer author);
Long countByAuthor(AUserInAServer author);
Long countByAdder(AUserInAServer adder);
}

View File

@@ -0,0 +1,239 @@
package dev.sheldan.sissi.module.quotes.service;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.UserService;
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.core.utils.CompletableFutureList;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.command.QuoteResponseModel;
import dev.sheldan.sissi.module.quotes.model.command.QuoteStatsModel;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.model.database.QuoteAttachment;
import dev.sheldan.sissi.module.quotes.repository.QuoteRepository;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.AbstractChannel;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class QuoteServiceBean {
@Autowired
private QuoteRepository quoteRepository;
@Autowired
private MemberService memberService;
@Autowired
private UserService userService;
@Autowired
private TemplateService templateService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private SecureRandom secureRandom;
@Autowired
private ChannelService channelService;
private static final String QUOTE_RESPONSE_TEMPLATE_KEY = "quote_response";
public Optional<Quote> getRandomQuoteForMember(AUserInAServer aUserInAServer) {
// not nice, but good enough for now
List<Quote> allQuotes = quoteRepository.findByAuthor(aUserInAServer);
if(allQuotes.isEmpty()) {
return Optional.empty();
}
return Optional.of(allQuotes.get(secureRandom.nextInt(allQuotes.size())));
}
public Optional<Quote> getRandomQuote(AServer server) {
// not nice, but good enough for now
List<Quote> allQuotes = quoteRepository.findByServer(server);
if(allQuotes.isEmpty()) {
return Optional.empty();
}
return Optional.of(allQuotes.get(secureRandom.nextInt(allQuotes.size())));
}
public void deleteQuote(Long quoteId, AServer server) {
Optional<Quote> existingQuote = getQuote(quoteId, server);
if(existingQuote.isPresent()) {
quoteRepository.delete(existingQuote.get());
log.info("Deleting quote with id {} in server {}.", quoteId, server.getId());
} else {
throw new QuoteNotFoundException();
}
}
public Optional<Quote> getQuote(Long quoteId, AServer server) {
ServerSpecificId id = new ServerSpecificId(server.getId(), quoteId);
log.info("Loading quote with id {} in server {}.", quoteId, server.getId());
return quoteRepository.findById(id);
}
public CompletableFuture<MessageToSend> renderQuoteToMessageToSend(Quote quote) {
ServerChannelMessage quotedMessage = ServerChannelMessage
.builder()
.messageId(quote.getMessageId())
.channelId(quote.getSourceChannel().getId())
.serverId(quote.getServer().getId())
.build();
List<String> imageAttachments = quote
.getAttachments()
.stream()
.filter(QuoteAttachment::getIsImage)
.map(QuoteAttachment::getUrl)
.collect(Collectors.toList());
List<String> fileAttachments = quote
.getAttachments()
.stream()
.filter(quoteAttachment -> !quoteAttachment.getIsImage())
.map(QuoteAttachment::getUrl)
.collect(Collectors.toList());
QuoteResponseModel.QuoteResponseModelBuilder modelBuilder = QuoteResponseModel
.builder()
.quoteContent(quote.getText())
.imageAttachmentURLs(imageAttachments)
.quoteId(quote.getId().getId())
.fileAttachmentURLs(fileAttachments)
.creationDate(quote.getCreated())
.quotedMessage(quotedMessage);
Long quotedUserId = quote.getAuthor().getUserReference().getId();
Long quoteAdderUserId = quote.getAdder().getUserReference().getId();
Long serverId = quote.getServer().getId();
Long channelId = quote.getSourceChannel().getId();
Optional<TextChannel> sourceChannel = channelService.getTextChannelFromServerOptional(serverId, channelId);
List<Long> userIds = Arrays.asList(quotedUserId, quoteAdderUserId);
CompletableFutureList<User> futureList = userService.retrieveUsers(userIds);
CompletableFuture<MessageToSend> messageFuture = new CompletableFuture<>();
futureList.getMainFuture().whenComplete((unused, throwable) ->
createMessageToSend(futureList, modelBuilder, quotedUserId, quoteAdderUserId, serverId, sourceChannel)
.thenAccept(messageFuture::complete)
.exceptionally(throwable1 -> {
messageFuture.completeExceptionally(throwable1);
return null;
}));
return messageFuture;
}
private CompletableFuture<MessageToSend> createMessageToSend( CompletableFutureList<User> possibleUsers, QuoteResponseModel.QuoteResponseModelBuilder modelBuilder,
Long quotedUserId, Long quoteAdderUserId, Long serverId, Optional<TextChannel> sourceChannel) {
return memberService.getMembersInServerAsync(serverId, Arrays.asList(quotedUserId, quoteAdderUserId))
.thenApply(members -> {
List<User> foundUsers = possibleUsers.getObjects();
Member authorMember = members
.stream()
.filter(member -> member.getIdLong() == quotedUserId)
.findFirst()
.orElse(null);
Member adderMember = members
.stream()
.filter(member -> member.getIdLong() == quoteAdderUserId)
.findFirst()
.orElse(null);
User authorUser = foundUsers
.stream()
.filter(user -> user.getIdLong() == quotedUserId)
.findFirst()
.orElse(null);
User adderUser = foundUsers
.stream()
.filter(user -> user.getIdLong() == quoteAdderUserId)
.findFirst()
.orElse(null);
String adderAvatar = Optional
.ofNullable(adderMember)
.map(member -> member.getUser().getAvatarUrl())
.orElse(Optional
.ofNullable(adderUser)
.map(User::getAvatarUrl)
.orElse(null));
String authorAvatar = Optional
.ofNullable(authorMember)
.map(member -> member.getUser().getAvatarUrl())
.orElse(Optional
.ofNullable(authorUser)
.map(User::getAvatarUrl)
.orElse(null));
String adderName = Optional
.ofNullable(adderMember)
.map(Member::getEffectiveName)
.orElse(Optional
.ofNullable(adderUser)
.map(User::getName)
.orElse(null));
String authorName = Optional
.ofNullable(authorMember)
.map(Member::getEffectiveName)
.orElse(Optional
.ofNullable(authorUser)
.map(User::getName)
.orElse(null));
String channelName = sourceChannel
.map(AbstractChannel::getName)
.orElse(null);
QuoteResponseModel model = modelBuilder
.adderAvatarURL(adderAvatar)
.authorAvatarURL(authorAvatar)
.adderName(adderName)
.authorName(authorName)
.sourceChannelName(channelName)
.build();
return templateService.renderEmbedTemplate(QUOTE_RESPONSE_TEMPLATE_KEY, model, serverId);
});
}
public Optional<Quote> searchQuote(String query, AServer server) {
List<Quote> foundQuotes = quoteRepository.findByTextContainingAndServer(query, server);
if(foundQuotes.isEmpty()) {
return Optional.empty();
}
if(foundQuotes.size() > 1) {
log.info("Found multiple quotes in server {}, returning first one.", server.getId());
}
return Optional.of(foundQuotes.get(0));
}
public QuoteStatsModel getQuoteStats(Member member) {
AUserInAServer user = userInServerManagementService.loadOrCreateUser(member);
return getQuoteStats(user, member);
}
public QuoteStatsModel getQuoteStats(AUserInAServer user, Member member) {
Long authored = quoteRepository.countByAuthor(user);
Long added = quoteRepository.countByAdder(user);
return QuoteStatsModel
.builder()
.quoteCount(added)
.authorCount(authored)
.userName(member.getEffectiveName())
.userId(user.getUserReference().getId())
.serverId(user.getServerReference().getId())
.build();
}
}

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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,40 @@
<?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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<property name="quotesModule" value="(SELECT id FROM module WHERE name = 'quotes')"/>
<property name="quotesFeature" value="(SELECT id FROM feature WHERE key = 'quotes')"/>
<changeSet author="Sheldan" id="quotes-commands">
<insert tableName="command">
<column name="name" value="quote"/>
<column name="module_id" valueComputed="${quotesModule}"/>
<column name="feature_id" valueComputed="${quotesFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="quoteDelete"/>
<column name="module_id" valueComputed="${quotesModule}"/>
<column name="feature_id" valueComputed="${quotesFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="quoteGet"/>
<column name="module_id" valueComputed="${quotesModule}"/>
<column name="feature_id" valueComputed="${quotesFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="quoteSearch"/>
<column name="module_id" valueComputed="${quotesModule}"/>
<column name="feature_id" valueComputed="${quotesFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="quoteStats"/>
<column name="module_id" valueComputed="${quotesModule}"/>
<column name="feature_id" valueComputed="${quotesFeature}"/>
</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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="feature.xml" relativeToChangelogFile="true"/>
<include file="module.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="quotes_feature-insertion">
<insert tableName="feature">
<column name="key" value="quotes"/>
</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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="quotes-module-insertion">
<insert tableName="module">
<column name="name" value="quotes"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,54 @@
<?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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="quote-table">
<createTable tableName="quote">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="quote_pkey"/>
</column>
<column name="author_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="adder_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="source_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="message_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="text" type="VARCHAR(4096)">
<constraints nullable="true"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addForeignKeyConstraint baseColumnNames="author_user_in_server_id" baseTableName="quote" constraintName="fk_quote_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="adder_user_in_server_id" baseTableName="quote" constraintName="fk_quote_adder_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="source_channel_id" baseTableName="quote" constraintName="fk_quote_source_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="quote" constraintName="fk_quote_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS quote_update_trigger ON quote;
CREATE TRIGGER quote_update_trigger BEFORE UPDATE ON quote FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,33 @@
<?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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="quote_attachment-table">
<createTable tableName="quote_attachment">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="quote_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="url" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="is_image" type="BOOLEAN">
<constraints nullable="false"/>
</column>
</createTable>
<addPrimaryKey columnNames="server_id, id" tableName="quote_attachment" constraintName="pk_quote" validate="true"/>
<addForeignKeyConstraint baseColumnNames="quote_id" baseTableName="quote_attachment" constraintName="fk_quote_attachment_quote"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="quote" validate="true"/>
</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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="quote.xml" relativeToChangelogFile="true"/>
<include file="quote_attachment.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

File diff suppressed because it is too large Load Diff

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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.0.2/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,2 @@
abstracto.featureFlags.quotes.featureName=quotes
abstracto.featureFlags.quotes.enabled=false