Compare commits

...

28 Commits

Author SHA1 Message Date
Sheldan
61412f434c [maven-release-plugin] prepare release sissi-1.4.23 2023-11-23 01:04:07 +01:00
Sheldan
12e69a18fb [SIS-xxx] preparing for release 2023-11-23 01:02:35 +01:00
Sheldan
170ddd9c33 [SIS-xxx] fixing cache setup to add additional caches instead of overwriting the provided configuration 2023-11-23 01:01:40 +01:00
Sheldan
de8bbdcbee [SIS-xxx] fixing rest api image version 2023-11-21 00:39:07 +01:00
Sheldan
b5bf53fb6a [maven-release-plugin] prepare for next development iteration 2023-11-21 00:25:40 +01:00
Sheldan
159326dde3 [maven-release-plugin] prepare release sissi-1.4.22 2023-11-21 00:25:32 +01:00
Sheldan
7124d4e1d8 [SIS-xxx] prepare for release 2023-11-21 00:24:47 +01:00
Sheldan
c6f20d617d [SIS-xxx] adding rendering of current Debra donation information
updating for Debra 2023 campaign
adding internal rest api for debra information
adding a debra button to receive information
2023-11-21 00:23:03 +01:00
Sheldan
7449c05462 [maven-release-plugin] prepare for next development iteration 2023-11-14 23:41:52 +01:00
Sheldan
6bd0f0eaa2 [maven-release-plugin] prepare release sissi-1.4.21 2023-11-14 23:41:49 +01:00
Sheldan
9b8a3bcc1e [SIS-xxx] preparing for release 2023-11-14 23:40:38 +01:00
Sheldan
7b11a88256 [SIS-xxx] enabling external configuration of db pool size 2023-11-14 23:39:52 +01:00
Sheldan
30289e1166 [SIS-xxx] fixing template content 2023-11-01 09:20:14 +01:00
Sheldan
0bab3bbe69 [SIS-xxx] fixing template keys 2023-10-29 21:26:50 +01:00
Sheldan
6f75c76fc4 [maven-release-plugin] prepare for next development iteration 2023-10-29 19:16:26 +01:00
Sheldan
0d9aa4ae64 [maven-release-plugin] prepare release sissi-1.4.20 2023-10-29 19:16:22 +01:00
Sheldan
c6b0bf582b [SIS-xxx] bump version 2023-10-29 19:15:58 +01:00
Sheldan
dcd94ba5c0 [maven-release-plugin] prepare for next development iteration 2023-10-29 19:01:54 +01:00
Sheldan
98ae8ada8b [maven-release-plugin] prepare release sissi-1.4.19 2023-10-29 19:01:48 +01:00
Sheldan
96d3918a4d [SIS-25] adding rss news module
updating abstracto version
2023-10-29 19:00:16 +01:00
Sheldan
0d6f71baac [maven-release-plugin] prepare for next development iteration 2023-09-26 23:16:39 +02:00
Sheldan
d3421a6f95 [maven-release-plugin] prepare release sissi-1.4.18 2023-09-26 23:16:35 +02:00
Sheldan
0722bd6320 [SIS-xxx] preparing for release 2023-09-26 23:12:54 +02:00
Sheldan
a910870259 [SIS-xxx] removing discriminator and adapting join/leave text 2023-09-26 23:12:34 +02:00
Sheldan
18cc97600f [maven-release-plugin] prepare for next development iteration 2023-09-07 23:52:52 +02:00
Sheldan
a1ea57a0f3 [maven-release-plugin] prepare release sissi-1.4.17 2023-09-07 23:52:48 +02:00
Sheldan
23b554c93f [SIS-xxx] preparing for new release and upgrade of abstracto 2023-09-07 23:49:24 +02:00
Sheldan
5671c5019a [maven-release-plugin] prepare for next development iteration 2023-09-07 21:35:48 +02:00
254 changed files with 5796 additions and 92 deletions

View File

@@ -28,6 +28,7 @@ docker_build_with_restart(
)
docker_build(registry + 'sissi-db-data', 'deployment/image-packaging/src/main/docker/db-data/')
docker_build(registry + 'sissi-rest-api', 'deployment/image-packaging/src/main/docker/rest-api/')
docker_build(registry + 'sissi-template-data', 'deployment/image-packaging/src/main/docker/template-data/')

View File

@@ -3,16 +3,11 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>executable</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<finalName>sissi</finalName>
<plugins>
@@ -155,6 +150,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>rss-news</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>debra</artifactId>

View File

