blob: 20fcabedee3b69a1753d1f0bbf95522049196cd1 [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.stream.Collectors;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010048import javax.annotation.CheckForNull;
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010049import org.hibernate.annotations.Type;
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +020050import org.hibernate.annotations.Where;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010051
52@Entity
Matthias Andreas Benkard57c9a8a2020-01-24 19:09:38 +010053@Table(name = "posts", schema = "benki")
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010054@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +020055public abstract class Post extends PanacheEntityBase {
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010056
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010057 public enum Scope {
58 top_level,
59 comment
60 }
61
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010062 @Id
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010063 @SequenceGenerator(
64 allocationSize = 1,
65 sequenceName = "posts_id_seq",
66 name = "posts_id_seq",
67 schema = "benki")
68 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "posts_id_seq")
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010069 @Column(name = "id", nullable = false)
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010070 public Integer id;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010071
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010072 @Column(name = "date", nullable = true)
Matthias Andreas Benkard1e7674c2020-04-18 20:28:51 +020073 @CheckForNull
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010074 public OffsetDateTime date;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010075
Matthias Andreas Benkard2fa30292023-08-06 21:21:50 +020076 @Column(nullable = false, columnDefinition = "benki.post_scope")
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010077 @Enumerated(EnumType.STRING)
Matthias Andreas Benkarde3bc3ee2023-08-06 16:21:11 +020078 @Type(PostgreSQLEnumType.class)
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010079 public Scope scope = Scope.top_level;
80
Matthias Andreas Benkardaa754802020-01-24 11:55:26 +010081 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010082 @JoinColumn(name = "newsletter", referencedColumnName = "id", nullable = true)
83 @CheckForNull
84 @JsonbTransient
85 public Newsletter newsletter;
86
87 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010088 @JoinColumn(name = "owner", referencedColumnName = "id")
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +020089 @CheckForNull
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020090 @JsonbTransient
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +010091 public User owner;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010092
93 @ManyToMany(fetch = FetchType.LAZY)
94 @JoinTable(
95 name = "user_visible_posts",
Matthias Andreas Benkard553de3e2020-01-27 05:33:15 +010096 schema = "benki",
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010097 joinColumns = @JoinColumn(name = "message"),
98 inverseJoinColumns = @JoinColumn(name = "user"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020099 @JsonbTransient
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +0100100 public Set<User> visibleTo;
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +0100101
102 @ManyToMany(fetch = FetchType.LAZY)
103 @JoinTable(
104 name = "post_targets",
105 schema = "benki",
106 joinColumns = @JoinColumn(name = "message"),
107 inverseJoinColumns = @JoinColumn(name = "target"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200108 @JsonbTransient
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +0100109 public Set<Role> targets;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100110
Matthias Andreas Benkard0351a8f2022-05-26 08:05:00 +0200111 @ManyToMany(mappedBy = "referees")
112 @JsonbTransient
113 public Collection<LazychatMessage> referrers;
114
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200115 @ManyToMany(mappedBy = "referees")
Matthias Andreas Benkard0b2aa3b2022-06-06 09:42:35 +0200116 @OrderBy("date ASC")
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200117 @Where(clause = "scope = 'comment'")
118 @JsonbTransient
119 public Collection<LazychatMessage> comments;
120
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200121 @OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200122 @MapKey(name = "language")
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200123 public Map<String, PostText> texts = new HashMap<>();
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200124
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200125 public Map<String, PostText> getTexts() {
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200126 return texts;
127 }
128
Matthias Andreas Benkard371164a2020-03-23 06:21:25 +0100129 public abstract boolean isBookmark();
130
131 public abstract boolean isLazychatMessage();
132
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200133 @CheckForNull
134 public abstract String getTitle();
135
136 @CheckForNull
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200137 public abstract String getUri();
138
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200139 public Visibility getVisibility() {
140 if (targets.isEmpty()) {
141 return Visibility.PRIVATE;
142 } else if (targets.contains(Role.getWorld())) {
143 return Visibility.PUBLIC;
144 } else {
145 // FIXME: There should really be a check whether targets.equals(owner.defaultTargets) here.
146 // Otherwise the actual visibility is DISCRETIONARY.
147 return Visibility.SEMIPRIVATE;
148 }
149 }
150
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200151 protected static <T extends Post> CriteriaBuilder<T> queryViewable(
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100152 Class<T> entityClass,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200153 @CheckForNull User reader,
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100154 @CheckForNull User owner,
155 @CheckForNull Integer cursor,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100156 EntityManager em,
157 CriteriaBuilderFactory cbf,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200158 boolean forward,
159 @CheckForNull String searchQuery) {
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100160
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100161 CriteriaBuilder<T> cb = cbf.create(em, entityClass).select("post");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100162
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200163 if (reader == null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100164 cb =
165 cb.from(entityClass, "post")
166 .innerJoin("post.targets", "role")
167 .where("'world'")
168 .isMemberOf("role.tags");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100169 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100170 cb = cb.from(User.class, "user").where("user").eq(reader);
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200171 if (entityClass.isAssignableFrom(Post.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100172 cb = cb.innerJoin("user.visiblePosts", "post");
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200173 } else if (entityClass.isAssignableFrom(Bookmark.class)) {
Matthias Andreas Benkard6fca8dc2022-04-09 07:12:57 +0200174 cb = cb.innerJoin("user.visibleBookmarks", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200175 } else if (entityClass.isAssignableFrom(LazychatMessage.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100176 cb = cb.innerJoin("user.visibleLazychatMessages", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200177 } else {
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200178 throw new IllegalArgumentException();
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100179 }
180 }
181
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100182 cb = cb.fetch("post.owner");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100183
184 if (owner != null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100185 cb = cb.where("post.owner").eq(owner);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100186 }
187
188 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100189 cb = cb.orderByDesc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100190 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100191 cb = cb.orderByAsc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100192 }
193
194 if (cursor != null) {
195 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100196 cb = cb.where("post.id").le(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100197 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100198 cb = cb.where("post.id").gt(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100199 }
200 }
201
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200202 if (searchQuery != null && !searchQuery.isBlank()) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100203 cb =
204 cb.whereExists()
205 .from(PostText.class, "postText")
206 .where("postText.post")
207 .eqExpression("post")
208 .whereOr()
209 .whereExpression(
210 "post_matches_websearch(postText.searchTerms, 'de', :searchQueryText) = true")
211 .whereExpression(
212 "post_matches_websearch(postText.searchTerms, 'en', :searchQueryText) = true")
213 .endOr()
214 .end()
215 .setParameter("searchQueryText", searchQuery);
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200216 }
217
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100218 cb = cb.where("post.scope").eq(Scope.top_level);
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100219
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200220 cb = cb.leftJoinFetch("post.comments", "comment");
221 cb = cb.fetch("comment.texts");
222
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100223 return cb;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100224 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100225
Matthias Andreas Benkard6cfe16b2020-04-18 15:36:04 +0200226 public final boolean isVisibleTo(@Nullable User user) {
227 // FIXME: Make this more efficient.
228 return getVisibility() == Visibility.PUBLIC || (user != null && visibleTo.contains(user));
229 }
230
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200231 @CheckForNull
232 public final String getDescriptionHtml() {
233 var text = getText();
234 if (text == null) {
235 return null;
236 }
237 return text.getDescriptionHtml();
238 }
239
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100240 public final boolean isTopLevel() {
241 return scope == Scope.top_level;
242 }
243
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200244 public static class Day<T extends Post> {
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200245 public final @CheckForNull LocalDate date;
246 public final List<T> posts;
247
248 private Day(LocalDate date, List<T> posts) {
249 this.date = date;
250 this.posts = posts;
251 }
252
253 public void cacheDescriptions() {
254 for (var post : posts) {
255 post.getTexts().values().forEach(PostText::getDescriptionHtml);
256 }
257 }
258 }
259
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200260 public static class PostPage<T extends Post> {
Matthias Andreas Benkard593765d2020-04-18 20:44:07 +0200261 public @CheckForNull final Integer prevCursor;
262 public @CheckForNull final Integer cursor;
263 public @CheckForNull final Integer nextCursor;
264 public final List<T> posts;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100265
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200266 public PostPage(
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100267 @CheckForNull Integer c0,
268 @CheckForNull Integer c1,
269 @CheckForNull Integer c2,
270 List<T> resultList) {
271 this.prevCursor = c0;
272 this.cursor = c1;
273 this.nextCursor = c2;
274 this.posts = resultList;
275 }
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200276
Matthias Andreas Benkard60c08922020-06-13 19:22:25 +0200277 public void cacheDescriptions() {
278 days().forEach(Day::cacheDescriptions);
279 }
280
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200281 public List<Day<T>> days() {
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200282 return posts.stream()
283 .collect(Collectors.groupingBy(post -> post.date.toLocalDate()))
284 .entrySet()
285 .stream()
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200286 .map(x -> new Day<T>(x.getKey(), x.getValue()))
287 .sorted(Comparator.comparing((Day<T> day) -> day.date).reversed())
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200288 .collect(Collectors.toUnmodifiableList());
289 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100290 }
291
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200292 public static PostPage<Post> findViewable(
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100293 PostFilter postFilter,
294 EntityManager em,
295 CriteriaBuilderFactory cbf,
296 @CheckForNull User viewer,
297 @CheckForNull User owner) {
298 return findViewable(postFilter, em, cbf, viewer, owner, null, null, null);
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200299 }
300
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200301 public static PostPage<Post> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200302 PostFilter postFilter,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100303 EntityManager em,
304 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200305 @CheckForNull User viewer,
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200306 @CheckForNull User owner,
307 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200308 @CheckForNull Integer count,
309 @CheckForNull String searchQuery) {
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200310 Class<? extends Post> entityClass;
311 switch (postFilter) {
312 case BOOKMARKS_ONLY:
313 entityClass = Bookmark.class;
314 break;
315 case LAZYCHAT_MESSAGES_ONLY:
316 entityClass = LazychatMessage.class;
317 break;
318 default:
319 entityClass = Post.class;
320 }
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100321 return findViewable(entityClass, em, cbf, viewer, owner, cursor, count, searchQuery);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100322 }
323
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200324 protected static <T extends Post> PostPage<T> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200325 Class<? extends T> entityClass,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100326 EntityManager em,
327 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200328 @CheckForNull User viewer,
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100329 @CheckForNull User owner,
330 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200331 @CheckForNull Integer count,
332 @CheckForNull String searchQuery) {
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100333
334 if (cursor != null) {
335 Objects.requireNonNull(count);
336 }
337
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100338 var forwardCriteria =
339 queryViewable(entityClass, viewer, owner, cursor, em, cbf, true, searchQuery);
340 var forwardQuery = forwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100341
342 if (count != null) {
343 forwardQuery.setMaxResults(count + 1);
344 }
345
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100346 @CheckForNull Integer prevCursor = null;
347 @CheckForNull Integer nextCursor = null;
348
349 if (cursor != null) {
350 // Look backwards as well so we can find the prevCursor.
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200351 var backwardCriteria =
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100352 queryViewable(entityClass, viewer, owner, cursor, em, cbf, false, searchQuery);
353 var backwardQuery = backwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100354 backwardQuery.setMaxResults(count);
355 var backwardResults = backwardQuery.getResultList();
356 if (!backwardResults.isEmpty()) {
357 prevCursor = backwardResults.get(backwardResults.size() - 1).id;
358 }
359 }
360
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100361 var forwardResults = new ArrayList<T>(forwardQuery.getResultList());
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100362 if (count != null) {
363 if (forwardResults.size() == count + 1) {
364 nextCursor = forwardResults.get(count).id;
365 forwardResults.remove((int) count);
366 }
367 }
368
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200369 // Fetch texts (to avoid n+1 selects).
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100370 fetchTexts(forwardResults);
371
372 return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults);
373 }
374
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200375 public static <T extends Post> void fetchTexts(Collection<T> posts) {
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100376 var postIds = posts.stream().map(x -> x.id).collect(toList());
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200377
378 if (!postIds.isEmpty()) {
379 find("SELECT p FROM Post p LEFT JOIN FETCH p.texts WHERE p.id IN (?1)", postIds).stream()
380 .count();
381 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100382 }
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200383
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200384 @CheckForNull
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200385 public PostText getText() {
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200386 var texts = getTexts();
387 if (texts.isEmpty()) {
388 return null;
389 } else if (texts.containsKey("")) {
390 return texts.get("");
391 } else if (texts.containsKey("en")) {
392 return texts.get("en");
393 } else {
394 return texts.values().stream().findAny().get();
395 }
396 }
397
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200398 public enum Visibility {
399 PUBLIC,
400 SEMIPRIVATE,
401 DISCRETIONARY,
402 PRIVATE,
403 }
404
405 @Override
406 public boolean equals(Object o) {
407 if (this == o) {
408 return true;
409 }
410 if (!(o instanceof Post)) {
411 return false;
412 }
Matthias Andreas Benkard475bf002023-08-06 20:56:30 +0200413 Post post = (Post) o;
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200414 return Objects.equals(id, post.id);
415 }
416
417 @Override
418 public int hashCode() {
419 return Objects.hash(id);
420 }
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +0100421}