mirror of
https://github.com/Sheldan/OnePlusBot.git
synced 2026-01-02 23:57:14 +00:00
[OPB-3] adding daily job to check for new news posts
updating to abstracto 1.5.12 preparing for release
This commit is contained in:
2
Tiltfile
2
Tiltfile
@@ -15,7 +15,7 @@ local_resource(
|
|||||||
deps=['pom.xml'])
|
deps=['pom.xml'])
|
||||||
|
|
||||||
docker_build_with_restart(
|
docker_build_with_restart(
|
||||||
registry + 'oneplus-bot',
|
registry + 'oneplus-bot-image',
|
||||||
'./application/executable/target/jar',
|
'./application/executable/target/jar',
|
||||||
entrypoint=['java', '-noverify', '-cp', '.:./lib/*', 'dev.sheldan.oneplus.bot.executable.Application'],
|
entrypoint=['java', '-noverify', '-cp', '.:./lib/*', 'dev.sheldan.oneplus.bot.executable.Application'],
|
||||||
dockerfile='./application/executable/Dockerfile',
|
dockerfile='./application/executable/Dockerfile',
|
||||||
|
|||||||
@@ -9,16 +9,15 @@
|
|||||||
|
|
||||||
<artifactId>news</artifactId>
|
<artifactId>news</artifactId>
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>8</maven.compiler.source>
|
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.sheldan.abstracto.scheduling</groupId>
|
<groupId>dev.sheldan.abstracto.scheduling</groupId>
|
||||||
<artifactId>scheduling-int</artifactId>
|
<artifactId>scheduling-int</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import java.util.List;
|
|||||||
@Component
|
@Component
|
||||||
public class NewsFeature implements FeatureConfig {
|
public class NewsFeature implements FeatureConfig {
|
||||||
|
|
||||||
|
public static final String NEWS_FORUM_POST_NOTIFICATION_SERVER_ID_ENV_NAME = "NEWS_FORUM_POST_NOTIFICATION_SERVER_ID";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FeatureDefinition getFeature() {
|
public FeatureDefinition getFeature() {
|
||||||
return NewsFeatureDefinition.NEWS;
|
return NewsFeatureDefinition.NEWS;
|
||||||
@@ -18,6 +20,6 @@ public class NewsFeature implements FeatureConfig {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PostTargetEnum> getRequiredPostTargets() {
|
public List<PostTargetEnum> getRequiredPostTargets() {
|
||||||
return Arrays.asList(NewsPostTarget.NEWS_TARGET);
|
return Arrays.asList(NewsPostTarget.NEWS_TARGET, NewsPostTarget.FORUM_POST_NOTIFICATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import lombok.Getter;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public enum NewsPostTarget implements PostTargetEnum {
|
public enum NewsPostTarget implements PostTargetEnum {
|
||||||
NEWS_TARGET("news");
|
NEWS_TARGET("news"), FORUM_POST_NOTIFICATION("forumPostNotification");
|
||||||
|
|
||||||
private String key;
|
private String key;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.job;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.service.NewsSourceServiceBean;
|
||||||
|
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 CheckForNewsPosts extends QuartzJobBean {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewsSourceServiceBean newsSourceServiceBean;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
|
||||||
|
try {
|
||||||
|
log.info("Executing news post check job.");
|
||||||
|
newsSourceServiceBean.checkForNewThreads();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("Failed to execute news post check job.", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.forum.ForumPost;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class ForumPostNotificationEntry {
|
||||||
|
private Long postId;
|
||||||
|
private String subject;
|
||||||
|
private String content;
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
public static ForumPostNotificationEntry fromPost(ForumPost forumPost) {
|
||||||
|
return ForumPostNotificationEntry
|
||||||
|
.builder()
|
||||||
|
.postId(forumPost.getId())
|
||||||
|
.subject(forumPost.getSubject())
|
||||||
|
.content(forumPost.getContent())
|
||||||
|
.creatorId(forumPost.getSource().getUserId())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class ForumPostNotificationModel {
|
||||||
|
private List<ForumPostNotificationEntry> entries;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.database;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Entity
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Table(name = "news_forum_post")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class NewsForumPost {
|
||||||
|
@Id
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "creator_id", nullable = false)
|
||||||
|
private NewsSource creator;
|
||||||
|
|
||||||
|
@Column(name = "created")
|
||||||
|
private Instant created;
|
||||||
|
|
||||||
|
@Column(name = "updated")
|
||||||
|
private Instant updated;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.database;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Entity
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Table(name = "news_source")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class NewsSource {
|
||||||
|
@Id
|
||||||
|
@Column(name = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Column(name = "thread_count")
|
||||||
|
private Long threadCount;
|
||||||
|
|
||||||
|
@Column(name = "created")
|
||||||
|
private Instant created;
|
||||||
|
|
||||||
|
@Column(name = "updated")
|
||||||
|
private Instant updated;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsSource;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class ForumPost {
|
||||||
|
private Long id;
|
||||||
|
private String subject;
|
||||||
|
private String content;
|
||||||
|
private NewsSource source;
|
||||||
|
|
||||||
|
public static ForumPost fromRow(NewsForumPostDataRow dataRow, NewsSource newsSource) {
|
||||||
|
return ForumPost
|
||||||
|
.builder()
|
||||||
|
.subject(dataRow.getSubject())
|
||||||
|
.id(dataRow.getId())
|
||||||
|
.content(dataRow.getContent())
|
||||||
|
.source(newsSource)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class NewsForumPostData {
|
||||||
|
@SerializedName("rows")
|
||||||
|
private List<NewsForumPostDataRow> rows;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class NewsForumPostDataRow {
|
||||||
|
@SerializedName("id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@SerializedName("subject")
|
||||||
|
private String subject;
|
||||||
|
|
||||||
|
@SerializedName("content")
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class NewsForumPostResponse {
|
||||||
|
|
||||||
|
@SerializedName("data")
|
||||||
|
private NewsForumPostData newsForumPostData;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class NewsSourceResponse {
|
||||||
|
@SerializedName("data")
|
||||||
|
private NewsSourceUserData user;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class NewsSourceUserData {
|
||||||
|
@SerializedName("userStatVO")
|
||||||
|
private NewsSourceUserStats userStats;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.model.forum;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class NewsSourceUserStats {
|
||||||
|
@SerializedName("threadCount")
|
||||||
|
private Long threadCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.repository;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsForumPost;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface NewsForumPostRepository extends JpaRepository<NewsForumPost, Long> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.repository;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsSource;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface NewsResourceRepository extends JpaRepository<NewsSource, Long> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.service;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsSource;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.forum.ForumPost;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.forum.NewsForumPostResponse;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.forum.NewsSourceResponse;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ForumApiClient {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OkHttpClient okHttpClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
|
@Value("${abstracto.feature.news.userURL}")
|
||||||
|
private String userRequestURL;
|
||||||
|
|
||||||
|
@Value("${abstracto.feature.news.threadURL}")
|
||||||
|
private String threadInfoUrl;
|
||||||
|
|
||||||
|
public Long getCurrentThreadCount(NewsSource newsSource) {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(String.format(userRequestURL, newsSource.getUserId()))
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = okHttpClient.newCall(request).execute();
|
||||||
|
if(!response.isSuccessful()) {
|
||||||
|
throw new AbstractoRunTimeException(String.format("Failed to load user info for id %s", newsSource.getUserId()));
|
||||||
|
}
|
||||||
|
NewsSourceResponse newsSourceResponse = gson.fromJson(response.body().string(), NewsSourceResponse.class);
|
||||||
|
if (newsSourceResponse.getUser() != null
|
||||||
|
&& newsSourceResponse.getUser().getUserStats() != null
|
||||||
|
&& newsSourceResponse.getUser().getUserStats().getThreadCount() != null) {
|
||||||
|
return newsSourceResponse.getUser().getUserStats().getThreadCount();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AbstractoRunTimeException(e);
|
||||||
|
}
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumPost> getPostsOfSource(NewsSource source) {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(String.format(threadInfoUrl, source.getUserId()))
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
Response response;
|
||||||
|
try {
|
||||||
|
response = okHttpClient.newCall(request).execute();
|
||||||
|
if(!response.isSuccessful()) {
|
||||||
|
throw new AbstractoRunTimeException(String.format("Failed to load thread info for id %s", source.getUserId()));
|
||||||
|
}
|
||||||
|
NewsForumPostResponse newsSourceResponse = gson.fromJson(response.body().string(), NewsForumPostResponse.class);
|
||||||
|
if (newsSourceResponse.getNewsForumPostData() != null
|
||||||
|
&& newsSourceResponse.getNewsForumPostData().getRows() != null) {
|
||||||
|
return newsSourceResponse
|
||||||
|
.getNewsForumPostData()
|
||||||
|
.getRows()
|
||||||
|
.stream()
|
||||||
|
.map(dataRow -> ForumPost.fromRow(dataRow, source))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AbstractoRunTimeException(e);
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.service;
|
||||||
|
|
||||||
|
import dev.sheldan.abstracto.core.service.PostTargetService;
|
||||||
|
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||||
|
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||||
|
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.config.NewsPostTarget;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.ForumPostNotificationEntry;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.ForumPostNotificationModel;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsForumPost;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsSource;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.forum.ForumPost;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.service.management.NewsForumPostManagementServiceBean;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.service.management.NewsSourceManagementServiceBean;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static dev.sheldan.oneplus.bot.modules.news.config.NewsFeature.NEWS_FORUM_POST_NOTIFICATION_SERVER_ID_ENV_NAME;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class NewsSourceServiceBean {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ForumApiClient forumApiClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewsSourceManagementServiceBean newsSourceManagementServiceBean;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewsForumPostManagementServiceBean newsForumPostManagementServiceBean;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PostTargetService postTargetService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TemplateService templateService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewsSourceServiceBean self;
|
||||||
|
|
||||||
|
private static final String NEWS_FORUM_POST_NOTIFICATION_TEMPLATE_KEY = "newsForumPost_notification";
|
||||||
|
|
||||||
|
public void checkForNewThreads() {
|
||||||
|
Long targetServerId = Long.parseLong(System.getenv(NEWS_FORUM_POST_NOTIFICATION_SERVER_ID_ENV_NAME));
|
||||||
|
List<ForumPost> newForumPosts = getNewForumPosts();
|
||||||
|
log.info("Found {} new forum posts.", newForumPosts.size());
|
||||||
|
if(newForumPosts.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ForumPostNotificationEntry> entries = new ArrayList<>();
|
||||||
|
newForumPosts.forEach(forumPost -> entries.add(ForumPostNotificationEntry.fromPost(forumPost)));
|
||||||
|
|
||||||
|
ForumPostNotificationModel model = ForumPostNotificationModel
|
||||||
|
.builder()
|
||||||
|
.entries(entries)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MessageToSend messageToSend = templateService.renderEmbedTemplate(NEWS_FORUM_POST_NOTIFICATION_TEMPLATE_KEY, model, targetServerId);
|
||||||
|
|
||||||
|
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, NewsPostTarget.FORUM_POST_NOTIFICATION, targetServerId))
|
||||||
|
.thenAccept(unused -> {
|
||||||
|
log.info("Sent news forum post notification.");
|
||||||
|
self.persistForumPostsAndThreadCount(entries);
|
||||||
|
}).exceptionally(throwable -> {
|
||||||
|
log.error("Failed to send news forum post notification.", throwable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void persistForumPostsAndThreadCount(List<ForumPostNotificationEntry> entries) {
|
||||||
|
Map<Long, NewsSource> sourceMap = newsSourceManagementServiceBean.loadNewsSources()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(NewsSource::getUserId, Function.identity()));
|
||||||
|
|
||||||
|
entries.forEach(forumPostNotificationEntry ->
|
||||||
|
newsForumPostManagementServiceBean.createPost(sourceMap.get(forumPostNotificationEntry.getCreatorId()), forumPostNotificationEntry.getPostId()));
|
||||||
|
|
||||||
|
sourceMap.values().forEach(newsSource -> {
|
||||||
|
Long currentThreadCount = forumApiClient.getCurrentThreadCount(newsSource);
|
||||||
|
newsSource.setThreadCount(currentThreadCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasThreadCountChanged(NewsSource newsSource) {
|
||||||
|
Long currentThreadCount = forumApiClient.getCurrentThreadCount(newsSource);
|
||||||
|
return !currentThreadCount.equals(newsSource.getThreadCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ForumPost> getNewForumPosts() {
|
||||||
|
List<NewsSource> newsSources = newsSourceManagementServiceBean.loadNewsSources();
|
||||||
|
log.info("Total news source count: {}", newsSources.size());
|
||||||
|
List<NewsSource> sourcesWithChangedThreadCount = newsSources
|
||||||
|
.stream()
|
||||||
|
.filter(this::hasThreadCountChanged)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
log.info("News sources with new thread count: {}", sourcesWithChangedThreadCount.size());
|
||||||
|
|
||||||
|
List<ForumPost> currentForumPosts = sourcesWithChangedThreadCount
|
||||||
|
.stream()
|
||||||
|
.map(newsSource -> forumApiClient.getPostsOfSource(newsSource))
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
log.info("Total amount of incoming forum posts: {}", currentForumPosts.size());
|
||||||
|
if(currentForumPosts.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Long> incomingForumPostIds = currentForumPosts
|
||||||
|
.stream()
|
||||||
|
.map(ForumPost::getId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
List<NewsForumPost> existingNewsForumPosts = newsForumPostManagementServiceBean.getAllPosts();
|
||||||
|
|
||||||
|
log.info("Total amount of existing and tracked forum posts: {}", existingNewsForumPosts.size());
|
||||||
|
|
||||||
|
Set<Long> existingForumPostIds = existingNewsForumPosts
|
||||||
|
.stream()
|
||||||
|
.map(NewsForumPost::getId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
incomingForumPostIds.removeAll(existingForumPostIds);
|
||||||
|
|
||||||
|
if(incomingForumPostIds.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, ForumPost> incomingPostMap = currentForumPosts
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(ForumPost::getId, Function.identity()));
|
||||||
|
|
||||||
|
return incomingForumPostIds.stream().map(incomingPostMap::get).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.service.management;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsForumPost;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsSource;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.repository.NewsForumPostRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class NewsForumPostManagementServiceBean {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewsForumPostRepository repository;
|
||||||
|
|
||||||
|
public List<NewsForumPost> getAllPosts() {
|
||||||
|
return repository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NewsForumPost createPost(NewsSource creator, Long id) {
|
||||||
|
NewsForumPost post = NewsForumPost
|
||||||
|
.builder()
|
||||||
|
.creator(creator)
|
||||||
|
.id(id)
|
||||||
|
.build();
|
||||||
|
return repository.save(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.sheldan.oneplus.bot.modules.news.service.management;
|
||||||
|
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.model.database.NewsSource;
|
||||||
|
import dev.sheldan.oneplus.bot.modules.news.repository.NewsResourceRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class NewsSourceManagementServiceBean {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewsResourceRepository newsResourceRepository;
|
||||||
|
|
||||||
|
public List<NewsSource> loadNewsSources() {
|
||||||
|
return newsResourceRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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="news_check_job.xml" relativeToChangelogFile="true"/>
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -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="check_for_news_job-insert">
|
||||||
|
<insert tableName="scheduler_job">
|
||||||
|
<column name="name" value="checkForNewsJob"/>
|
||||||
|
<column name="group_name" value="news"/>
|
||||||
|
<column name="clazz" value="dev.sheldan.oneplus.bot.modules.news.job.CheckForNewsPosts"/>
|
||||||
|
<column name="active" value="true"/>
|
||||||
|
<column name="cron_expression" value="0 0 0 * * ?"/>
|
||||||
|
<column name="recovery" value="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?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_forum_post-table">
|
||||||
|
<createTable tableName="news_forum_post">
|
||||||
|
<column name="id" type="BIGINT">
|
||||||
|
<constraints nullable="true" primaryKey="true" primaryKeyName="pk_news_forum_post"/>
|
||||||
|
</column>
|
||||||
|
<column name="creator_id" type="BIGINT">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
|
||||||
|
</createTable>
|
||||||
|
<addForeignKeyConstraint baseColumnNames="creator_id" baseTableName="news_forum_post" constraintName="fk_news_forum_post_creator"
|
||||||
|
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
|
||||||
|
referencedColumnNames="user_id" referencedTableName="news_source" validate="true"/>
|
||||||
|
|
||||||
|
<sql>
|
||||||
|
DROP TRIGGER IF EXISTS news_forum_post_update_trigger ON news_forum_post;
|
||||||
|
CREATE TRIGGER repost_check_channel_group_update_trigger BEFORE UPDATE ON news_forum_post FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
|
||||||
|
</sql>
|
||||||
|
<sql>
|
||||||
|
DROP TRIGGER IF EXISTS news_forum_post_insert_trigger ON news_forum_post;
|
||||||
|
CREATE TRIGGER repost_check_channel_group_insert_trigger BEFORE INSERT ON news_forum_post FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?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_source-table">
|
||||||
|
<createTable tableName="news_source">
|
||||||
|
<column name="user_id" type="BIGINT">
|
||||||
|
<constraints nullable="true" primaryKey="true" primaryKeyName="pk_news_source"/>
|
||||||
|
</column>
|
||||||
|
<column name="thread_count" type="BIGINT">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<sql>
|
||||||
|
DROP TRIGGER IF EXISTS news_source_update_trigger ON news_source;
|
||||||
|
CREATE TRIGGER repost_check_channel_group_update_trigger BEFORE UPDATE ON news_source FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
|
||||||
|
</sql>
|
||||||
|
<sql>
|
||||||
|
DROP TRIGGER IF EXISTS news_source_insert_trigger ON news_source;
|
||||||
|
CREATE TRIGGER repost_check_channel_group_insert_trigger BEFORE INSERT ON news_source FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -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="news_source.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="news_forum_post.xml" relativeToChangelogFile="true"/>
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -8,4 +8,5 @@
|
|||||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||||
<include file="1.3.9-news/collection.xml" relativeToChangelogFile="true"/>
|
<include file="1.3.9-news/collection.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="1.3.10-news/collection.xml" relativeToChangelogFile="true"/>
|
<include file="1.3.10-news/collection.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="1.6.11/collection.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
abstracto.postTargets.news.name=news
|
abstracto.postTargets.news.name=news
|
||||||
|
abstracto.postTargets.forumPostNotification.name=forumPostNotification
|
||||||
|
|
||||||
abstracto.featureFlags.news.featureName=news
|
abstracto.featureFlags.news.featureName=news
|
||||||
abstracto.featureFlags.news.enabled=false
|
abstracto.featureFlags.news.enabled=false
|
||||||
|
|
||||||
abstracto.feature.news.removalDays=4
|
abstracto.feature.news.removalDays=4
|
||||||
abstracto.feature.news.postLockSeconds=3600
|
abstracto.feature.news.postLockSeconds=3600
|
||||||
|
|
||||||
|
abstracto.feature.news.userURL=https://community.oneplus.com/ajax/user/frontend/user/info?uid=%s
|
||||||
|
# TODO support pagination.. eventually
|
||||||
|
abstracto.feature.news.threadURL=https://community.oneplus.com/ajax/user/frontend/thread/page?page=1&limit=1000&uid=%s
|
||||||
@@ -15,10 +15,10 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 1.6.10
|
version: 1.6.11
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "1.6.10"
|
appVersion: "1.6.11"
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: db-credentials
|
name: db-credentials
|
||||||
key: dbName
|
key: dbName
|
||||||
|
- name: NEWS_FORUM_POST_NOTIFICATION_SERVER_ID
|
||||||
|
value: "{{ .Values.bot.config.newsForumPostNotificationServerId }}"
|
||||||
- name: TOKEN
|
- name: TOKEN
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ bot:
|
|||||||
repository: harbor.sheldan.dev/oneplus-bot
|
repository: harbor.sheldan.dev/oneplus-bot
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
image: oneplus-bot-image
|
image: oneplus-bot-image
|
||||||
tag: 1.6.10
|
tag: 1.6.11
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
initialDelaySeconds: 60
|
initialDelaySeconds: 60
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
@@ -17,32 +17,34 @@ bot:
|
|||||||
initialDelaySeconds: 60
|
initialDelaySeconds: 60
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
|
config:
|
||||||
|
newsForumPostNotificationServerId:
|
||||||
|
|
||||||
templateDeployment:
|
templateDeployment:
|
||||||
enabled: true
|
enabled: true
|
||||||
repository: harbor.sheldan.dev/abstracto
|
repository: harbor.sheldan.dev/abstracto
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
image: abstracto-template-deployment
|
image: abstracto-template-deployment
|
||||||
tag: 1.5.9
|
tag: 1.5.12
|
||||||
|
|
||||||
templateDeploymentData:
|
templateDeploymentData:
|
||||||
repository: harbor.sheldan.dev/oneplus-bot
|
repository: harbor.sheldan.dev/oneplus-bot
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
image: oneplus-bot-template-data
|
image: oneplus-bot-template-data
|
||||||
tag: 1.6.10
|
tag: 1.6.11
|
||||||
|
|
||||||
dbConfigDeployment:
|
dbConfigDeployment:
|
||||||
enabled: true
|
enabled: true
|
||||||
repository: harbor.sheldan.dev/abstracto
|
repository: harbor.sheldan.dev/abstracto
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
image: abstracto-db-deployment
|
image: abstracto-db-deployment
|
||||||
tag: 1.5.9
|
tag: 1.5.12
|
||||||
|
|
||||||
dbConfigDeploymentData:
|
dbConfigDeploymentData:
|
||||||
repository: harbor.sheldan.dev/oneplus-bot
|
repository: harbor.sheldan.dev/oneplus-bot
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
image: oneplus-bot-db-data
|
image: oneplus-bot-db-data
|
||||||
tag: 1.6.10
|
tag: 1.6.11
|
||||||
|
|
||||||
dbCredentials:
|
dbCredentials:
|
||||||
host:
|
host:
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
REGISTRY_PREFIX=harbor.sheldan.dev/oneplus-bot/
|
REGISTRY_PREFIX=harbor.sheldan.dev/oneplus-bot/
|
||||||
VERSION=1.6.10
|
VERSION=1.6.11
|
||||||
4
pom.xml
4
pom.xml
@@ -19,8 +19,8 @@
|
|||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<!-- edit in release.yml as well -->
|
<!-- edit in release.yml as well -->
|
||||||
<!-- when releasing a new opbot version, update the .env as well-->
|
<!-- when releasing a new opbot version, update the .env as well-->
|
||||||
<abstracto.version>1.5.10</abstracto.version>
|
<abstracto.version>1.5.12</abstracto.version>
|
||||||
<abstracto.templates.version>1.4.22</abstracto.templates.version>
|
<abstracto.templates.version>1.4.23</abstracto.templates.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"embeds": [
|
||||||
|
{
|
||||||
|
<#macro postDisplay post>
|
||||||
|
https://community.oneplus.com/thread/${post.postId?c} | ${post.subject?json_string}
|
||||||
|
</#macro>
|
||||||
|
"description": "<#list entries as entry><@postDisplay post=entry /></#list>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
The channel in which the notifications for new forum posts should be posted to. Currently: ${currentTarget}
|
||||||
Reference in New Issue
Block a user