summaryrefslogtreecommitdiff
path: root/src/main/java/eu
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2020-11-01 12:58:35 +0100
committerMatthias Andreas Benkard <>2020-11-01 12:58:35 +0100
commitba3e58c04e918723233dcc66996399eeeff24007 (patch)
treeece716b5ff12e77ee2eaf0163912ebe49077f0e0 /src/main/java/eu
parent8ae23a7fa6202377f957919d763c618859cb0d74 (diff)
KB68 Implement newsletter sending.
Change-Id: I1d56e40d7f35d6be77fde1a1e8519a91bd2dc3b8
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/newsletter/Newsletter.java29
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSender.java117
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSubscription.java29
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java20
4 files changed, 191 insertions, 4 deletions
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/newsletter/Newsletter.java b/src/main/java/eu/mulk/mulkcms2/benki/newsletter/Newsletter.java
new file mode 100644
index 0000000..3d9a3fe
--- /dev/null
+++ b/src/main/java/eu/mulk/mulkcms2/benki/newsletter/Newsletter.java
@@ -0,0 +1,29 @@
+package eu.mulk.mulkcms2.benki.newsletter;
+
+import eu.mulk.mulkcms2.benki.posts.Post;
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import java.time.OffsetDateTime;
+import java.util.Collection;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "newsletters", schema = "benki")
+public class Newsletter extends PanacheEntityBase {
+
+ @Id
+ @Column(name = "id", nullable = false)
+ public Integer id;
+
+ @Column(name = "date", nullable = false)
+ public OffsetDateTime date = OffsetDateTime.now();
+
+ @OneToMany(mappedBy = "owner", fetch = FetchType.LAZY)
+ @OrderBy("date")
+ public Collection<Post<?>> posts;
+}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSender.java b/src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSender.java
new file mode 100644
index 0000000..1f13c08
--- /dev/null
+++ b/src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSender.java
@@ -0,0 +1,117 @@
+package eu.mulk.mulkcms2.benki.newsletter;
+
+import static java.util.stream.Collectors.partitioningBy;
+import static java.util.stream.Collectors.toList;
+
+import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
+import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
+import eu.mulk.mulkcms2.benki.posts.Post;
+import io.quarkus.mailer.MailTemplate.MailTemplateInstance;
+import io.quarkus.panache.common.Sort;
+import io.quarkus.qute.TemplateExtension;
+import io.quarkus.qute.api.CheckedTemplate;
+import io.quarkus.scheduler.Scheduled;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.annotation.CheckForNull;
+import javax.enterprise.context.Dependent;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.transaction.Transactional;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.hibernate.Session;
+
+@Dependent
+public class NewsletterSender {
+
+ private static final DateTimeFormatter humanDateFormatter =
+ DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
+
+ @ConfigProperty(name = "mulkcms.newsletter.time-zone")
+ ZoneId newsletterTimeZone;
+
+ @PersistenceContext EntityManager em;
+
+ @CheckedTemplate
+ static class Templates {
+ public static native MailTemplateInstance newsletter(
+ Newsletter newsletter, List<Bookmark> bookmarks, List<LazychatMessage> lazychatMessages);
+ }
+
+ @Scheduled(cron = "0 0 0 ? * Mon")
+ @Transactional
+ void run() throws InterruptedException, TimeoutException, ExecutionException {
+ var session = em.unwrap(Session.class);
+
+ List<Post<?>> posts = Post.list("newsletter IS NULL", Sort.ascending("date"));
+ Post.fetchTexts(posts);
+
+ if (posts.isEmpty()) {
+ return;
+ }
+
+ var postsByClass = posts.stream().collect(partitioningBy(Post::isBookmark));
+ var bookmarks =
+ postsByClass.getOrDefault(Boolean.TRUE, List.of()).stream()
+ .map(x -> (Bookmark) x)
+ .collect(toList());
+ var lazychatMessages =
+ postsByClass.getOrDefault(Boolean.FALSE, List.of()).stream()
+ .map(x -> (LazychatMessage) x)
+ .collect(toList());
+
+ var date = OffsetDateTime.now(newsletterTimeZone);
+ var newsletterNumber =
+ (int)
+ session
+ .createQuery("SELECT max(id) FROM Newsletter", Integer.class)
+ .uniqueResultOptional()
+ .map(x -> x + 1)
+ .orElse(1);
+
+ var newsletter = new Newsletter();
+ newsletter.id = newsletterNumber;
+ newsletter.date = date;
+ newsletter.persist();
+
+ posts.forEach(post -> post.newsletter = newsletter);
+
+ var subscriberEmails =
+ NewsletterSubscription.<NewsletterSubscription>streamAll()
+ .map(x -> x.email)
+ .toArray(String[]::new);
+
+ var mailText = Templates.newsletter(newsletter, bookmarks, lazychatMessages);
+ var sendJob =
+ mailText
+ .subject(String.format("MulkCMS newsletter #%d", newsletterNumber))
+ .bcc(subscriberEmails)
+ .send();
+ sendJob.toCompletableFuture().get(10000, TimeUnit.SECONDS);
+ }
+
+ @TemplateExtension
+ @CheckForNull
+ static String humanDate(@CheckForNull LocalDate x) {
+ if (x == null) {
+ return null;
+ }
+ return humanDateFormatter.format(x);
+ }
+
+ @TemplateExtension
+ @CheckForNull
+ static String humanDate(@CheckForNull OffsetDateTime x) {
+ if (x == null) {
+ return null;
+ }
+ return humanDateFormatter.format(x);
+ }
+}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSubscription.java b/src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSubscription.java
new file mode 100644
index 0000000..7aeda60
--- /dev/null
+++ b/src/main/java/eu/mulk/mulkcms2/benki/newsletter/NewsletterSubscription.java
@@ -0,0 +1,29 @@
+package eu.mulk.mulkcms2.benki.newsletter;
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import java.time.OffsetDateTime;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.Email;
+import org.hibernate.annotations.NaturalId;
+
+@Entity
+@Table(name = "newsletter_subscriptions", schema = "benki")
+public class NewsletterSubscription extends PanacheEntityBase {
+
+ @Id
+ @Column(name = "id", nullable = false)
+ @GeneratedValue
+ public Integer id;
+
+ @Column(name = "start_date", nullable = false)
+ public OffsetDateTime startDate = OffsetDateTime.now();
+
+ @NaturalId
+ @Column(name = "email", nullable = false)
+ @Email
+ public String email;
+}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
index fd023d7..346b71f 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
@@ -5,12 +5,14 @@ import static java.util.stream.Collectors.toList;
import eu.mulk.mulkcms2.benki.accesscontrol.Role;
import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
+import eu.mulk.mulkcms2.benki.newsletter.Newsletter;
import eu.mulk.mulkcms2.benki.users.User;
import eu.mulk.mulkcms2.benki.users.User_;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -70,6 +72,12 @@ public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {
public OffsetDateTime date;
@ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "newsletter", referencedColumnName = "id", nullable = true)
+ @CheckForNull
+ @JsonbTransient
+ public Newsletter newsletter;
+
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner", referencedColumnName = "id")
@CheckForNull
@JsonbTransient
@@ -340,18 +348,22 @@ public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {
}
// Fetch texts (to avoid n+1 selects).
- var postIds = forwardResults.stream().map(x -> x.id).collect(toList());
+ fetchTexts(forwardResults);
+
+ return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults);
+ }
+
+ public static <T extends Post<?>> void fetchTexts(Collection<T> posts) {
+ var postIds = posts.stream().map(x -> x.id).collect(toList());
if (!postIds.isEmpty()) {
find("SELECT p FROM Post p LEFT JOIN FETCH p.texts WHERE p.id IN (?1)", postIds).stream()
.count();
}
-
- return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults);
}
@CheckForNull
- protected Text getText() {
+ public Text getText() {
var texts = getTexts();
if (texts.isEmpty()) {
return null;