[OPB-12] adding news/newsupdate command and introducing mechanisms for cleanup thereof

changing templates to have the metaconfig
moving starboard custom templates
This commit is contained in:
Sheldan
2021-04-24 01:38:59 +02:00
parent d860ad6291
commit bf55064984
64 changed files with 2477 additions and 10 deletions

View File

@@ -101,6 +101,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.oneplus.bot.application.modules</groupId>
<artifactId>news</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
<?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.oneplus.bot.application.modules</groupId>
<artifactId>oneplus-bot-modules</artifactId>
<version>1.3.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>news</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>
</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,54 @@
package dev.sheldan.oneplus.bot.modules.news.commands;
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.oneplus.bot.modules.news.config.NewsFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.news.config.NewsModuleDefinition;
import dev.sheldan.oneplus.bot.modules.news.service.NewsServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class News extends AbstractConditionableCommand {
@Autowired
private NewsServiceBean newsServiceBean;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String text = (String) commandContext.getParameters().getParameters().get(0);
return newsServiceBean.sendNewsPost(text, commandContext.getMessage())
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter newsText = Parameter.builder().name("text").type(String.class).remainder(true).templated(true).build();
List<Parameter> parameters = Arrays.asList(newsText);
HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build();
return CommandConfiguration.builder()
.name("news")
.module(NewsModuleDefinition.NEWS)
.parameters(parameters)
.supportsEmbedException(true)
.async(true)
.help(helpInfo)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return NewsFeatureDefinition.NEWS;
}
}

View File