@@ -1,6 +1,7 @@
spring.datasource.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
spring.datasource.username= ${DB_USER}
spring.datasource.password= ${DB_PASS}
spring.datasource.hikari.maximum-pool-size=${hikariPoolSize}
spring.jpa.hibernate.default_schema=abstracto
spring.jpa.properties.hibernate.default_schema=abstracto
spring.quartz.jdbc.initialize-schema=never

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi</groupId>
<artifactId>sissi</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -22,6 +22,18 @@
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>apache-jena-libs</artifactId>
<version>${apache-jena.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.apptasticsoftware</groupId>
<artifactId>rssreader</artifactId>
<version>${rssreader.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application.module.custom</groupId>
<artifactId>sissi-customizations</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>application</artifactId>
<groupId>dev.sheldan.sissi.application</groupId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,19 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<classifier>jakarta</classifier>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,69 @@
package dev.sheldan.sissi.module.debra.api;
import dev.sheldan.sissi.module.debra.model.api.*;
import dev.sheldan.sissi.module.debra.service.DonationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME;
@RestController
@RequestMapping(value = "/debra")
public class DebraDonationStatusController {
@Autowired
private DonationService donationService;
@GetMapping(value = "/latestDonations", produces = "application/json")
public DonationStats getLatestDonations() {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsResponse donationResponse = donationService.getCachedDonationAmount(serverId);
List<DonationInfo> donations = donationService.getLatestDonations(donationResponse, Integer.MAX_VALUE)
.stream()
.map(DonationInfo::fromDonationItemModel)
.toList();
return DonationStats
.builder()
.totalAmount(donationResponse.getPage().getCollected())
.donations(donations)
.build();
}
@GetMapping(value = "/highestDonations", produces = "application/json")
public DonationStats getHighestDonations() {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsResponse donationResponse = donationService.getCachedDonationAmount(serverId);
List<DonationInfo> donations = donationService.getHighestDonations(donationResponse, Integer.MAX_VALUE)
.stream()
.map(DonationInfo::fromDonationItemModel)
.toList();
return DonationStats
.builder()
.totalAmount(donationResponse.getPage().getCollected())
.donations(donations)
.build();
}
@GetMapping(value = "/campaignInfo", produces = "application/json")
public CampaignInfo getCampaignInfo() {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsResponse donationResponse = donationService.getCachedDonationAmount(serverId);
Description pageObject = donationResponse.getPage();
return CampaignInfo
.builder()
.collected(pageObject.getCollected())
.collectedNet(pageObject.getCollectedNet())
.donationCount(donationResponse.getDonationCount())
.currency(pageObject.getCurrency())
.percent(pageObject.getPercent())
.displayName(pageObject.getDisplayName())
.slug(pageObject.getSlug())
.target(pageObject.getTarget())
.build();
}
}

View File

@@ -0,0 +1,91 @@
package dev.sheldan.sissi.module.debra.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.sissi.module.debra.config.DebraFeatureDefinition;
import dev.sheldan.sissi.module.debra.config.DebraSlashCommandNames;
import dev.sheldan.sissi.module.debra.service.DonationService;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
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 DebraInfoButton extends AbstractConditionableCommand {
private static final String DEBRA_INFO_BUTTON = "debraInfoButton";
private static final String DEBRA_INFO_BUTTON_RESPONSE_TEMPLATE_KEY = "debraInfoButton_response";
private static final String TARGET_CHANNEL_PARAMETER_KEY = "targetChannel";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private DonationService donationService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
GuildMessageChannel targetChannel = slashCommandParameterService.getCommandOption(TARGET_CHANNEL_PARAMETER_KEY, event, GuildMessageChannel.class);
return donationService.sendDebraInfoButtonMessage(targetChannel)
.thenCompose(unused -> interactionService.replyEmbed(DEBRA_INFO_BUTTON_RESPONSE_TEMPLATE_KEY, event))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(DebraSlashCommandNames.DEBRA_INTERNAL)
.commandName("debrainfobutton")
.build();
Parameter targetChannelParameter = Parameter
.builder()
.templated(true)
.name(TARGET_CHANNEL_PARAMETER_KEY)
.type(GuildMessageChannel.class)
.build();
List<Parameter> parameters = Arrays.asList(targetChannelParameter);
return CommandConfiguration.builder()
.name(DEBRA_INFO_BUTTON)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.parameters(parameters)
.supportsEmbedException(true)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return DebraFeatureDefinition.DEBRA;
}
}

View File

@@ -113,20 +113,16 @@ public class Donations extends AbstractConditionableCommand {
private MessageToSend getDonationMessageToSend(Long serverId, Integer top, Integer latest) {
DonationsModel donationModel;
try {
DonationsResponse donationResponse = donationService.fetchCurrentDonationAmount(serverId);
donationModel = donationConverter.convertDonationResponse(donationResponse);
if(top != null) {
donationModel.setDonations(donationService.getHighestDonations(donationResponse, top));
donationModel.setType(DonationsModel.DonationType.TOP);
} else if(latest != null) {
donationModel.setType(DonationsModel.DonationType.LATEST);
donationModel.setDonations(donationService.getLatestDonations(donationResponse, latest));
} else {
donationModel.setDonations(new ArrayList<>());
}
} catch (IOException e) {
throw new AbstractoRunTimeException("Failed to load donation amount.", e);
DonationsResponse donationResponse = donationService.fetchCurrentDonationAmount(serverId);
donationModel = donationConverter.convertDonationResponse(donationResponse);
if(top != null) {
donationModel.setDonations(donationService.getHighestDonations(donationResponse, top));
donationModel.setType(DonationsModel.DonationType.TOP);
} else if(latest != null) {
donationModel.setType(DonationsModel.DonationType.LATEST);
donationModel.setDonations(donationService.getLatestDonations(donationResponse, latest));
} else {
donationModel.setDonations(new ArrayList<>());
}
return templateService.renderEmbedTemplate(DONATIONS_RESPONSE_TEMPLATE_KEY, donationModel);
}

View File

@@ -0,0 +1,33 @@
package dev.sheldan.sissi.module.debra.config;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.jsr107.Eh107Configuration;
import org.ehcache.xml.XmlConfiguration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.URL;
@Configuration
@Slf4j
@EnableCaching
public class CacheConfig {
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
URL myUrl = getClass().getResource("/donation-cache-config.xml");
XmlConfiguration xmlConfig = new XmlConfiguration(myUrl);
org.ehcache.CacheManager myCacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
return cm -> {
myCacheManager.getRuntimeConfiguration().getCacheConfigurations().entrySet().forEach(cacheConfiguration -> {
javax.cache.configuration.Configuration<?, ?> jConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(cacheConfiguration.getValue());
log.info("Creating custom cache: " + cacheConfiguration.getKey());
cm.createCache(cacheConfiguration.getKey(), jConfiguration);
});
};
}
}

View File

@@ -2,4 +2,5 @@ package dev.sheldan.sissi.module.debra.config;
public class DebraSlashCommandNames {
public static final String DEBRA = "debra";
public static final String DEBRA_INTERNAL = "debrainternal";
}

View File

@@ -0,0 +1,71 @@
package dev.sheldan.sissi.module.debra.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.service.MessageService;
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.sissi.module.debra.config.DebraFeatureDefinition;
import dev.sheldan.sissi.module.debra.service.DonationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class DebraInfoButtonClickedListener implements ButtonClickedListener {
@Autowired
private MessageService messageService;
@Autowired
private TemplateService templateService;
@Autowired
private InteractionService interactionService;
private static final String DEBRA_INFO_MESSAGE_TEMPLATE_KEY = "debraInfoMessage";
private static final String DEBRA_INFO_MESSAGE_RESPONSE_TEMPLATE_KEY = "debraInfoMessage_response";
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(DEBRA_INFO_MESSAGE_TEMPLATE_KEY, new Object(), model.getServerId());
messageService.sendMessageToSendToUser(model.getEvent().getUser(), messageToSend).thenAccept(interactionHook -> {
log.info("Send debra info message to user {}", model.getEvent().getUser().getIdLong());
}).exceptionally(throwable -> {
log.error("Failed to send debra info message to user {}", model.getEvent().getUser().getIdLong(), throwable);
return null;
});
MessageToSend responseMessageToSend = templateService.renderEmbedTemplate(DEBRA_INFO_MESSAGE_RESPONSE_TEMPLATE_KEY, new Object(), model.getServerId());
FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(responseMessageToSend, model.getEvent().getInteraction().getHook()))
.thenAccept(interactionHook -> {
log.info("Send debra info message response to user {}", model.getEvent().getUser().getIdLong());
}).exceptionally(throwable -> {
log.error("Failed to send debra info message response to user {}", model.getEvent().getUser().getIdLong(), throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getOrigin().equals(DonationService.DEBRA_INFO_BUTTON_ORIGIN);
}
@Override
public FeatureDefinition getFeature() {
return DebraFeatureDefinition.DEBRA;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.sissi.module.debra.model.api;
import lombok.Builder;
import lombok.Getter;
import java.math.BigDecimal;
import java.math.BigInteger;
@Builder
@Getter
public class CampaignInfo {
private BigInteger donationCount;
private BigDecimal collected;
private BigDecimal target;
private String currency;
private String slug;
private String displayName;
private BigDecimal collectedNet;
private BigDecimal percent;
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.sissi.module.debra.model.api;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -10,11 +11,18 @@ import java.math.BigDecimal;
@Setter
@Builder
public class Description {
@SerializedName("collected")
private BigDecimal collected;
@SerializedName("target")
private BigDecimal target;
@SerializedName("currency")
private String currency;
@SerializedName("slug")
private String slug;
@SerializedName("displayname")
private String displayName;
@SerializedName("collectednet")
private BigDecimal collectedNet;
@SerializedName("percent")
private BigDecimal percent;
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.sissi.module.debra.model.api;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -10,10 +11,16 @@ import java.math.BigDecimal;
@Setter
@Builder
public class Donation {
@SerializedName("amount")
private BigDecimal amount;
@SerializedName("currency")
private String currency;
@SerializedName("text")
private String text;
@SerializedName("anonym")
private Integer anonym;
@SerializedName("firstname")
private String firstname;
@SerializedName("lastname")
private String lastname;
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.sissi.module.debra.model.api;
import dev.sheldan.sissi.module.debra.model.commands.DonationItemModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
@Builder
public class DonationInfo {
private String firstName;
private BigDecimal donationAmount;
private Boolean anonymous;
public static DonationInfo fromDonationItemModel(DonationItemModel donationItemModel) {
return DonationInfo
.builder()
.donationAmount(donationItemModel.getDonationAmount())
.anonymous(donationItemModel.getAnonymous())
.firstName(donationItemModel.getFirstName())
.build();
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.debra.model.api;
import lombok.Builder;
import lombok.Getter;
import java.math.BigDecimal;
import java.util.List;
@Getter
@Builder
public class DonationStats {
private List<DonationInfo> donations;
private BigDecimal totalAmount;
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.sissi.module.debra.model.api;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -11,7 +12,10 @@ import java.util.List;
@Setter
@Builder
public class DonationsResponse {
@SerializedName("page")
private Description page;
@SerializedName("donation_count")
private BigInteger donationCount;
@SerializedName("donations")
private List<Donation> donations;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.sissi.module.debra.model.commands;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Getter
@Setter
public class DebraInfoButtonPayload implements ButtonPayload {
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.sissi.module.debra.model.commands;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class DebraInfoModel {
private String buttonId;
}

View File

@@ -2,10 +2,12 @@ package dev.sheldan.sissi.module.debra.model.commands;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
@Builder
public class DonationItemModel {
private String firstName;

View File

@@ -0,0 +1,18 @@
package dev.sheldan.sissi.module.debra.service;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
@Component
public class DonationCacheService implements InitializingBean {
@Autowired
private CacheManager cacheManager;
@Override
public void afterPropertiesSet() throws Exception {
cacheManager.getCache("donations");
}
}

View File

@@ -2,8 +2,14 @@ package dev.sheldan.sissi.module.debra.service;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
@@ -13,17 +19,22 @@ import dev.sheldan.sissi.module.debra.config.DebraProperties;
import dev.sheldan.sissi.module.debra.converter.DonationConverter;
import dev.sheldan.sissi.module.debra.model.api.Donation;
import dev.sheldan.sissi.module.debra.model.api.DonationsResponse;
import dev.sheldan.sissi.module.debra.model.commands.DebraInfoButtonPayload;
import dev.sheldan.sissi.module.debra.model.commands.DebraInfoModel;
import dev.sheldan.sissi.module.debra.model.commands.DonationItemModel;
import dev.sheldan.sissi.module.debra.model.commands.DonationsModel;
import dev.sheldan.sissi.module.debra.model.listener.DonationResponseModel;
import dev.sheldan.sissi.module.debra.model.listener.DonationNotificationModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.math.BigDecimal;
@@ -60,10 +71,28 @@ public class DonationService {
@Autowired
private ConfigService configService;
@Autowired
private ChannelService channelService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private DonationService self;
private static final String DEBRA_DONATION_NOTIFICATION_TEMPLATE_KEY = "debra_donation_notification";
private static final Pattern MESSAGE_PATTERN = Pattern.compile("(.*) hat (\\d{1,9},\\d{2}) Euro gespendet!<br \\/>Vielen Dank!<br \\/>Nachricht:<br \\/>(.*)");
private static final String DEBRA_INFO_BUTTON_MESSAGE_TEMPLATE_KEY = "debraInfoButton";
public static final String DEBRA_INFO_BUTTON_ORIGIN = "DEBRA_INFO_BUTTON";
public DonationResponseModel parseDonationFromMessage(String message) {
Matcher matcher = MESSAGE_PATTERN.matcher(message);
if (matcher.find()) {
@@ -105,23 +134,32 @@ public class DonationService {
.collect(Collectors.toList());
}
public DonationsResponse fetchCurrentDonationAmount(Long serverId) throws IOException {
Long fetchSize = configService.getLongValueOrConfigDefault(DEBRA_DONATION_API_FETCH_SIZE_KEY, serverId);
Request request = new Request.Builder()
.url(String.format(debraProperties.getDonationAPIUrl(), fetchSize))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
if(!response.isSuccessful()) {
if (log.isDebugEnabled()) {
log.error("Failed to retrieve urban dictionary definition. Response had code {} with body {}.",
response.code(), response.body());
}
throw new DonationAmountNotFoundException();
}
Gson gson = getGson();
@Cacheable(value = "donation-cache")
public DonationsResponse getCachedDonationAmount(Long serverId) {
return fetchCurrentDonationAmount(serverId);
}
public DonationsResponse fetchCurrentDonationAmount(Long serverId) {
try {
Long fetchSize = configService.getLongValueOrConfigDefault(DEBRA_DONATION_API_FETCH_SIZE_KEY, serverId);
Request request = new Request.Builder()
.url(String.format(debraProperties.getDonationAPIUrl(), fetchSize))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
if(!response.isSuccessful()) {
if (log.isDebugEnabled()) {
log.error("Failed to retrieve donation response. Response had code {} with body {}.",
response.code(), response.body());
}
throw new DonationAmountNotFoundException();
}
Gson gson = getGson();
return gson.fromJson(response.body().string(), DonationsResponse.class);
} catch (Exception exception) {
throw new AbstractoRunTimeException(exception);
}
return gson.fromJson(response.body().string(), DonationsResponse.class);
}
private Gson getGson() {
@@ -130,7 +168,7 @@ public class DonationService {
.create();
}
private DonationsModel getDonationInfoModel(Long serverId) throws IOException {
private DonationsModel getDonationInfoModel(Long serverId) {
return donationConverter.convertDonationResponse(fetchCurrentDonationAmount(serverId));
}
@@ -148,4 +186,26 @@ public class DonationService {
firstMessage.addAll(secondMessage);
return FutureUtils.toSingleFutureGeneric(firstMessage);
}
public CompletableFuture<Void> sendDebraInfoButtonMessage(GuildMessageChannel guildMessageChannel) {
String buttonId = componentService.generateComponentId();
DebraInfoModel model = DebraInfoModel
.builder()
.buttonId(buttonId)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(DEBRA_INFO_BUTTON_MESSAGE_TEMPLATE_KEY, model, guildMessageChannel.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, guildMessageChannel)).thenAccept(unused -> {
self.persistButtonPayload(guildMessageChannel, buttonId);
});
}
@Transactional
public void persistButtonPayload(GuildMessageChannel guildMessageChannel, String buttonId) {
DebraInfoButtonPayload payload = DebraInfoButtonPayload
.builder()
.build();
AServer server = serverManagementService.loadServer(guildMessageChannel.getGuild().getIdLong());
componentPayloadService.createButtonPayload(buttonId, payload, DEBRA_INFO_BUTTON_ORIGIN, server);
}
}

View File

@@ -5,7 +5,7 @@ abstracto.postTargets.debraDonationNotification.name=debraDonationNotification
abstracto.postTargets.debraDonationNotification2.name=debraDonationNotification2
sissi.debra.websocketURL=ws://spenden.baba.fm:8765/
sissi.debra.donationAPIUrl=https://www.altruja.de/api/page/discord-fuer-debra-2022?details=1&num=%s&ort=0
sissi.debra.donationAPIUrl=https://www.altruja.de/api/page/discord-gg-austria-fuer-debra-2023?details=1&num=%s&ort=0
abstracto.systemConfigs.debraDonationNotificationDelayMillis.name=debraDonationNotificationDelayMillis
abstracto.systemConfigs.debraDonationNotificationDelayMillis.longValue=60000

View File

@@ -0,0 +1,24 @@
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="
http://www.ehcache.org/v3
http://www.ehcache.org/schema/ehcache-core-3.8-1.xsd">
<cache uses-template="default" alias="donation-cache">
<expiry>
<ttl unit="seconds">120</ttl>
</expiry>
<resources>
<heap unit="entries">5</heap>
</resources>
</cache>
<cache-template name="default">
<expiry>
<ttl unit="seconds">600</ttl>
</expiry>
<resources>
<heap>50</heap>
</resources>
</cache-template>
</config>

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

View File

@@ -0,0 +1,20 @@
<?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="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="debraFeature" value="(SELECT id FROM feature WHERE key = 'debra')"/>
<changeSet author="Sheldan" id="debraInfoButton-command">
<insert tableName="command">
<column name="name" value="debraInfoButton"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${debraFeature}"/>
</insert>
</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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -7,4 +7,5 @@
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.3.6/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.21/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -16,7 +16,6 @@ import org.springframework.stereotype.Component;
@PersistJobDataAfterExecution
public class MeetupCleanupJob extends QuartzJobBean {
@Autowired
private MeetupServiceBean meetupServiceBean;

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,6 +14,7 @@
<module>quotes</module>
<module>meetup</module>
<module>debra</module>
<module>rss-news</module>
</modules>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.4.16</version>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,59 @@
<?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>sissi-modules</artifactId>
<groupId>dev.sheldan.sissi.application</groupId>
<version>1.4.23</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.sissi.application.module</groupId>
<artifactId>rss-news</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>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${abstracto.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-arq</artifactId>
<version>${apache-jena.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-core</artifactId>
<version>${apache-jena.version}</version>
</dependency>
<dependency>
<groupId>com.apptasticsoftware</groupId>
<artifactId>rssreader</artifactId>
</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,95 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
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 CreateNewsCategory extends AbstractConditionableCommand {
private static final String CATEGORY_KEY_NAME_PARAMETER = "categoryName";
private static final String CREATE_SERVER_NEWS_CATEGORY_RESPONSE_TEMPLATE_KEY = "createNewsCategory_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String categoryName = slashCommandParameterService.getCommandOption(CATEGORY_KEY_NAME_PARAMETER, event, String.class);
AServer server = serverManagementService.loadOrCreate(event.getGuild().getIdLong());
newsCategoryServiceBean.createCategory(categoryName, server);
return interactionService.replyEmbed(CREATE_SERVER_NEWS_CATEGORY_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter nameParameter = Parameter
.builder()
.templated(true)
.name(CATEGORY_KEY_NAME_PARAMETER)
.type(String.class)
.build();
List<Parameter> parameters = Arrays.asList(nameParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("category")
.commandName("create")
.build();
return CommandConfiguration.builder()
.name("createNewsCategory")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,122 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryChannelMappingServiceBean;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class CreateNewsCategoryChannelMapping extends AbstractConditionableCommand {
private static final String CATEGORY_KEY_NAME_PARAMETER = "categoryName";
private static final String CHANNEL_PARAMETER = "channel";
private static final String CREATE_NEWS_CATEGORY_CHANNEL_MAPPING_RESPONSE_TEMPLATE_KEY = "createNewsCategoryChannelMapping_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
@Autowired
private NewsCategoryChannelMappingServiceBean newsCategoryMappingServiceBean;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String categoryName = slashCommandParameterService.getCommandOption(CATEGORY_KEY_NAME_PARAMETER, event, String.class);
GuildChannel channel = slashCommandParameterService.getCommandOption(CHANNEL_PARAMETER, event, TextChannel.class, GuildChannel.class);
newsCategoryMappingServiceBean.createNewsCategoryChannelMapping(categoryName, channel);
return interactionService.replyEmbed(CREATE_NEWS_CATEGORY_CHANNEL_MAPPING_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public List<String> performAutoComplete(CommandAutoCompleteInteractionEvent event) {
if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), CATEGORY_KEY_NAME_PARAMETER)) {
return newsCategoryServiceBean.getNamesOfNewsCategoriesStartingWith(event.getFocusedOption().getValue(), event.getGuild());
} else {
return new ArrayList<>();
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter categoryParameter = Parameter
.builder()
.templated(true)
.name(CATEGORY_KEY_NAME_PARAMETER)
.supportsAutoComplete(true)
.type(String.class)
.build();
Parameter channelParameter = Parameter
.builder()
.name(CHANNEL_PARAMETER)
.type(TextChannel.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(categoryParameter, channelParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("categorychannelmapping")
.commandName("create")
.build();
return CommandConfiguration.builder()
.name("createNewsCategoryChannelMapping")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.parameters(parameters)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,152 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import dev.sheldan.sissi.module.rssnews.service.NewsFeedSourceCategoryServiceBean;
import dev.sheldan.sissi.module.rssnews.service.NewsFeedSourceCategorySubscriptionServiceBean;
import dev.sheldan.sissi.module.rssnews.service.management.NewsFeedSourceManagementServiceBean;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class CreateNewsCategorySubscription extends AbstractConditionableCommand {
private static final String CATEGORY_KEY_NAME_PARAMETER = "categoryName";
private static final String SOURCE_CATEGORY_PARAMETER = "sourceCategory";
private static final String NEWS_FEED_SOURCE_KEY_PARAMETER = "newsFeed";
private static final String CREATE_NEWS_CATEGORY_SUBSCRIPTION_RESPONSE_TEMPLATE_KEY = "createNewsCategorySubscription_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
@Autowired
private NewsFeedSourceCategorySubscriptionServiceBean newsFeedSourceCategorySubscriptionServiceBean;
@Autowired
private NewsFeedSourceCategoryServiceBean newsFeedSourceCategoryServiceBean;
@Autowired
private NewsFeedSourceManagementServiceBean newsFeedSourceManagementServiceBean;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String categoryName = slashCommandParameterService.getCommandOption(CATEGORY_KEY_NAME_PARAMETER, event, String.class);
String sourceCategoryName = slashCommandParameterService.getCommandOption(SOURCE_CATEGORY_PARAMETER, event, String.class);
String newsFeedSourceName = slashCommandParameterService.getCommandOption(NEWS_FEED_SOURCE_KEY_PARAMETER, event, String.class);
Guild guild = event.getGuild();
newsFeedSourceCategorySubscriptionServiceBean.createNewsFeedSourceCategorySubscription(categoryName, sourceCategoryName, newsFeedSourceName, guild);
return interactionService.replyEmbed(CREATE_NEWS_CATEGORY_SUBSCRIPTION_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public List<String> performAutoComplete(CommandAutoCompleteInteractionEvent event) {
if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), CATEGORY_KEY_NAME_PARAMETER)) {
return newsCategoryServiceBean.getNamesOfNewsCategoriesStartingWith(event.getFocusedOption().getValue(), event.getGuild());
} if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), SOURCE_CATEGORY_PARAMETER)) {
String newsFeedSourceName = slashCommandParameterService.getCommandOption(NEWS_FEED_SOURCE_KEY_PARAMETER, event, String.class);
Optional<NewsFeedSource> newsFeedSourceOptional = newsFeedSourceManagementServiceBean.getNewsFeedSourceWithNameOptional(newsFeedSourceName);
if(newsFeedSourceOptional.isEmpty()) {
return new ArrayList<>();
}
return newsFeedSourceCategoryServiceBean.getNamesOfNewsSourceCategoriesInNewsFeedStartingWith(event.getFocusedOption().getValue(), newsFeedSourceOptional.get());
} if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), NEWS_FEED_SOURCE_KEY_PARAMETER)) {
return newsFeedSourceManagementServiceBean.getNewsFeedSourceNamesStartingWithName(event.getFocusedOption().getValue());
} else {
return new ArrayList<>();
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter categoryParameter = Parameter
.builder()
.templated(true)
.name(CATEGORY_KEY_NAME_PARAMETER)
.supportsAutoComplete(true)
.type(String.class)
.build();
Parameter sourceCategoryParameter = Parameter
.builder()
.name(SOURCE_CATEGORY_PARAMETER)
.type(String.class)
.supportsAutoComplete(true)
.templated(true)
.build();
Parameter newsFeedKeyParameter = Parameter
.builder()
.name(NEWS_FEED_SOURCE_KEY_PARAMETER)
.type(String.class)
.supportsAutoComplete(true)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(categoryParameter, newsFeedKeyParameter, sourceCategoryParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("categorysubscription")
.commandName("create")
.build();
return CommandConfiguration.builder()
.name("createNewsCategorySubscription")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.parameters(parameters)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,95 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
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 DeleteNewsCategory extends AbstractConditionableCommand {
private static final String CATEGORY_KEY_NAME_PARAMETER = "categoryName";
private static final String DELETE_NEWS_CATEGORY_RESPONSE_TEMPLATE_KEY = "deleteNewsCategory_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String categoryName = slashCommandParameterService.getCommandOption(CATEGORY_KEY_NAME_PARAMETER, event, String.class);
AServer server = serverManagementService.loadOrCreate(event.getGuild().getIdLong());
newsCategoryServiceBean.deleteNewsCategoryByName(categoryName, server);
return interactionService.replyEmbed(DELETE_NEWS_CATEGORY_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter nameParameter = Parameter
.builder()
.templated(true)
.name(CATEGORY_KEY_NAME_PARAMETER)
.type(String.class)
.build();
List<Parameter> parameters = Arrays.asList(nameParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("category")
.commandName("delete")
.build();
return CommandConfiguration.builder()
.name("deleteNewsCategory")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,122 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryChannelMappingServiceBean;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class DeleteNewsCategoryChannelMapping extends AbstractConditionableCommand {
private static final String CATEGORY_KEY_NAME_PARAMETER = "categoryName";
private static final String CHANNEL_PARAMETER = "channel";
private static final String DELETE_NEWS_CATEGORY_CHANNEL_MAPPING_RESPONSE_TEMPLATE_KEY = "deleteNewsCategoryChannelMapping_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
@Autowired
private NewsCategoryChannelMappingServiceBean newsCategoryMappingServiceBean;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String categoryName = slashCommandParameterService.getCommandOption(CATEGORY_KEY_NAME_PARAMETER, event, String.class);
GuildChannel channel = slashCommandParameterService.getCommandOption(CHANNEL_PARAMETER, event, TextChannel.class, GuildChannel.class);
newsCategoryMappingServiceBean.deleteNewsCategoryChannelMapping(categoryName, channel);
return interactionService.replyEmbed(DELETE_NEWS_CATEGORY_CHANNEL_MAPPING_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public List<String> performAutoComplete(CommandAutoCompleteInteractionEvent event) {
if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), CATEGORY_KEY_NAME_PARAMETER)) {
return newsCategoryServiceBean.getNamesOfNewsCategoriesStartingWith(event.getFocusedOption().getValue(), event.getGuild());
} else {
return new ArrayList<>();
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter categoryParameter = Parameter
.builder()
.templated(true)
.name(CATEGORY_KEY_NAME_PARAMETER)
.supportsAutoComplete(true)
.type(String.class)
.build();
Parameter channelParameter = Parameter
.builder()
.name(CHANNEL_PARAMETER)
.type(TextChannel.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(categoryParameter, channelParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("categorychannelmapping")
.commandName("delete")
.build();
return CommandConfiguration.builder()
.name("deleteNewsCategoryChannelMapping")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.parameters(parameters)
.slashCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,152 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import dev.sheldan.sissi.module.rssnews.service.NewsFeedSourceCategoryServiceBean;
import dev.sheldan.sissi.module.rssnews.service.NewsFeedSourceCategorySubscriptionServiceBean;
import dev.sheldan.sissi.module.rssnews.service.management.NewsFeedSourceManagementServiceBean;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class DeleteNewsCategorySubscription extends AbstractConditionableCommand {
private static final String CATEGORY_KEY_NAME_PARAMETER = "categoryName";
private static final String SOURCE_CATEGORY_PARAMETER = "sourceCategory";
private static final String NEWS_FEED_SOURCE_KEY_PARAMETER = "newsFeed";
private static final String CREATE_NEWS_CATEGORY_SUBSCRIPTION_RESPONSE_TEMPLATE_KEY = "deleteNewsCategorySubscription_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
@Autowired
private NewsFeedSourceCategorySubscriptionServiceBean newsFeedSourceCategorySubscriptionServiceBean;
@Autowired
private NewsFeedSourceCategoryServiceBean newsFeedSourceCategoryServiceBean;
@Autowired
private NewsFeedSourceManagementServiceBean newsFeedSourceManagementServiceBean;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String categoryName = slashCommandParameterService.getCommandOption(CATEGORY_KEY_NAME_PARAMETER, event, String.class);
String sourceCategoryName = slashCommandParameterService.getCommandOption(SOURCE_CATEGORY_PARAMETER, event, String.class);
String newsFeedSourceName = slashCommandParameterService.getCommandOption(NEWS_FEED_SOURCE_KEY_PARAMETER, event, String.class);
Guild guild = event.getGuild();
newsFeedSourceCategorySubscriptionServiceBean.deleteNewsFeedSourceCategorySubscription(categoryName, sourceCategoryName, newsFeedSourceName, guild);
return interactionService.replyEmbed(CREATE_NEWS_CATEGORY_SUBSCRIPTION_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public List<String> performAutoComplete(CommandAutoCompleteInteractionEvent event) {
if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), CATEGORY_KEY_NAME_PARAMETER)) {
return newsCategoryServiceBean.getNamesOfNewsCategoriesStartingWith(event.getFocusedOption().getValue(), event.getGuild());
} if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), SOURCE_CATEGORY_PARAMETER)) {
String newsFeedSourceName = slashCommandParameterService.getCommandOption(NEWS_FEED_SOURCE_KEY_PARAMETER, event, String.class);
Optional<NewsFeedSource> newsFeedSourceOptional = newsFeedSourceManagementServiceBean.getNewsFeedSourceWithNameOptional(newsFeedSourceName);
if(newsFeedSourceOptional.isEmpty()) {
return new ArrayList<>();
}
return newsFeedSourceCategoryServiceBean.getNamesOfNewsSourceCategoriesInNewsFeedStartingWith(event.getFocusedOption().getValue(), newsFeedSourceOptional.get());
} if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), NEWS_FEED_SOURCE_KEY_PARAMETER)) {
return newsFeedSourceManagementServiceBean.getNewsFeedSourceNamesStartingWithName(event.getFocusedOption().getValue());
} else {
return new ArrayList<>();
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter categoryParameter = Parameter
.builder()
.templated(true)
.name(CATEGORY_KEY_NAME_PARAMETER)
.supportsAutoComplete(true)
.type(String.class)
.build();
Parameter sourceCategoryParameter = Parameter
.builder()
.name(SOURCE_CATEGORY_PARAMETER)
.type(String.class)
.supportsAutoComplete(true)
.templated(true)
.build();
Parameter newsFeedKeyParameter = Parameter
.builder()
.name(NEWS_FEED_SOURCE_KEY_PARAMETER)
.type(String.class)
.supportsAutoComplete(true)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(categoryParameter, newsFeedKeyParameter, sourceCategoryParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("categorysubscription")
.commandName("delete")
.build();
return CommandConfiguration.builder()
.name("deleteNewsCategorySubscription")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.parameters(parameters)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,78 @@
package dev.sheldan.sissi.module.rssnews.commands;
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.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RssNewsSlashCommandNames;
import dev.sheldan.sissi.module.rssnews.model.template.NewsCategoryInfo;
import dev.sheldan.sissi.module.rssnews.model.template.ShowNewsCategoriesResponse;
import dev.sheldan.sissi.module.rssnews.service.NewsCategoryServiceBean;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class ShowNewsCategories extends AbstractConditionableCommand {
private static final String SHOW_NEWS_CATEGORIES_TEMPLATE_KEY = "showNewsCategories_response";
@Autowired
private NewsCategoryServiceBean newsCategoryServiceBean;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
List<NewsCategoryInfo> categoryInfos = newsCategoryServiceBean.getCategoryInfos(event.getGuild());
ShowNewsCategoriesResponse responseModel = ShowNewsCategoriesResponse
.builder()
.newsCategories(categoryInfos)
.build();
return interactionService.replyEmbed(SHOW_NEWS_CATEGORIES_TEMPLATE_KEY, responseModel, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(RssNewsSlashCommandNames.RSS_NEWS)
.groupName("category")
.commandName("show")
.build();
return CommandConfiguration.builder()
.name("showNewsCategories")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.sissi.module.rssnews.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.sissi.module.rssnews.orf.config.OrfNewsFeatureConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class RSSNewsFeatureConfig implements FeatureConfig {
@Autowired
private OrfNewsFeatureConfig orfNewsFeatureConfig;
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.RSS_NEWS;
}
@Override
public List<FeatureConfig> getDependantFeatures() {
return Arrays.asList(orfNewsFeatureConfig);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.sissi.module.rssnews.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum RssNewsFeatureDefinition implements FeatureDefinition {
RSS_NEWS("rssNews"), ORF_NEWS("orfNews");
private String key;
RssNewsFeatureDefinition(String key) {
this.key = key;
}
}

View File

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

View File

@@ -0,0 +1,5 @@
package dev.sheldan.sissi.module.rssnews.config;
public class RssNewsSlashCommandNames {
public static final String RSS_NEWS = "rssnews";
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsCategoryAlreadyExistsException extends AbstractoTemplatableException {
public NewsCategoryAlreadyExistsException() {
super("News Category already exists.");
}
@Override
public String getTemplateName() {
return "news_category_already_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsCategoryChannelMappingAlreadyExistsException extends AbstractoTemplatableException {
public NewsCategoryChannelMappingAlreadyExistsException() {
super("News Category channel mapping already exists.");
}
@Override
public String getTemplateName() {
return "news_category_channel_mapping_already_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsCategoryChannelMappingNotFoundException extends AbstractoTemplatableException {
public NewsCategoryChannelMappingNotFoundException() {
super("News Category channel mapping not found.");
}
@Override
public String getTemplateName() {
return "news_category_channel_mapping_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsCategoryNotFoundException extends AbstractoTemplatableException {
public NewsCategoryNotFoundException() {
super("News Category not found.");
}
@Override
public String getTemplateName() {
return "news_category_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsFeedSourceCategoryNotFoundException extends AbstractoTemplatableException {
public NewsFeedSourceCategoryNotFoundException() {
super("News feed source category not found.");
}
@Override
public String getTemplateName() {
return "news_feed_source_category_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsFeedSourceCategorySubscriptionAlreadyExistsException extends AbstractoTemplatableException {
public NewsFeedSourceCategorySubscriptionAlreadyExistsException() {
super("News feed source category subscription already exists.");
}
@Override
public String getTemplateName() {
return "news_feed_source_category_subscription_already_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsFeedSourceCategorySubscriptionNotFoundException extends AbstractoTemplatableException {
public NewsFeedSourceCategorySubscriptionNotFoundException() {
super("News feed source category subscription not found.");
}
@Override
public String getTemplateName() {
return "news_feed_source_category_subscription_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.sissi.module.rssnews.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NewsFeedSourceNotFoundException extends AbstractoTemplatableException {
public NewsFeedSourceNotFoundException() {
super("News feed source not found.");
}
@Override
public String getTemplateName() {
return "news_feed_source_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.sissi.module.rssnews.model.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_category")
@Getter
@Setter
@EqualsAndHashCode
public class NewsCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@ManyToMany
@JoinTable(
name = "news_category_in_news_feed_source_category",
joinColumns = @JoinColumn(name = "server_category_id"),
inverseJoinColumns = @JoinColumn(name = "source_category_id"))
private List<NewsFeedSourceCategory> sourceCategories;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "newsCategory")
@Builder.Default
private List<NewsCategoryChannelMapping> mappings = new ArrayList<>();
@Column(name = "key", nullable = false)
private String key;
@Column(name = "enabled", nullable = false)
private Boolean enabled;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,54 @@
package dev.sheldan.sissi.module.rssnews.model.database;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_category_channel_mapping")
@Getter
@Setter
@EqualsAndHashCode
public class NewsCategoryChannelMapping {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "channel_id", referencedColumnName = "id", nullable = false)
private AChannel channel;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "category_id", referencedColumnName = "id", nullable = false)
private NewsCategory newsCategory;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "mapping")
@Builder.Default
private List<NewsPost> posts = new ArrayList<>();
@Column(name = "enabled", nullable = false)
private Boolean enabled;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,34 @@
package dev.sheldan.sissi.module.rssnews.model.database;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_feed_record")
@Getter
@Setter
@EqualsAndHashCode
public class NewsFeedRecord {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_id", referencedColumnName = "id")
private NewsFeedSource source;
@Column(name = "url")
private String url;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.sissi.module.rssnews.model.database;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_feed_source")
@Getter
@Setter
@EqualsAndHashCode
public class NewsFeedSource {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "url", nullable = false)
private String url;
@Column(name = "name", nullable = false)
private String name;
@Enumerated(EnumType.STRING)
@Column(name = "feed_type", nullable = false)
private NewsFeedSourceCategoryType type;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "source")
@Builder.Default
private List<NewsFeedSourceCategory> categories = new ArrayList<>();
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "source")
@Builder.Default
private List<NewsFeedRecord> records = new ArrayList<>();
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,41 @@
package dev.sheldan.sissi.module.rssnews.model.database;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_feed_source_category")
@Getter
@Setter
@EqualsAndHashCode
public class NewsFeedSourceCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_id", referencedColumnName = "id")
private NewsFeedSource source;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
@Getter
@ManyToMany(mappedBy = "sourceCategories")
private List<NewsCategory> categories;
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.sissi.module.rssnews.model.database;
public enum NewsFeedSourceCategoryType {
RFD, RSS
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.sissi.module.rssnews.model.database;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "news_post")
@Getter
@Setter
@EqualsAndHashCode
public class NewsPost {
@Id
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "url")
private String url;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "channel_id", referencedColumnName = "id", nullable = false)
private AChannel postChannel;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "news_category_channel_mapping_id", referencedColumnName = "id", nullable = false)
private NewsCategoryChannelMapping mapping;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -0,0 +1,54 @@
package dev.sheldan.sissi.module.rssnews.model.feed;
import com.apptasticsoftware.rssreader.Item;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSourceCategoryType;
import dev.sheldan.sissi.module.rssnews.model.feed.rfd.RFDItem;
import lombok.*;
@Getter
@Builder
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class NewsResponseItem {
private String title;
private String link;
private String mainCategory;
private String date;
private String secondCategory;
private String description;
private String imageURL;
private NewsFeedSourceCategoryType type;
private NewsFeedSource newsFeedSource;
public static NewsResponseItem fromRFDItem(RFDItem item) {
return NewsResponseItem
.builder()
.title(item.getTitle())
.date(item.getDate())
.description(item.getDescription())
.mainCategory(item.getSubject())
.secondCategory(item.getOewaCategory())
.type(NewsFeedSourceCategoryType.RFD)
.link(item.getLink())
.build();
}
public static NewsResponseItem fromRSSItem(Item item) {
String imageUrl = null;
if(item.getEnclosure().isPresent()) {
imageUrl = item.getEnclosure().get().getUrl();
}
return NewsResponseItem
.builder()
.title(item.getTitle().orElse(null))
.date(item.getPubDate().orElse(null))
.description(item.getDescription().orElse(null))
.mainCategory(item.getCategory().orElse(null))
.type(NewsFeedSourceCategoryType.RSS)
.link(item.getLink().orElse(null))
.imageURL(imageUrl)
.build();
}
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.sissi.module.rssnews.model.feed.rfd;
import lombok.*;
@Getter
@Builder
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RFDItem {
private String title;
private String link;
private String subject;
private String date;
private String oewaCategory;
private String description;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.sissi.module.rssnews.model.template;
import dev.sheldan.abstracto.core.models.template.display.ChannelDisplay;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class NewsCategoryChannelMappingInfo {
private ChannelDisplay channel;
private Boolean enabled;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.rssnews.model.template;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class NewsCategoryInfo {
private String name;
private List<NewsCategorySubscriptionInfo> subscriptions;
private List<NewsCategoryChannelMappingInfo> mappings;
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.sissi.module.rssnews.model.template;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class NewsCategorySubscriptionInfo {
private String newsFeedName;
private List<String> newsFeedCategories;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.sissi.module.rssnews.model.template;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Builder
@Getter
public class ShowNewsCategoriesResponse {
private List<NewsCategoryInfo> newsCategories;
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.sissi.module.rssnews.orf.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.sissi.module.rssnews.config.RSSNewsFeatureConfig;
import dev.sheldan.sissi.module.rssnews.config.RssNewsFeatureDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class OrfNewsFeatureConfig implements FeatureConfig {
@Autowired
private RSSNewsFeatureConfig rssNewsFeatureConfig;
@Override
public FeatureDefinition getFeature() {
return RssNewsFeatureDefinition.ORF_NEWS;
}
@Override
public List<FeatureConfig> getRequiredFeatures() {
return Arrays.asList(rssNewsFeatureConfig);
}
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.sissi.module.rssnews.orf.job;
import dev.sheldan.sissi.module.rssnews.orf.service.OrfNewsService;
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 NewsPostGatherJob extends QuartzJobBean {
@Autowired
private OrfNewsService orfNewsRFDService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
log.info("Executing news retrieval job.");
orfNewsRFDService.checkNewsPosts();
} catch (Exception e) {
log.error("News retrieval job failed.", e);
}
}
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.sissi.module.rssnews.orf.model;
import dev.sheldan.sissi.module.rssnews.model.feed.NewsResponseItem;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class OrfNewsMessagePost {
private String title;
private String url;
private String category;
private String description;
private String imageURL;
public static OrfNewsMessagePost fromNewsResponseItem(NewsResponseItem newsResponseItem) {
return OrfNewsMessagePost
.builder()
.category(newsResponseItem.getMainCategory())
.description(newsResponseItem.getDescription())
.imageURL(newsResponseItem.getImageURL())
.title(newsResponseItem.getTitle())
.url(newsResponseItem.getLink())
.build();
}
}

View File

@@ -0,0 +1,263 @@
package dev.sheldan.sissi.module.rssnews.orf.service;
import com.apptasticsoftware.rssreader.Item;
import com.apptasticsoftware.rssreader.RssReader;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
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.rssnews.model.database.*;
import dev.sheldan.sissi.module.rssnews.orf.config.OrfNewsFeatureConfig;
import dev.sheldan.sissi.module.rssnews.model.feed.NewsResponseItem;
import dev.sheldan.sissi.module.rssnews.model.feed.rfd.RFDItem;
import dev.sheldan.sissi.module.rssnews.orf.model.OrfNewsMessagePost;
import dev.sheldan.sissi.module.rssnews.service.management.*;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.jena.rdf.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Slf4j
public class OrfNewsService {
@Autowired
private OkHttpClient okHttpClient;
@Autowired
private NewsFeedSourceManagementServiceBean newsFeedSourceManagementBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private OrfNewsFeatureConfig orfNewsFeatureConfig;
@Autowired
private NewsFeedRecordManagementServiceBean newsFeedRecordManagementBean;
@Autowired
private NewsCategoryManagementServiceBean newsCategoryManagementBean;
@Autowired
private NewsFeedSourceCategoryManagementServiceBean newsFeedSourceCategoryManagementBean;
@Autowired
private NewsFeedRecordManagementServiceBean newsFeedRecordManagementServiceBean;
@Autowired
private NewsCategoryChannelMappingManagementServiceBean newsCategoryChannelMappingManagementServiceBean;
@Autowired
private NewsPostManagementServiceBean newsPostManagementServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
@Autowired
private OrfNewsService self;
private static final String ORF_NEWS_POST_TEMPLATE_KEY = "orf_news_post";
@Transactional
public void checkNewsPosts() {
List<AServer> servers = serverManagementService.getAllServers();
List<AServer> serversWithEnabledFeature = servers
.stream()
.filter(server -> featureFlagService.isFeatureEnabled(orfNewsFeatureConfig, server))
.toList();
if(serversWithEnabledFeature.isEmpty()) {
log.info("No servers have orf news enabled - skipping execution.");
return;
}
List<NewsFeedSource> sources = newsFeedSourceManagementBean.getAllSources();
List<NewsResponseItem> newsItems = new ArrayList<>();
sources.forEach(source -> newsItems.addAll(loadItemsFromNewsSource(source)));
Set<String> existingRecordUrls = newsFeedRecordManagementBean.getAllRecords()
.stream()
.map(NewsFeedRecord::getUrl)
.collect(Collectors.toSet());
List<NewsResponseItem> newItems = newsItems
.stream()
.filter(newsResponseItem -> !existingRecordUrls.contains(newsResponseItem.getLink()))
.toList();
List<NewsCategory> categoriesToCover = newsCategoryManagementBean.getNewsCategoriesOfServers(serversWithEnabledFeature);
categoriesToCover.forEach(category -> sendNewsItemsToNewsCategory(newItems, category));
createNewCategories(newsItems);
saveNewsRecords(newItems);
}
private void saveNewsRecords(List<NewsResponseItem> newItems) {
newItems.forEach(newsResponseItem -> newsFeedRecordManagementServiceBean.createRecord(newsResponseItem));
}
private void createNewCategories(List<NewsResponseItem> newsItems) {
Set<String> allExistingCategories = newsFeedSourceCategoryManagementBean.getAllCategories()
.stream()
.map(NewsFeedSourceCategory::getName)
.collect(Collectors.toSet());
Set<String> incomingCategories = newsItems
.stream()
.map(NewsResponseItem::getMainCategory)
.collect(Collectors.toSet());
HashSet<Object> seenCategories = new HashSet<>();
newsItems.removeIf(e -> !seenCategories.add(e.getMainCategory()));
Map<String, NewsResponseItem> categoryToNewsItem = newsItems
.stream()
.collect(Collectors.toMap(NewsResponseItem::getMainCategory, Function.identity()));
incomingCategories.removeAll(allExistingCategories);
incomingCategories.removeIf(Objects::isNull);
if(!incomingCategories.isEmpty()) {
incomingCategories.forEach(categoryName ->
newsFeedSourceCategoryManagementBean.createCategory(categoryName, categoryToNewsItem.get(categoryName).getNewsFeedSource()));
}
}
private void sendNewsItemsToNewsCategory(List<NewsResponseItem> newsItems, NewsCategory category) {
if(!category.getEnabled()) {
return;
}
Set<String> categoriesToSend = category
.getSourceCategories()
.stream()
.map(NewsFeedSourceCategory::getName)
.collect(Collectors.toSet());
List<NewsResponseItem> itemsToSend = newsItems
.stream()
.filter(newsResponseItem -> categoriesToSend.contains(newsResponseItem.getMainCategory()))
.toList();
category.getMappings().forEach(newsCategoryChannelMapping -> sendNewsItemToChannel(itemsToSend, newsCategoryChannelMapping));
}
private void sendNewsItemToChannel(List<NewsResponseItem> items, NewsCategoryChannelMapping mapping) {
items.forEach(item -> {
Long channelId = mapping.getChannel().getId();
Long serverId = mapping.getServer().getId();
Long mappingId = mapping.getId();
OrfNewsMessagePost orfNewsMessagePostModel = OrfNewsMessagePost.fromNewsResponseItem(item);
MessageToSend messageToSend = templateService.renderEmbedTemplate(ORF_NEWS_POST_TEMPLATE_KEY, orfNewsMessagePostModel, serverId);
CompletableFutureList<Message> futureList = new CompletableFutureList<>(channelService.sendMessageEmbedToSendToAChannel(messageToSend, mapping.getChannel()));
futureList.getMainFuture().thenAccept(unused -> {
log.info("Sent news post {} to channel {}.", item.getTitle(), channelId);
Long messageId = futureList.getObjects().get(0).getIdLong();
self.persistNewsPost(messageId, mappingId, item);
}).exceptionally(throwable -> {
log.warn("Failed to send news post {} to channel {}.", item.getTitle(), channelId);
return null;
});
});
}
@Transactional
public void persistNewsPost(Long messageId, Long mappingId, NewsResponseItem item) {
NewsCategoryChannelMapping channelMapping = newsCategoryChannelMappingManagementServiceBean.getChannelMappingById(mappingId);
newsPostManagementServiceBean.createNewsPost(messageId, item, channelMapping);
}
public List<NewsResponseItem> loadItemsFromNewsSource(NewsFeedSource source) {
Request request = new Request.Builder()
.url(source.getUrl())
.get()
.build();
try {
Response response = okHttpClient.newCall(request).execute();
return switch (source.getType()) {
case RFD -> loadNewsSourceRFD(response, source);
case RSS -> loadNewsSourceRSS(response, source);
};
} catch (IOException ex) {
throw new AbstractoRunTimeException(String.format("Failed to gather news from %s", source.getName()), ex);
}
}
private List<NewsResponseItem> loadNewsSourceRSS(Response response, NewsFeedSource source) {
RssReader reader = new RssReader();
try (response) {
InputStream is = response.body().byteStream();
Stream<Item> rssFeed = reader.read(is);
return rssFeed.map(NewsResponseItem::fromRSSItem)
.peek(newsResponseItem -> newsResponseItem.setNewsFeedSource(source))
.toList();
}
}
private List<NewsResponseItem> loadNewsSourceRFD(Response response, NewsFeedSource source) throws IOException {
Map<String, RFDItem> items = new HashMap<>();
final Model model = ModelFactory.createDefaultModel();
model.read(new ByteArrayInputStream(response.body().bytes()), null);
StmtIterator statements = model.listStatements();
while(statements.hasNext()) {
Statement x = statements.nextStatement();
String article = x.getSubject().toString();
RFDItem item;
if(!items.containsKey(article)) {
item = new RFDItem();
} else {
item = items.get(article);
}
String predicate = x.getPredicate().toString();
String objectValue = x.getObject().toString();
switch (predicate) {
case "http://rss.orf.at/1.0/oewaCategory":
item.setOewaCategory(objectValue);
break;
case "http://purl.org/rss/1.0/link":
item.setLink(objectValue);
break;
case "http://purl.org/dc/elements/1.1/subject":
item.setSubject(objectValue);
break;
case "http://purl.org/dc/elements/1.1/date":
item.setDate(objectValue);
break;
case "http://purl.org/rss/1.0/description":
item.setDescription(objectValue);
break;
case "http://purl.org/rss/1.0/title":
item.setTitle(objectValue);
break;
}
items.putIfAbsent(article, item);
}
Set<String> uselessEntries = new HashSet<>();
items.forEach((s, rfdItem) -> {
if(rfdItem.getLink() == null) {
uselessEntries.add(s);
}
});
uselessEntries.forEach(items::remove);
return items
.values()
.stream()
.map(NewsResponseItem::fromRFDItem)
.peek(newsResponseItem -> newsResponseItem.setNewsFeedSource(source))
.toList();
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.rssnews.repository;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategoryChannelMapping;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface NewsCategoryChannelMappingRepository extends JpaRepository<NewsCategoryChannelMapping, Long> {
boolean existsByChannelAndNewsCategory(AChannel channel, NewsCategory newsCategory);
void deleteByNewsCategory(NewsCategory newsCategory);
void deleteByNewsCategoryAndChannel(NewsCategory newsCategory, AChannel channel);
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.sissi.module.rssnews.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface NewsCategoryRepository extends JpaRepository<NewsCategory, Long> {
List<NewsCategory> getByServerIn(List<AServer> servers);
List<NewsCategory> getByServer(AServer server);
Optional<NewsCategory> findByKeyAndServer(String name, AServer server);
boolean existsByKeyAndServer(String name, AServer server);
List<NewsCategory> findByKeyStartsWithAndServer(String name, AServer server);
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.sissi.module.rssnews.repository;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface NewsFeedRecordRepository extends JpaRepository<NewsFeedRecord, Long> {
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.sissi.module.rssnews.repository;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSourceCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface NewsFeedSourceCategoryRepository extends JpaRepository<NewsFeedSourceCategory, Long> {
List<NewsFeedSourceCategory> findByNameStartsWith(String name);
List<NewsFeedSourceCategory> findByNameStartsWithAndSource(String name, NewsFeedSource newsFeedSource);
Optional<NewsFeedSourceCategory> findByNameAndSource(String name, NewsFeedSource newsFeedSource);
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.rssnews.repository;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface NewsFeedSourceRepository extends JpaRepository<NewsFeedSource, Long> {
Optional<NewsFeedSource> findByName(String name);
List<NewsFeedSource> findByNameStartsWith(String name);
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.sissi.module.rssnews.repository;
import dev.sheldan.sissi.module.rssnews.model.database.NewsPost;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface NewsPostRepository extends JpaRepository<NewsPost, Long> {
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.sissi.module.rssnews.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.sissi.module.rssnews.exception.NewsCategoryChannelMappingAlreadyExistsException;
import dev.sheldan.sissi.module.rssnews.exception.NewsCategoryChannelMappingNotFoundException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategoryChannelMapping;
import dev.sheldan.sissi.module.rssnews.service.management.NewsCategoryChannelMappingManagementServiceBean;
import dev.sheldan.sissi.module.rssnews.service.management.NewsCategoryManagementServiceBean;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NewsCategoryChannelMappingServiceBean {
@Autowired
private NewsCategoryManagementServiceBean newsCategoryManagementServiceBean;
@Autowired
private NewsCategoryChannelMappingManagementServiceBean newsCategoryChannelMappingManagementServiceBean;
@Autowired
private ChannelManagementService channelManagementService;
public NewsCategoryChannelMapping createNewsCategoryChannelMapping(String categoryName, GuildChannel channel) {
AChannel aChannel = channelManagementService.loadChannel(channel.getIdLong());
NewsCategory newsCategory = newsCategoryManagementServiceBean.findNewsCategoryByNameInServer(categoryName, aChannel.getServer());
if(newsCategoryChannelMappingManagementServiceBean.newsCategoryChannelMappingExists(newsCategory, aChannel)) {
throw new NewsCategoryChannelMappingAlreadyExistsException();
}
return newsCategoryChannelMappingManagementServiceBean.createNewsCategoryChannelMapping(newsCategory, aChannel);
}
public void deleteNewsCategoryChannelMapping(String categoryName, GuildChannel channel) {
AChannel aChannel = channelManagementService.loadChannel(channel.getIdLong());
NewsCategory newsCategory = newsCategoryManagementServiceBean.findNewsCategoryByNameInServer(categoryName, aChannel.getServer());
if(!newsCategoryChannelMappingManagementServiceBean.newsCategoryChannelMappingExists(newsCategory, aChannel)) {
throw new NewsCategoryChannelMappingNotFoundException();
}
newsCategoryChannelMappingManagementServiceBean.deleteNewsCategoryChannelMapping(newsCategory, aChannel);
}
}

View File

@@ -0,0 +1,103 @@
package dev.sheldan.sissi.module.rssnews.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.display.ChannelDisplay;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.ChannelUtils;
import dev.sheldan.sissi.module.rssnews.exception.NewsCategoryAlreadyExistsException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import dev.sheldan.sissi.module.rssnews.model.template.NewsCategoryChannelMappingInfo;
import dev.sheldan.sissi.module.rssnews.model.template.NewsCategoryInfo;
import dev.sheldan.sissi.module.rssnews.model.template.NewsCategorySubscriptionInfo;
import dev.sheldan.sissi.module.rssnews.service.management.NewsCategoryChannelMappingManagementServiceBean;
import dev.sheldan.sissi.module.rssnews.service.management.NewsCategoryManagementServiceBean;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class NewsCategoryServiceBean {
@Autowired
private NewsCategoryManagementServiceBean newsCategoryManagementServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private NewsCategoryChannelMappingManagementServiceBean newsCategoryChannelMappingManagementServiceBean;
public void createCategory(String name, AServer server) {
if(newsCategoryManagementServiceBean.newsCategoryExistsByNameInServer(name, server)) {
throw new NewsCategoryAlreadyExistsException();
}
newsCategoryManagementServiceBean.createCategory(name, server);
}
public void deleteNewsCategoryByName(String name, AServer server) {
NewsCategory newsCategory = newsCategoryManagementServiceBean.findNewsCategoryByNameInServer(name, server);
newsCategoryChannelMappingManagementServiceBean.deleteChannelMappingsOfNewsCategory(newsCategory);
newsCategoryManagementServiceBean.deleteNewsCategory(newsCategory);
}
public List<NewsCategory> findNewsCategoriesStartingWith(String name, Guild guild) {
AServer server = serverManagementService.loadServer(guild.getIdLong());
return newsCategoryManagementServiceBean.findNewsCategoriesStartingWith(name, server);
}
public List<String> getNamesOfNewsCategoriesStartingWith(String name, Guild guild) {
return findNewsCategoriesStartingWith(name, guild)
.stream()
.map(NewsCategory::getKey)
.toList();
}
public List<NewsCategoryInfo> getCategoryInfos(Guild guild) {
AServer server = serverManagementService.loadServer(guild);
List<NewsCategory> newsCategories = newsCategoryManagementServiceBean.getNewsCategoriesOfServers(server);
return newsCategories.stream().map(newsCategory -> {
List<NewsCategorySubscriptionInfo> subscriptionInfos = new ArrayList<>();
Map<String, List<String>> sourceCategories = new HashMap<>();
newsCategory
.getSourceCategories()
.forEach(sourceCategory -> {
if(!sourceCategories.containsKey(sourceCategory.getSource().getName())) {
sourceCategories.put(sourceCategory.getSource().getName(), new ArrayList<>());
}
sourceCategories.get(sourceCategory.getSource().getName()).add(sourceCategory.getName());
});
sourceCategories.forEach((sourceName, categoryList) ->
subscriptionInfos.add(NewsCategorySubscriptionInfo
.builder()
.newsFeedName(sourceName)
.newsFeedCategories(categoryList)
.build()));
List<NewsCategoryChannelMappingInfo> mappings = new ArrayList<>();
newsCategory.getMappings().forEach(newsCategoryChannelMapping -> {
ChannelDisplay channelDisplay = ChannelDisplay
.builder()
.channelMention(ChannelUtils.getAsMention(newsCategoryChannelMapping.getChannel().getId()))
.build();
mappings.add(NewsCategoryChannelMappingInfo
.builder()
.enabled(newsCategoryChannelMapping.getEnabled())
.channel(channelDisplay)
.build());
});
return NewsCategoryInfo
.builder()
.name(newsCategory.getKey())
.subscriptions(subscriptionInfos)
.mappings(mappings)
.build();
}).toList();
}
}

View File

@@ -0,0 +1,40 @@
package dev.sheldan.sissi.module.rssnews.service;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSourceCategory;
import dev.sheldan.sissi.module.rssnews.service.management.NewsFeedSourceCategoryManagementServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class NewsFeedSourceCategoryServiceBean {
@Autowired
private NewsFeedSourceCategoryManagementServiceBean newsFeedSourceCategoryManagementServiceBean;
public List<NewsFeedSourceCategory> findNewsFeedSourceCategoriesStartingWith(String name) {
return newsFeedSourceCategoryManagementServiceBean.findNewsFeedSourceCategoriesNameStartingWith(name);
}
public List<NewsFeedSourceCategory> getNewsSourceCategoriesInNewsFeedStartingWith(String name, NewsFeedSource newsFeedSource) {
return newsFeedSourceCategoryManagementServiceBean.findNewsFeedSourceCategoriesNameStartingWith(name, newsFeedSource);
}
public List<String> getNamesOfNewsCategoriesStartingWith(String name) {
return findNewsFeedSourceCategoriesStartingWith(name)
.stream()
.map(NewsFeedSourceCategory::getName)
.toList();
}
public List<String> getNamesOfNewsSourceCategoriesInNewsFeedStartingWith(String name, NewsFeedSource newsFeedSource) {
return getNewsSourceCategoriesInNewsFeedStartingWith(name, newsFeedSource)
.stream()
.map(NewsFeedSourceCategory::getName)
.toList();
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.sissi.module.rssnews.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.sissi.module.rssnews.exception.NewsFeedSourceCategorySubscriptionAlreadyExistsException;
import dev.sheldan.sissi.module.rssnews.exception.NewsFeedSourceCategorySubscriptionNotFoundException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSourceCategory;
import dev.sheldan.sissi.module.rssnews.service.management.NewsCategoryManagementServiceBean;
import dev.sheldan.sissi.module.rssnews.service.management.NewsFeedSourceCategoryManagementServiceBean;
import dev.sheldan.sissi.module.rssnews.service.management.NewsFeedSourceManagementServiceBean;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NewsFeedSourceCategorySubscriptionServiceBean {
@Autowired
private NewsCategoryManagementServiceBean newsCategoryManagementServiceBean;
@Autowired
private NewsFeedSourceCategoryManagementServiceBean newsFeedSourceCategoryManagementServiceBean;
@Autowired
private NewsFeedSourceManagementServiceBean newsFeedSourceManagementServiceBean;
@Autowired
private ServerManagementService serverManagementService;
public void createNewsFeedSourceCategorySubscription(String categoryName, String sourceCategoryName, String newsFeedSourceName, Guild guild) {
AServer server = serverManagementService.loadServer(guild.getIdLong());
NewsCategory newsCategory = newsCategoryManagementServiceBean.findNewsCategoryByNameInServer(categoryName, server);
NewsFeedSource newsFeedSource = newsFeedSourceManagementServiceBean.getNewsFeedSourceWithName(newsFeedSourceName);
NewsFeedSourceCategory sourceCategory = newsFeedSourceCategoryManagementServiceBean.findNewsFeedSourceCategoryByNameAndNewsFeedSource(sourceCategoryName, newsFeedSource);
if(newsCategory.getSourceCategories().contains(sourceCategory)) {
throw new NewsFeedSourceCategorySubscriptionAlreadyExistsException();
}
newsCategory.getSourceCategories().add(sourceCategory);
sourceCategory.getCategories().add(newsCategory);
}
public void deleteNewsFeedSourceCategorySubscription(String categoryName, String sourceCategoryName, String newsFeedSourceName, Guild guild) {
AServer server = serverManagementService.loadServer(guild.getIdLong());
NewsCategory newsCategory = newsCategoryManagementServiceBean.findNewsCategoryByNameInServer(categoryName, server);
NewsFeedSource newsFeedSource = newsFeedSourceManagementServiceBean.getNewsFeedSourceWithName(newsFeedSourceName);
NewsFeedSourceCategory sourceCategory = newsFeedSourceCategoryManagementServiceBean.findNewsFeedSourceCategoryByNameAndNewsFeedSource(sourceCategoryName, newsFeedSource);
if(!newsCategory.getSourceCategories().contains(sourceCategory)) {
throw new NewsFeedSourceCategorySubscriptionNotFoundException();
}
newsCategory.getSourceCategories().remove(sourceCategory);
sourceCategory.getCategories().remove(newsCategory);
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.sissi.module.rssnews.service;
import dev.sheldan.sissi.module.rssnews.service.management.NewsFeedSourceManagementServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NewsFeedSourceServiceBean {
@Autowired
private NewsFeedSourceManagementServiceBean newsFeedSourceManagementServiceBean;
}

View File

@@ -0,0 +1,48 @@
package dev.sheldan.sissi.module.rssnews.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.sissi.module.rssnews.exception.NewsCategoryChannelMappingNotFoundException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategoryChannelMapping;
import dev.sheldan.sissi.module.rssnews.repository.NewsCategoryChannelMappingRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NewsCategoryChannelMappingManagementServiceBean {
@Autowired
private NewsCategoryChannelMappingRepository repository;
public NewsCategoryChannelMapping createNewsCategoryChannelMapping(NewsCategory newsCategory, AChannel channel) {
NewsCategoryChannelMapping category = NewsCategoryChannelMapping
.builder()
.channel(channel)
.server(channel.getServer())
.newsCategory(newsCategory)
.enabled(true)
.build();
return repository.save(category);
}
public NewsCategoryChannelMapping getChannelMappingById(Long id) {
return repository.findById(id).orElseThrow(NewsCategoryChannelMappingNotFoundException::new);
}
public boolean newsCategoryChannelMappingExists(NewsCategory newsCategory, AChannel channel) {
return repository.existsByChannelAndNewsCategory(channel, newsCategory);
}
public void deleteChannelMappingsOfNewsCategory(NewsCategory newsCategory) {
repository.deleteByNewsCategory(newsCategory);
}
public void deleteNewsCategoryChannelMapping(NewsCategory newsCategory, AChannel channel) {
repository.deleteByNewsCategoryAndChannel(newsCategory, channel);
}
public void deleteNewsCategoryChannelMapping(NewsCategoryChannelMapping mapping) {
repository.delete(mapping);
}
}

View File

@@ -0,0 +1,57 @@
package dev.sheldan.sissi.module.rssnews.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.sissi.module.rssnews.exception.NewsCategoryNotFoundException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategory;
import dev.sheldan.sissi.module.rssnews.repository.NewsCategoryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class NewsCategoryManagementServiceBean {
@Autowired
private NewsCategoryRepository repository;
public List<NewsCategory> getNewsCategoriesOfServers(List<AServer> servers) {
return repository.getByServerIn(servers);
}
public List<NewsCategory> getNewsCategoriesOfServers(AServer server) {
return repository.getByServer(server);
}
public NewsCategory createCategory(String name, AServer server) {
NewsCategory category = NewsCategory
.builder()
.key(name)
.server(server)
.enabled(true)
.build();
return repository.save(category);
}
public Optional<NewsCategory> findNewsCategoryByNameInServerOptional(String name, AServer server) {
return repository.findByKeyAndServer(name, server);
}
public NewsCategory findNewsCategoryByNameInServer(String name, AServer server) {
return findNewsCategoryByNameInServerOptional(name, server).orElseThrow(NewsCategoryNotFoundException::new);
}
public boolean newsCategoryExistsByNameInServer(String name, AServer server) {
return repository.existsByKeyAndServer(name, server);
}
public void deleteNewsCategory(NewsCategory newsCategory) {
repository.delete(newsCategory);
}
public List<NewsCategory> findNewsCategoriesStartingWith(String name, AServer server) {
return repository.findByKeyStartsWithAndServer(name, server);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sheldan.sissi.module.rssnews.service.management;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedRecord;
import dev.sheldan.sissi.module.rssnews.model.feed.NewsResponseItem;
import dev.sheldan.sissi.module.rssnews.repository.NewsFeedRecordRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class NewsFeedRecordManagementServiceBean {
@Autowired
private NewsFeedRecordRepository repository;
public List<NewsFeedRecord> getAllRecords() {
return repository.findAll();
}
public NewsFeedRecord createRecord(NewsResponseItem newsResponseItem) {
NewsFeedRecord newsFeedRecord = NewsFeedRecord
.builder()
.source(newsResponseItem.getNewsFeedSource())
.url(newsResponseItem.getLink())
.build();
return repository.save(newsFeedRecord);
}
}

View File

@@ -0,0 +1,47 @@
package dev.sheldan.sissi.module.rssnews.service.management;
import dev.sheldan.sissi.module.rssnews.exception.NewsFeedSourceCategoryNotFoundException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSourceCategory;
import dev.sheldan.sissi.module.rssnews.repository.NewsFeedSourceCategoryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class NewsFeedSourceCategoryManagementServiceBean {
@Autowired
private NewsFeedSourceCategoryRepository repository;
public List<NewsFeedSourceCategory> getAllCategories(){
return repository.findAll();
}
public NewsFeedSourceCategory createCategory(String name, NewsFeedSource newsFeedSource) {
NewsFeedSourceCategory category = NewsFeedSourceCategory
.builder()
.source(newsFeedSource)
.name(name)
.build();
return repository.save(category);
}
public List<NewsFeedSourceCategory> findNewsFeedSourceCategoriesNameStartingWith(String name) {
return repository.findByNameStartsWith(name);
}
public List<NewsFeedSourceCategory> findNewsFeedSourceCategoriesNameStartingWith(String name, NewsFeedSource newsFeedSource) {
return repository.findByNameStartsWithAndSource(name, newsFeedSource);
}
public Optional<NewsFeedSourceCategory> findNewsFeedSourceCategoryByNameAndNewsFeedSourceOptional(String name, NewsFeedSource newsFeedSource) {
return repository.findByNameAndSource(name, newsFeedSource);
}
public NewsFeedSourceCategory findNewsFeedSourceCategoryByNameAndNewsFeedSource(String name, NewsFeedSource newsFeedSource) {
return findNewsFeedSourceCategoryByNameAndNewsFeedSourceOptional(name, newsFeedSource).orElseThrow(NewsFeedSourceCategoryNotFoundException::new);
}
}

View File

@@ -0,0 +1,41 @@
package dev.sheldan.sissi.module.rssnews.service.management;
import dev.sheldan.sissi.module.rssnews.exception.NewsFeedSourceNotFoundException;
import dev.sheldan.sissi.module.rssnews.model.database.NewsFeedSource;
import dev.sheldan.sissi.module.rssnews.repository.NewsFeedSourceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class NewsFeedSourceManagementServiceBean {
@Autowired
private NewsFeedSourceRepository newsFeedSourceRepository;
public List<NewsFeedSource> getAllSources() {
return newsFeedSourceRepository.findAll();
}
public Optional<NewsFeedSource> getNewsFeedSourceWithNameOptional(String name) {
return newsFeedSourceRepository.findByName(name);
}
public NewsFeedSource getNewsFeedSourceWithName(String name) {
return getNewsFeedSourceWithNameOptional(name).orElseThrow(NewsFeedSourceNotFoundException::new);
}
public List<NewsFeedSource> getNewsFeedSourcesStartingWithName(String name) {
return newsFeedSourceRepository.findByNameStartsWith(name);
}
public List<String> getNewsFeedSourceNamesStartingWithName(String name) {
return getNewsFeedSourcesStartingWithName(name)
.stream()
.map(NewsFeedSource::getName)
.toList();
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.sissi.module.rssnews.service.management;
import dev.sheldan.sissi.module.rssnews.model.database.NewsCategoryChannelMapping;
import dev.sheldan.sissi.module.rssnews.model.database.NewsPost;
import dev.sheldan.sissi.module.rssnews.model.feed.NewsResponseItem;
import dev.sheldan.sissi.module.rssnews.repository.NewsPostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NewsPostManagementServiceBean {
@Autowired
private NewsPostRepository newsPostRepository;
public NewsPost createNewsPost(Long id, NewsResponseItem newsResponseItem, NewsCategoryChannelMapping newsCategoryChannelMapping) {
NewsPost post = NewsPost
.builder()
.url(newsResponseItem.getLink())
.postChannel(newsCategoryChannelMapping.getChannel())
.mapping(newsCategoryChannelMapping)
.server(newsCategoryChannelMapping.getServer())
.id(id)
.build();
return newsPostRepository.save(post);
}
}

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,49 @@
<?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="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="rssNewsFeature" value="(SELECT id FROM feature WHERE key = 'rssNews')"/>
<changeSet author="Sheldan" id="rssNewsFeature-commands">
<insert tableName="command">
<column name="name" value="createNewsCategory"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="deleteNewsCategory"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="showNewsCategories"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="createNewsCategoryChannelMapping"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="deleteNewsCategoryChannelMapping"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="createNewsCategorySubscription"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="deleteNewsCategorySubscription"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${rssNewsFeature}"/>
</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.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="command.xml" relativeToChangelogFile="true"/>
<include file="news_feed_source.xml" relativeToChangelogFile="true"/>
<include file="news_gathering_job.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,17 @@
<?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="news_feature-insertion">
<insert tableName="feature">
<column name="key" value="orfNews"/>
</insert>
<insert tableName="feature">
<column name="key" value="rssNews"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,92 @@
<?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="news_feed_source-initial_sources">
<insert tableName="news_feed_source">
<column name="name" value="news"/>
<column name="url" value="https://rss.orf.at/news.xml"/>
<column name="feed_type" value="RFD"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="sport"/>
<column name="url" value="https://rss.orf.at/sport.xml"/>
<column name="feed_type" value="RFD"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="help"/>
<column name="url" value="https://rss.orf.at/help.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="science"/>
<column name="url" value="https://rss.orf.at/science.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="oe3"/>
<column name="url" value="https://rss.orf.at/oe3.xml"/>
<column name="feed_type" value="RFD"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="fm4"/>
<column name="url" value="https://rss.orf.at/fm4.xml"/>
<column name="feed_type" value="RFD"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="oesterreich"/>
<column name="url" value="https://rss.orf.at/oesterreich.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="burgenland"/>
<column name="url" value="https://rss.orf.at/burgenland.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="wien"/>
<column name="url" value="https://rss.orf.at/wien.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="niederoesterreich"/>
<column name="url" value="https://rss.orf.at/noe.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="oberoesterreich"/>
<column name="url" value="https://rss.orf.at/ooe.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="salzburg"/>
<column name="url" value="https://rss.orf.at/salzburg.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="steiermark"/>
<column name="url" value="https://rss.orf.at/steiermark.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="kaernten"/>
<column name="url" value="https://rss.orf.at/kaernten.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="tirol"/>
<column name="url" value="https://rss.orf.at/tirol.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
<insert tableName="news_feed_source">
<column name="name" value="vorarlberg"/>
<column name="url" value="https://rss.orf.at/vorarlberg.xml"/>
<column name="feed_type" value="RSS"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,19 @@
<?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="news_gathering_job-insert">
<insert tableName="scheduler_job">
<column name="name" value="newsGatheringJob"/>
<column name="group_name" value="rssNews"/>
<column name="clazz" value="dev.sheldan.sissi.module.rssnews.orf.job.NewsPostGatherJob"/>
<column name="active" value="true"/>
<column name="cron_expression" value="0 */10 * * * ?"/>
<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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="news_category-table">
<createTable tableName="news_category">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false"/>
</column>
<column name="key" type="VARCHAR(128)">
<constraints nullable="false"/>
</column>
<column name="enabled" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addUniqueConstraint columnNames="key, server_id"
constraintName="uc_news_category"
disabled="false"
tableName="news_category"/>
<addPrimaryKey columnNames="id" tableName="news_category" constraintName="pk_news_category" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="news_category" constraintName="fk_news_category_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS news_category_update_trigger ON news_category;
CREATE TRIGGER news_category_update_trigger BEFORE UPDATE ON news_category FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS news_category_insert_trigger ON news_category;
CREATE TRIGGER news_category_insert_trigger BEFORE INSERT ON news_category FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
<changeSet author="Sheldan" id="news_category_in_news_feed_source_category-table">
<createTable tableName="news_category_in_news_feed_source_category">
<column name="server_category_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="source_category_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="server_category_id" baseTableName="news_category_in_news_feed_source_category"
constraintName="fk_news_category_in_news_feed_source_category_server_category" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="news_category"
validate="true"/>
<addForeignKeyConstraint baseColumnNames="source_category_id" baseTableName="news_category_in_news_feed_source_category"
constraintName="fk_news_category_in_news_feed_source_category_server_category_source_category" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="news_feed_source_category" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,53 @@
<?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="news_category_channel_mapping-table">
<createTable tableName="news_category_channel_mapping">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false"/>
</column>
<column name="enabled" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="category_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addUniqueConstraint columnNames="server_id, channel_id, category_id"
constraintName="uc_news_category_channel_mapping"
disabled="false"
tableName="news_category_channel_mapping"/>
<addPrimaryKey columnNames="id" tableName="news_category_channel_mapping" constraintName="pk_news_category_channel_mapping" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="news_category_channel_mapping" constraintName="fk_news_category_channel_mapping_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="channel_id" baseTableName="news_category_channel_mapping" constraintName="fk_news_category_channel_mapping_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<addForeignKeyConstraint baseColumnNames="category_id" baseTableName="news_category_channel_mapping" constraintName="fk_news_category_channel_mapping_news_category"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="news_category" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS news_category_channel_mapping_update_trigger ON news_category_channel_mapping;
CREATE TRIGGER news_category_channel_mapping_update_trigger BEFORE UPDATE ON news_category_channel_mapping FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS news_category_channel_mapping_insert_trigger ON news_category_channel_mapping;
CREATE TRIGGER news_category_channel_mapping_insert_trigger BEFORE INSERT ON news_category_channel_mapping 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.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="news_feed_record-table">
<createTable tableName="news_feed_record">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false"/>
</column>
<column name="url" type="VARCHAR(128)">
<constraints nullable="false"/>
</column>
<column name="source_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addUniqueConstraint columnNames="url, source_id"
constraintName="uc_news_feed_record"
disabled="false"
tableName="news_feed_record"/>
<addPrimaryKey columnNames="id" tableName="news_feed_record" constraintName="pk_news_feed_record" validate="true"/>
<addForeignKeyConstraint baseColumnNames="source_id" baseTableName="news_feed_record" constraintName="fk_news_feed_record_source"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="news_feed_source" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS news_feed_record_update_trigger ON news_feed_record;
CREATE TRIGGER news_feed_record_update_trigger BEFORE UPDATE ON news_feed_record FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS news_feed_record_insert_trigger ON news_feed_record;
CREATE TRIGGER news_feed_record_insert_trigger BEFORE INSERT ON news_feed_record FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</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" >
<changeSet author="Sheldan" id="news_feed_source-table">
<createTable tableName="news_feed_source">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false"/>
</column>
<column name="url" type="VARCHAR(255)">
<constraints nullable="false" unique="true"/>
</column>
<column name="name" type="VARCHAR(128)">
<constraints nullable="false" unique="true"/>
</column>
<column name="feed_type" type="VARCHAR(256)">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey columnNames="id" tableName="news_feed_source" constraintName="pk_news_feed_source" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS news_feed_source_update_trigger ON news_feed_source;
CREATE TRIGGER news_feed_source_update_trigger BEFORE UPDATE ON news_feed_source FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS news_feed_source_insert_trigger ON news_feed_source;
CREATE TRIGGER news_feed_source_insert_trigger BEFORE INSERT ON news_feed_source FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
<sql>
ALTER TABLE news_feed_source ADD CONSTRAINT check_news_feed_source_feed_type CHECK (feed_type IN ('RFD', 'RSS'));
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,41 @@
<?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="news_feed_source_category-table">
<createTable tableName="news_feed_source_category">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false"/>
</column>
<column name="name" type="VARCHAR(128)">
<constraints nullable="false"/>
</column>
<column name="source_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addUniqueConstraint columnNames="name, source_id"
constraintName="uc_news_feed_category"
disabled="false"
tableName="news_feed_source_category"/>
<addPrimaryKey columnNames="id" tableName="news_feed_source_category" constraintName="pk_news_feed_source_category" validate="true"/>
<addForeignKeyConstraint baseColumnNames="source_id" baseTableName="news_feed_source_category" constraintName="fk_news_feed_source_category_news_feed_source"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="news_feed_source" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS news_feed_source_category_update_trigger ON news_feed_source_category;
CREATE TRIGGER news_feed_source_category_update_trigger BEFORE UPDATE ON news_feed_source_category FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS news_feed_source_category_insert_trigger ON news_feed_source_category;
CREATE TRIGGER news_feed_source_category_insert_trigger BEFORE INSERT ON news_feed_source_category FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

Some files were not shown because too many files have changed in this diff Show More