blob: 949d127373ef0969e969921d4a25ab38091a5a82 [file] [log] [blame]
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +01001package eu.mulk.mulkcms2.benki.newsletter;
2
3import static java.util.stream.Collectors.partitioningBy;
4import static java.util.stream.Collectors.toList;
5
6import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
7import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
8import eu.mulk.mulkcms2.benki.posts.Post;
9import io.quarkus.mailer.MailTemplate.MailTemplateInstance;
10import io.quarkus.panache.common.Sort;
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +020011import io.quarkus.qute.CheckedTemplate;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010012import io.quarkus.qute.TemplateExtension;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010013import io.quarkus.scheduler.Scheduled;
14import java.time.LocalDate;
15import java.time.OffsetDateTime;
16import java.time.ZoneId;
17import java.time.format.DateTimeFormatter;
18import java.time.format.FormatStyle;
19import java.util.List;
20import java.util.concurrent.ExecutionException;
21import java.util.concurrent.TimeUnit;
22import java.util.concurrent.TimeoutException;
23import javax.annotation.CheckForNull;
24import javax.enterprise.context.Dependent;
25import javax.persistence.EntityManager;
26import javax.persistence.PersistenceContext;
27import javax.transaction.Transactional;
28import org.eclipse.microprofile.config.inject.ConfigProperty;
29import org.hibernate.Session;
Matthias Andreas Benkard601508f2022-11-08 21:42:59 +010030import org.hibernate.annotations.QueryHints;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010031
32@Dependent
33public class NewsletterSender {
34
35 private static final DateTimeFormatter humanDateFormatter =
36 DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
37
38 @ConfigProperty(name = "mulkcms.newsletter.time-zone")
39 ZoneId newsletterTimeZone;
40
Matthias Andreas Benkardd4df22d2021-02-06 11:31:27 +010041 @ConfigProperty(name = "quarkus.mailer.from")
42 String senderAddress;
43
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010044 @PersistenceContext EntityManager em;
45
46 @CheckedTemplate
47 static class Templates {
48 public static native MailTemplateInstance newsletter(
49 Newsletter newsletter, List<Bookmark> bookmarks, List<LazychatMessage> lazychatMessages);
50 }
51
52 @Scheduled(cron = "0 0 0 ? * Mon")
53 @Transactional
54 void run() throws InterruptedException, TimeoutException, ExecutionException {
55 var session = em.unwrap(Session.class);
56
Matthias Andreas Benkard5000f9a2020-11-03 07:09:42 +010057 List<Post<?>> posts =
Matthias Andreas Benkard601508f2022-11-08 21:42:59 +010058 Post.find(
59 """
60 SELECT DISTINCT p FROM Post p
61 JOIN p.targets r
62 JOIN r.tags tag
63 WHERE p.newsletter IS NULL
64 AND p.scope = 'top_level'
65 AND tag = 'world'
66 """,
67 Sort.ascending("date"))
68 .withHint(QueryHints.PASS_DISTINCT_THROUGH, false)
69 .list();
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010070 Post.fetchTexts(posts);
71
72 if (posts.isEmpty()) {
73 return;
74 }
75
76 var postsByClass = posts.stream().collect(partitioningBy(Post::isBookmark));
77 var bookmarks =
78 postsByClass.getOrDefault(Boolean.TRUE, List.of()).stream()
79 .map(x -> (Bookmark) x)
80 .collect(toList());
81 var lazychatMessages =
82 postsByClass.getOrDefault(Boolean.FALSE, List.of()).stream()
83 .map(x -> (LazychatMessage) x)
84 .collect(toList());
85
86 var date = OffsetDateTime.now(newsletterTimeZone);
87 var newsletterNumber =
88 (int)
89 session
90 .createQuery("SELECT max(id) FROM Newsletter", Integer.class)
91 .uniqueResultOptional()
92 .map(x -> x + 1)
93 .orElse(1);
94
95 var newsletter = new Newsletter();
96 newsletter.id = newsletterNumber;
97 newsletter.date = date;
98 newsletter.persist();
99
100 posts.forEach(post -> post.newsletter = newsletter);
101
102 var subscriberEmails =
Matthias Andreas Benkardff358942020-11-03 06:16:17 +0100103 NewsletterSubscription.<NewsletterSubscription>stream("registrationKey IS NULL")
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100104 .map(x -> x.email)
105 .toArray(String[]::new);
106
107 var mailText = Templates.newsletter(newsletter, bookmarks, lazychatMessages);
108 var sendJob =
109 mailText
110 .subject(String.format("MulkCMS newsletter #%d", newsletterNumber))
Matthias Andreas Benkardd4df22d2021-02-06 11:31:27 +0100111 .to(senderAddress)
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100112 .bcc(subscriberEmails)
113 .send();
Matthias Andreas Benkarda50ac8d2021-06-28 19:58:36 +0200114 sendJob.subscribe().asCompletionStage().get(10000, TimeUnit.SECONDS);
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100115 }
116
117 @TemplateExtension
118 @CheckForNull
119 static String humanDate(@CheckForNull LocalDate x) {
120 if (x == null) {
121 return null;
122 }
123 return humanDateFormatter.format(x);
124 }
125
126 @TemplateExtension
127 @CheckForNull
128 static String humanDate(@CheckForNull OffsetDateTime x) {
129 if (x == null) {
130 return null;
131 }
132 return humanDateFormatter.format(x);
133 }
134}