blob: a0245c8ab3c3cfbfa03157098f191f9cb7630a3c [file] [log] [blame]
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +02001package eu.mulk.mulkcms2.benki.posts;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +01002
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +02003import static java.util.stream.Collectors.toList;
4
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +01005import com.blazebit.persistence.CriteriaBuilder;
6import com.blazebit.persistence.CriteriaBuilderFactory;
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +01007import eu.mulk.mulkcms2.benki.accesscontrol.Role;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +01008import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
9import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010010import eu.mulk.mulkcms2.benki.newsletter.Newsletter;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010011import eu.mulk.mulkcms2.benki.users.User;
Matthias Andreas Benkarde3bc3ee2023-08-06 16:21:11 +020012import io.hypersistence.utils.hibernate.type.basic.PostgreSQLEnumType;
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +010013import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
Matthias Andreas Benkarde3bc3ee2023-08-06 16:21:11 +020014import jakarta.annotation.Nullable;
15import jakarta.json.bind.annotation.JsonbTransient;
16import jakarta.persistence.CascadeType;
17import jakarta.persistence.Column;
18import jakarta.persistence.Entity;
19import jakarta.persistence.EntityManager;
20import jakarta.persistence.EnumType;
21import jakarta.persistence.Enumerated;
22import jakarta.persistence.FetchType;
23import jakarta.persistence.GeneratedValue;
24import jakarta.persistence.GenerationType;
25import jakarta.persistence.Id;
26import jakarta.persistence.Inheritance;
27import jakarta.persistence.InheritanceType;
28import jakarta.persistence.JoinColumn;
29import jakarta.persistence.JoinTable;
30import jakarta.persistence.ManyToMany;
31import jakarta.persistence.ManyToOne;
32import jakarta.persistence.MapKey;
33import jakarta.persistence.OneToMany;
34import jakarta.persistence.OrderBy;
35import jakarta.persistence.SequenceGenerator;
36import jakarta.persistence.Table;
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +020037import java.time.LocalDate;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010038import java.time.OffsetDateTime;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010039import java.util.ArrayList;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010040import java.util.Collection;
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +020041import java.util.Comparator;
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020042import java.util.HashMap;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010043import java.util.List;
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020044import java.util.Map;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010045import java.util.Objects;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010046import java.util.Set;
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +020047import java.util.TimeZone;
48import java.util.stream.Collectors;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010049import javax.annotation.CheckForNull;
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010050import org.hibernate.annotations.Type;
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +020051import org.hibernate.annotations.Where;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010052
53@Entity
Matthias Andreas Benkard57c9a8a2020-01-24 19:09:38 +010054@Table(name = "posts", schema = "benki")
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010055@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020056public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010057
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010058 public enum Scope {
59 top_level,
60 comment
61 }
62
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010063 @Id
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010064 @SequenceGenerator(
65 allocationSize = 1,
66 sequenceName = "posts_id_seq",
67 name = "posts_id_seq",
68 schema = "benki")
69 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "posts_id_seq")
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010070 @Column(name = "id", nullable = false)
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010071 public Integer id;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010072
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010073 @Column(name = "date", nullable = true)
Matthias Andreas Benkard1e7674c2020-04-18 20:28:51 +020074 @CheckForNull
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010075 public OffsetDateTime date;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010076
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010077 @Column(nullable = false)
78 @Enumerated(EnumType.STRING)
Matthias Andreas Benkarde3bc3ee2023-08-06 16:21:11 +020079 @Type(PostgreSQLEnumType.class)
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010080 public Scope scope = Scope.top_level;
81
Matthias Andreas Benkardaa754802020-01-24 11:55:26 +010082 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010083 @JoinColumn(name = "newsletter", referencedColumnName = "id", nullable = true)
84 @CheckForNull
85 @JsonbTransient
86 public Newsletter newsletter;
87
88 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010089 @JoinColumn(name = "owner", referencedColumnName = "id")
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +020090 @CheckForNull
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020091 @JsonbTransient
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +010092 public User owner;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010093
94 @ManyToMany(fetch = FetchType.LAZY)
95 @JoinTable(
96 name = "user_visible_posts",
Matthias Andreas Benkard553de3e2020-01-27 05:33:15 +010097 schema = "benki",
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010098 joinColumns = @JoinColumn(name = "message"),
99 inverseJoinColumns = @JoinColumn(name = "user"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200100 @JsonbTransient
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +0100101 public Set<User> visibleTo;
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +0100102
103 @ManyToMany(fetch = FetchType.LAZY)
104 @JoinTable(
105 name = "post_targets",
106 schema = "benki",
107 joinColumns = @JoinColumn(name = "message"),
108 inverseJoinColumns = @JoinColumn(name = "target"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200109 @JsonbTransient
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +0100110 public Set<Role> targets;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100111
Matthias Andreas Benkard0351a8f2022-05-26 08:05:00 +0200112 @ManyToMany(mappedBy = "referees")
113 @JsonbTransient
114 public Collection<LazychatMessage> referrers;
115
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200116 @ManyToMany(mappedBy = "referees")
Matthias Andreas Benkard0b2aa3b2022-06-06 09:42:35 +0200117 @OrderBy("date ASC")
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200118 @Where(clause = "scope = 'comment'")
119 @JsonbTransient
120 public Collection<LazychatMessage> comments;
121
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200122 @OneToMany(
123 mappedBy = "post",
124 fetch = FetchType.LAZY,
125 cascade = CascadeType.ALL,
126 targetEntity = PostText.class)
127 @MapKey(name = "language")
128 public Map<String, Text> texts = new HashMap<>();
129
130 public Map<String, Text> getTexts() {
131 return texts;
132 }
133
Matthias Andreas Benkard371164a2020-03-23 06:21:25 +0100134 public abstract boolean isBookmark();
135
136 public abstract boolean isLazychatMessage();
137
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200138 @CheckForNull
139 public abstract String getTitle();
140
141 @CheckForNull
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200142 public abstract String getUri();
143
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200144 public Visibility getVisibility() {
145 if (targets.isEmpty()) {
146 return Visibility.PRIVATE;
147 } else if (targets.contains(Role.getWorld())) {
148 return Visibility.PUBLIC;
149 } else {
150 // FIXME: There should really be a check whether targets.equals(owner.defaultTargets) here.
151 // Otherwise the actual visibility is DISCRETIONARY.
152 return Visibility.SEMIPRIVATE;
153 }
154 }
155
Matthias Andreas Benkarde3bc3ee2023-08-06 16:21:11 +0200156 protected static <T extends Post<?>> CriteriaBuilder<T> queryViewable(
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100157 Class<T> entityClass,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200158 @CheckForNull User reader,
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100159 @CheckForNull User owner,
160 @CheckForNull Integer cursor,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100161 EntityManager em,
162 CriteriaBuilderFactory cbf,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200163 boolean forward,
164 @CheckForNull String searchQuery) {
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100165
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100166 CriteriaBuilder<T> cb = cbf.create(em, entityClass).select("post");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100167
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200168 if (reader == null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100169 cb =
170 cb.from(entityClass, "post")
171 .innerJoin("post.targets", "role")
172 .where("'world'")
173 .isMemberOf("role.tags");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100174 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100175 cb = cb.from(User.class, "user").where("user").eq(reader);
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200176 if (entityClass.isAssignableFrom(Post.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100177 cb = cb.innerJoin("user.visiblePosts", "post");
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200178 } else if (entityClass.isAssignableFrom(Bookmark.class)) {
Matthias Andreas Benkard6fca8dc2022-04-09 07:12:57 +0200179 cb = cb.innerJoin("user.visibleBookmarks", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200180 } else if (entityClass.isAssignableFrom(LazychatMessage.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100181 cb = cb.innerJoin("user.visibleLazychatMessages", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200182 } else {
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200183 throw new IllegalArgumentException();
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100184 }
185 }
186
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100187 cb = cb.fetch("post.owner");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100188
189 if (owner != null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100190 cb = cb.where("post.owner").eq(owner);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100191 }
192
193 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100194 cb = cb.orderByDesc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100195 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100196 cb = cb.orderByAsc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100197 }
198
199 if (cursor != null) {
200 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100201 cb = cb.where("post.id").le(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100202 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100203 cb = cb.where("post.id").gt(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100204 }
205 }
206
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200207 if (searchQuery != null && !searchQuery.isBlank()) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100208 cb =
209 cb.whereExists()
210 .from(PostText.class, "postText")
211 .where("postText.post")
212 .eqExpression("post")
213 .whereOr()
214 .whereExpression(
215 "post_matches_websearch(postText.searchTerms, 'de', :searchQueryText) = true")
216 .whereExpression(
217 "post_matches_websearch(postText.searchTerms, 'en', :searchQueryText) = true")
218 .endOr()
219 .end()
220 .setParameter("searchQueryText", searchQuery);
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200221 }
222
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100223 cb = cb.where("post.scope").eq(Scope.top_level);
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100224
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200225 cb = cb.leftJoinFetch("post.comments", "comment");
226 cb = cb.fetch("comment.texts");
227
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100228 return cb;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100229 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100230
Matthias Andreas Benkard6cfe16b2020-04-18 15:36:04 +0200231 public final boolean isVisibleTo(@Nullable User user) {
232 // FIXME: Make this more efficient.
233 return getVisibility() == Visibility.PUBLIC || (user != null && visibleTo.contains(user));
234 }
235
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200236 @CheckForNull
237 public final String getDescriptionHtml() {
238 var text = getText();
239 if (text == null) {
240 return null;
241 }
242 return text.getDescriptionHtml();
243 }
244
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100245 public final boolean isTopLevel() {
246 return scope == Scope.top_level;
247 }
248
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200249 public static class Day<T extends Post<? extends PostText<?>>> {
250 public final @CheckForNull LocalDate date;
251 public final List<T> posts;
252
253 private Day(LocalDate date, List<T> posts) {
254 this.date = date;
255 this.posts = posts;
256 }
257
258 public void cacheDescriptions() {
259 for (var post : posts) {
260 post.getTexts().values().forEach(PostText::getDescriptionHtml);
261 }
262 }
263 }
264
265 public static class PostPage<T extends Post<? extends PostText<?>>> {
Matthias Andreas Benkard593765d2020-04-18 20:44:07 +0200266 public @CheckForNull final Integer prevCursor;
267 public @CheckForNull final Integer cursor;
268 public @CheckForNull final Integer nextCursor;
269 public final List<T> posts;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100270
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200271 private static final TimeZone timeZone = TimeZone.getDefault();
272
273 public PostPage(
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100274 @CheckForNull Integer c0,
275 @CheckForNull Integer c1,
276 @CheckForNull Integer c2,
277 List<T> resultList) {
278 this.prevCursor = c0;
279 this.cursor = c1;
280 this.nextCursor = c2;
281 this.posts = resultList;
282 }
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200283
Matthias Andreas Benkard60c08922020-06-13 19:22:25 +0200284 public void cacheDescriptions() {
285 days().forEach(Day::cacheDescriptions);
286 }
287
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200288 public List<Day<T>> days() {
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200289 return posts.stream()
290 .collect(Collectors.groupingBy(post -> post.date.toLocalDate()))
291 .entrySet()
292 .stream()
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200293 .map(x -> new Day<T>(x.getKey(), x.getValue()))
294 .sorted(Comparator.comparing((Day<T> day) -> day.date).reversed())
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200295 .collect(Collectors.toUnmodifiableList());
296 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100297 }
298
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200299 public static PostPage<Post<? extends PostText<?>>> findViewable(
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100300 PostFilter postFilter,
301 EntityManager em,
302 CriteriaBuilderFactory cbf,
303 @CheckForNull User viewer,
304 @CheckForNull User owner) {
305 return findViewable(postFilter, em, cbf, viewer, owner, null, null, null);
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200306 }
307
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200308 public static PostPage<Post<? extends PostText<?>>> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200309 PostFilter postFilter,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100310 EntityManager em,
311 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200312 @CheckForNull User viewer,
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200313 @CheckForNull User owner,
314 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200315 @CheckForNull Integer count,
316 @CheckForNull String searchQuery) {
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200317 Class<? extends Post> entityClass;
318 switch (postFilter) {
319 case BOOKMARKS_ONLY:
320 entityClass = Bookmark.class;
321 break;
322 case LAZYCHAT_MESSAGES_ONLY:
323 entityClass = LazychatMessage.class;
324 break;
325 default:
326 entityClass = Post.class;
327 }
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100328 return findViewable(entityClass, em, cbf, viewer, owner, cursor, count, searchQuery);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100329 }
330
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200331 protected static <T extends Post<? extends PostText<?>>> PostPage<T> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200332 Class<? extends T> entityClass,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100333 EntityManager em,
334 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200335 @CheckForNull User viewer,
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100336 @CheckForNull User owner,
337 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200338 @CheckForNull Integer count,
339 @CheckForNull String searchQuery) {
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100340
341 if (cursor != null) {
342 Objects.requireNonNull(count);
343 }
344
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100345 var forwardCriteria =
346 queryViewable(entityClass, viewer, owner, cursor, em, cbf, true, searchQuery);
347 var forwardQuery = forwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100348
349 if (count != null) {
350 forwardQuery.setMaxResults(count + 1);
351 }
352
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100353 @CheckForNull Integer prevCursor = null;
354 @CheckForNull Integer nextCursor = null;
355
356 if (cursor != null) {
357 // Look backwards as well so we can find the prevCursor.
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200358 var backwardCriteria =
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100359 queryViewable(entityClass, viewer, owner, cursor, em, cbf, false, searchQuery);
360 var backwardQuery = backwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100361 backwardQuery.setMaxResults(count);
362 var backwardResults = backwardQuery.getResultList();
363 if (!backwardResults.isEmpty()) {
364 prevCursor = backwardResults.get(backwardResults.size() - 1).id;
365 }
366 }
367
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100368 var forwardResults = new ArrayList<T>(forwardQuery.getResultList());
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100369 if (count != null) {
370 if (forwardResults.size() == count + 1) {
371 nextCursor = forwardResults.get(count).id;
372 forwardResults.remove((int) count);
373 }
374 }
375
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200376 // Fetch texts (to avoid n+1 selects).
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100377 fetchTexts(forwardResults);
378
379 return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults);
380 }
381
382 public static <T extends Post<?>> void fetchTexts(Collection<T> posts) {
383 var postIds = posts.stream().map(x -> x.id).collect(toList());
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200384
385 if (!postIds.isEmpty()) {
386 find("SELECT p FROM Post p LEFT JOIN FETCH p.texts WHERE p.id IN (?1)", postIds).stream()
387 .count();
388 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100389 }
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200390
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200391 @CheckForNull
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100392 public Text getText() {
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200393 var texts = getTexts();
394 if (texts.isEmpty()) {
395 return null;
396 } else if (texts.containsKey("")) {
397 return texts.get("");
398 } else if (texts.containsKey("en")) {
399 return texts.get("en");
400 } else {
401 return texts.values().stream().findAny().get();
402 }
403 }
404
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200405 public enum Visibility {
406 PUBLIC,
407 SEMIPRIVATE,
408 DISCRETIONARY,
409 PRIVATE,
410 }
411
412 @Override
413 public boolean equals(Object o) {
414 if (this == o) {
415 return true;
416 }
417 if (!(o instanceof Post)) {
418 return false;
419 }
Matthias Andreas Benkardde46b232021-02-06 07:51:57 +0100420 Post<?> post = (Post<?>) o;
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200421 return Objects.equals(id, post.id);
422 }
423
424 @Override
425 public int hashCode() {
426 return Objects.hash(id);
427 }
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +0100428}