@@ -0,0 +1,57 @@
package dev.sheldan.oneplus.bot.modules.news.commands;
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.oneplus.bot.modules.news.config.NewsFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.news.config.NewsModuleDefinition;
import dev.sheldan.oneplus.bot.modules.news.service.NewsServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class UpdateNews extends AbstractConditionableCommand {
@Autowired
private NewsServiceBean newsServiceBean;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long messageId = (Long) parameters.get(0);
String postText = (String) parameters.get(1);
return newsServiceBean.updateNewsPostViaId(messageId, postText, commandContext.getMessage())
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter newsPostId = Parameter.builder().name("newsPostId").type(Long.class).templated(true).build();
Parameter newsText = Parameter.builder().name("text").type(String.class).remainder(true).templated(true).build();
List<Parameter> parameters = Arrays.asList(newsPostId, newsText);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("updateNews")
.module(NewsModuleDefinition.NEWS)
.parameters(parameters)
.supportsEmbedException(true)
.async(true)
.help(helpInfo)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return NewsFeatureDefinition.NEWS;
}
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.oneplus.bot.modules.news.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class NewsFeature implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return NewsFeatureDefinition.NEWS;
}
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(NewsPostTarget.NEWS_TARGET);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.modules.news.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum NewsFeatureDefinition implements FeatureDefinition {
NEWS("news");
private String key;
NewsFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.oneplus.bot.modules.news.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 NewsModuleDefinition implements ModuleDefinition {
public static final String NEWS = "news";
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name(NEWS).templated(true).build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.modules.news.config;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import lombok.Getter;
@Getter
public enum NewsPostTarget implements PostTargetEnum {
NEWS_TARGET("news");
private String key;
NewsPostTarget(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.oneplus.bot.modules.news.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:news.properties")
public class NewsProperties {
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.oneplus.bot.modules.news.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class NewsPostLockedException extends AbstractoRunTimeException implements Templatable {
@Override
public String getTemplateName() {
return "news_post_locked_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.oneplus.bot.modules.news.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class NewsPostNotFoundException extends AbstractoRunTimeException implements Templatable {
@Override
public String getTemplateName() {
return "news_post_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.oneplus.bot.modules.news.job;
import dev.sheldan.oneplus.bot.modules.news.service.NewsServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class NewsPostCleanupJob extends QuartzJobBean {
@Autowired
private NewsServiceBean newsServiceBean;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
log.info("Executing news post cleanup job.");
newsServiceBean.cleanUpNewsPosts();
} catch (Exception exception) {
log.error("Failed to execute news post cleanup job.", exception);
}
}
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.oneplus.bot.modules.news.job;
import dev.sheldan.oneplus.bot.modules.news.service.NewsServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class NewsPostLockingJob extends QuartzJobBean {
@Autowired
private NewsServiceBean newsServiceBean;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
log.info("Executing news post lock job.");
newsServiceBean.lockNewsPosts();
} catch (Exception exception) {
log.error("Failed to execute news post lock job.", exception);
}
}
}

View File

@@ -0,0 +1,46 @@
package dev.sheldan.oneplus.bot.modules.news.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageTextUpdatedListener;
import dev.sheldan.abstracto.core.models.listener.MessageTextUpdatedModel;
import dev.sheldan.oneplus.bot.modules.news.config.NewsFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsPost;
import dev.sheldan.oneplus.bot.modules.news.service.NewsServiceBean;
import dev.sheldan.oneplus.bot.modules.news.service.management.NewsPostManagementServiceBean;
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 NewsMessageSourceMessageUpdatedListener implements AsyncMessageTextUpdatedListener {
@Autowired
private NewsPostManagementServiceBean newsPostManagementServiceBean;
@Autowired
private NewsServiceBean newsServiceBean;
@Override
public DefaultListenerResult execute(MessageTextUpdatedModel model) {
Optional<NewsPost> existingPostOptional = newsPostManagementServiceBean.getNewsPostForSourceMessage(model.getAfter().getIdLong());
if(existingPostOptional.isPresent()) {
NewsPost newsPost = existingPostOptional.get();
if(!newsPost.isLocked()) {
newsServiceBean.updateNewsPost(newsPost, model.getAfter());
return DefaultListenerResult.PROCESSED;
} else {
log.info("Not updating news post {}, because it is locked.", newsPost.getSourceMessageId());
}
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return NewsFeatureDefinition.NEWS;
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.oneplus.bot.modules.news.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
@Getter
@Setter
@Builder
public class NewsMessageModel {
private String messageText;
private Message message;
private Member author;
private Role newsRole;
}

View File

@@ -0,0 +1,51 @@
package dev.sheldan.oneplus.bot.modules.news.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.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_post")
@Getter
@Setter
@EqualsAndHashCode
public class NewsPost {
@Id
@Column(name = "source_message_id")
private Long sourceMessageId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@Column(name = "news_message_id")
private Long newsMessageId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_user_in_server_id", nullable = false)
private AUserInAServer author;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_channel_id", nullable = false)
private AChannel sourceChannel;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "news_channel_id", nullable = false)
private AChannel newsChannel;
@Column(name = "locked")
private boolean locked;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.oneplus.bot.modules.news.repository;
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsPost;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@Repository
public interface NewsPostRepository extends JpaRepository<NewsPost, Long> {
List<NewsPost> findByCreatedLessThanAndLockedFalse(Instant date);
List<NewsPost> findByUpdatedLessThanAndLockedTrue(Instant date);
Optional<NewsPost> findByNewsMessageId(Long newsMessageId);
}

View File

@@ -0,0 +1,127 @@
package dev.sheldan.oneplus.bot.modules.news.service;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.oneplus.bot.modules.news.config.NewsPostTarget;
import dev.sheldan.oneplus.bot.modules.news.exception.NewsPostLockedException;
import dev.sheldan.oneplus.bot.modules.news.model.NewsMessageModel;
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsPost;
import dev.sheldan.oneplus.bot.modules.news.service.management.NewsPostManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class NewsServiceBean {
@Autowired
private PostTargetService postTargetService;
@Autowired
private TemplateService templateService;
@Autowired
private NewsPostManagementServiceBean newsPostManagementServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private NewsServiceBean self;
private static final String MESSAGE_TEMPLATE_KEY = "news_post";
@Value("${abstracto.feature.news.postLockSeconds}")
private Long postLockSeconds;
@Value("${abstracto.feature.news.removalDays}")
private Long removalDays;
@Transactional
public void lockNewsPosts() {
Instant oldestDate = Instant.now().minus(postLockSeconds, ChronoUnit.SECONDS).truncatedTo(ChronoUnit.DAYS);
log.info("Locking news posts older than {}.", oldestDate);
List<NewsPost> oldPosts = newsPostManagementServiceBean.findNewsPostsOlderNotLocked(oldestDate);
log.info("Locking {} news posts.", oldPosts.size());
oldPosts.forEach(newsPost -> newsPost.setLocked(true));
}
@Transactional
public void cleanUpNewsPosts() {
Instant oldestDate = Instant.now().minus(removalDays, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS);
log.info("Deleting news posts older than {}.", oldestDate);
List<NewsPost> oldPosts = newsPostManagementServiceBean.findNewsPostsUpdatedOlderThanAndLocked(oldestDate);
newsPostManagementServiceBean.deleteNewsPosts(oldPosts);
}
public CompletableFuture<Message> sendNewsPost(String text, Message commandMessage) {
NewsMessageModel model = NewsMessageModel
.builder()
.messageText(text)
.message(commandMessage)
.author(commandMessage.getMember())
.build();
log.info("Sending new message post based on message {}.", commandMessage.getIdLong());
Long serverId = commandMessage.getGuild().getIdLong();
MessageToSend messageToSend = templateService.renderEmbedTemplate(MESSAGE_TEMPLATE_KEY, model, serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, NewsPostTarget.NEWS_TARGET, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures)
.thenApply(unused -> {
Message createdMessage = messageFutures.get(0).join();
self.persistPost(commandMessage, createdMessage);
return createdMessage;
});
}
public CompletableFuture<Void> updateNewsPostViaId(Long postId, String postText, Message updatedMessage) {
NewsPost post = newsPostManagementServiceBean.getNewsPostForNewsMessageId(postId);
if(post.isLocked()) {
throw new NewsPostLockedException();
}
return updateNewsPostMessage(post, updatedMessage, postText);
}
public CompletableFuture<Void> updateNewsPost(NewsPost newsPost, Message updatedMessage) {
String contentStripped = updatedMessage.getContentRaw();
String command = contentStripped.split(" ")[0];
String postText = updatedMessage.getContentRaw().replaceFirst(command, "");
return updateNewsPostMessage(newsPost, updatedMessage, postText);
}
private CompletableFuture<Void> updateNewsPostMessage(NewsPost newsPost, Message updatedMessage, String postText) {
NewsMessageModel model = NewsMessageModel
.builder()
.messageText(postText)
.message(updatedMessage)
.author(updatedMessage.getMember())
.build();
Long serverId = updatedMessage.getGuild().getIdLong();
newsPost.setUpdated(Instant.now());
log.info("Updating news post {} with new content based on message from user {} in server {}.",
newsPost.getSourceMessageId(), updatedMessage.getIdLong(), updatedMessage.getGuild().getId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(MESSAGE_TEMPLATE_KEY, model, serverId);
TextChannel newsChannel = channelService.getTextChannelFromServer(serverId, newsPost.getNewsChannel().getId());
return channelService.editMessageInAChannelFuture(messageToSend, newsChannel, newsPost.getNewsMessageId())
.thenApply(message -> null);
}
@Transactional
public void persistPost(Message commandMessage, Message createdMessage) {
log.info("Persisting news post with created message {} based on command message {}.", createdMessage.getIdLong(), commandMessage.getIdLong());
newsPostManagementServiceBean.createNewsPost(commandMessage, createdMessage);
}
}

View File

@@ -0,0 +1,77 @@
package dev.sheldan.oneplus.bot.modules.news.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.oneplus.bot.modules.news.exception.NewsPostNotFoundException;
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsPost;
import dev.sheldan.oneplus.bot.modules.news.repository.NewsPostRepository;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@Component
@Slf4j
public class NewsPostManagementServiceBean {
@Autowired
private NewsPostRepository newsPostRepository;
@Autowired
private EntityManager entityManager;
@Autowired
private UserInServerManagementService userInServerManagementService;
public NewsPost createNewsPost(Message commandMessage, Message createdMessage) {
AChannel sourceChannel = entityManager.getReference(AChannel.class, commandMessage.getChannel().getIdLong());
AChannel newsChannel = entityManager.getReference(AChannel.class, createdMessage.getChannel().getIdLong());
AUserInAServer author = userInServerManagementService.loadOrCreateUser(commandMessage.getMember());
NewsPost post = NewsPost
.builder()
.sourceChannel(sourceChannel)
.newsChannel(newsChannel)
.author(author)
.newsMessageId(createdMessage.getIdLong())
.sourceMessageId(commandMessage.getIdLong())
.server(author.getServerReference())
.locked(false)
.build();
log.debug("Created news post based on message {}.", createdMessage.getIdLong());
return newsPostRepository.save(post);
}
public Optional<NewsPost> getNewsPostForSourceMessage(Long sourceMessageId) {
return newsPostRepository.findById(sourceMessageId);
}
public Optional<NewsPost> getNewsPostForNewsMessageIdOptional(Long sourceMessageId) {
return newsPostRepository.findByNewsMessageId(sourceMessageId);
}
public NewsPost getNewsPostForNewsMessageId(Long sourceMessageId) {
return getNewsPostForNewsMessageIdOptional(sourceMessageId).orElseThrow(NewsPostNotFoundException::new);
}
public List<NewsPost> findNewsPostsOlderNotLocked(Instant pointInTime) {
log.debug("Checking for not locked news posts older than {}", pointInTime);
return newsPostRepository.findByCreatedLessThanAndLockedFalse(pointInTime);
}
public List<NewsPost> findNewsPostsUpdatedOlderThanAndLocked(Instant pointInTime) {
log.debug("Checking for not locked news posts updated older than {}.", pointInTime);
return newsPostRepository.findByUpdatedLessThanAndLockedTrue(pointInTime);
}
public void deleteNewsPosts(List<NewsPost> postsToDelete) {
log.info("Deleting {} news posts.", postsToDelete.size());
postsToDelete.forEach(newsPost -> log.info("Deleting news post {}", newsPost.getSourceMessageId()));
newsPostRepository.deleteAll(postsToDelete);
}
}

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="news-tables/tables.xml" relativeToChangelogFile="true"/>
<include file="news-seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,23 @@
<?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="newsFeature" value="(SELECT id FROM feature WHERE key = 'news')"/>
<property name="newsModule" value="(SELECT id FROM module WHERE name = 'news')"/>
<changeSet author="Sheldan" id="news-commands" >
<insert tableName="command">
<column name="name" value="news"/>
<column name="module_id" valueComputed="${newsModule}"/>
<column name="feature_id" valueComputed="${newsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="updateNews"/>
<column name="module_id" valueComputed="${newsModule}"/>
<column name="feature_id" valueComputed="${newsFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,13 @@
<?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="feature.xml" relativeToChangelogFile="true"/>
<include file="module.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
<include file="news_jobs.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-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="news_feature-insertion">
<insert tableName="feature">
<column name="key" value="news"/>
</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="news-module-insertion">
<insert tableName="module">
<column name="name" value="news"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,28 @@
<?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="news-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="newsLockJob"/>
<column name="group_name" value="news"/>
<column name="clazz" value="dev.sheldan.oneplus.bot.modules.news.job.NewsPostLockingJob"/>
<column name="active" value="true"/>
<column name="cron_expression" value="0 0 * * * ?"/>
<column name="recovery" value="false"/>
</insert>
<insert tableName="scheduler_job">
<column name="name" value="newsCleanupJob"/>
<column name="group_name" value="news"/>
<column name="clazz" value="dev.sheldan.oneplus.bot.modules.news.job.NewsPostCleanupJob"/>
<column name="active" value="true"/>
<column name="cron_expression" value="0 0 0 * * ?"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,62 @@
<?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="news_post-table">
<createTable tableName="news_post">
<column name="source_message_id" type="BIGINT">
<constraints nullable="true" primaryKey="true" primaryKeyName="pk_news_post"/>
</column>
<column name="source_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="news_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="author_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="news_message_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="locked" type="boolean">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="true"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addForeignKeyConstraint baseColumnNames="source_channel_id" baseTableName="news_post" constraintName="fk_news_post_source_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="news_channel_id" baseTableName="news_post" constraintName="fk_news_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="author_user_in_server_id" baseTableName="news_post" constraintName="fk_news_post_author"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="news_post" constraintName="fk_news_post_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS news_post_update_trigger ON news_post;
CREATE TRIGGER repost_check_channel_group_update_trigger BEFORE UPDATE ON news_post FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS news_post_insert_trigger ON news_post;
CREATE TRIGGER repost_check_channel_group_insert_trigger BEFORE INSERT ON news_post FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</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="news_post.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.3.9-news/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,7 @@
abstracto.postTargets.news.name=news
abstracto.featureFlags.news.featureName=news
abstracto.featureFlags.news.enabled=false
abstracto.feature.news.removalDays=4
abstracto.feature.news.postLockSeconds=3600

View File

@@ -0,0 +1,24 @@
<?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.oneplus.bot.application</groupId>
<artifactId>application</artifactId>
<version>1.3.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.oneplus.bot.application.modules</groupId>
<artifactId>oneplus-bot-modules</artifactId>
<packaging>pom</packaging>
<modules>
<module>news</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -15,6 +15,7 @@
<modules>
<module>executable</module>
<module>oneplus-bot-customizations</module>
<module>oneplus-bot-modules</module>
</modules>
<dependencyManagement>