blob: c2f3dbcaf0b24b0398452e6332bffc61e1a9791d [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 Benkard67c60672021-01-30 14:43:39 +01007import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +01008import eu.mulk.mulkcms2.benki.accesscontrol.Role;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +01009import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
10import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010011import eu.mulk.mulkcms2.benki.newsletter.Newsletter;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010012import eu.mulk.mulkcms2.benki.users.User;
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +010013import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +020014import java.time.LocalDate;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010015import java.time.OffsetDateTime;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010016import java.util.ArrayList;
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010017import java.util.Collection;
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +020018import java.util.Comparator;
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020019import java.util.HashMap;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010020import java.util.List;
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020021import java.util.Map;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010022import java.util.Objects;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010023import java.util.Set;
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +020024import java.util.TimeZone;
25import java.util.stream.Collectors;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010026import javax.annotation.CheckForNull;
Matthias Andreas Benkard6cfe16b2020-04-18 15:36:04 +020027import javax.annotation.Nullable;
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020028import javax.json.bind.annotation.JsonbTransient;
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020029import javax.persistence.CascadeType;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010030import javax.persistence.Column;
31import javax.persistence.Entity;
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +010032import javax.persistence.EntityManager;
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010033import javax.persistence.EnumType;
34import javax.persistence.Enumerated;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010035import javax.persistence.FetchType;
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010036import javax.persistence.GeneratedValue;
37import javax.persistence.GenerationType;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010038import javax.persistence.Id;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010039import javax.persistence.Inheritance;
40import javax.persistence.InheritanceType;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010041import javax.persistence.JoinColumn;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010042import javax.persistence.JoinTable;
43import javax.persistence.ManyToMany;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010044import javax.persistence.ManyToOne;
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020045import javax.persistence.MapKey;
46import javax.persistence.OneToMany;
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +020047import javax.persistence.OrderBy;
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010048import javax.persistence.SequenceGenerator;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010049import javax.persistence.Table;
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010050import org.hibernate.annotations.Type;
51import org.hibernate.annotations.TypeDef;
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +020052import org.hibernate.annotations.Where;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010053
54@Entity
Matthias Andreas Benkard57c9a8a2020-01-24 19:09:38 +010055@Table(name = "posts", schema = "benki")
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010056@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010057@TypeDef(name = "pg_enum", typeClass = PostgreSQLEnumType.class)
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +020058public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010059
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010060 public enum Scope {
61 top_level,
62 comment
63 }
64
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010065 @Id
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010066 @SequenceGenerator(
67 allocationSize = 1,
68 sequenceName = "posts_id_seq",
69 name = "posts_id_seq",
70 schema = "benki")
71 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "posts_id_seq")
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010072 @Column(name = "id", nullable = false)
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010073 public Integer id;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010074
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010075 @Column(name = "date", nullable = true)
Matthias Andreas Benkard1e7674c2020-04-18 20:28:51 +020076 @CheckForNull
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010077 public OffsetDateTime date;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010078
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010079 @Column(nullable = false)
80 @Enumerated(EnumType.STRING)
81 @Type(type = "pg_enum")
82 public Scope scope = Scope.top_level;
83
Matthias Andreas Benkardaa754802020-01-24 11:55:26 +010084 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +010085 @JoinColumn(name = "newsletter", referencedColumnName = "id", nullable = true)
86 @CheckForNull
87 @JsonbTransient
88 public Newsletter newsletter;
89
90 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010091 @JoinColumn(name = "owner", referencedColumnName = "id")
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +020092 @CheckForNull
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020093 @JsonbTransient
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +010094 public User owner;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010095
96 @ManyToMany(fetch = FetchType.LAZY)
97 @JoinTable(
98 name = "user_visible_posts",
Matthias Andreas Benkard553de3e2020-01-27 05:33:15 +010099 schema = "benki",
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +0100100 joinColumns = @JoinColumn(name = "message"),
101 inverseJoinColumns = @JoinColumn(name = "user"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200102 @JsonbTransient
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +0100103 public Set<User> visibleTo;
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +0100104
105 @ManyToMany(fetch = FetchType.LAZY)
106 @JoinTable(
107 name = "post_targets",
108 schema = "benki",
109 joinColumns = @JoinColumn(name = "message"),
110 inverseJoinColumns = @JoinColumn(name = "target"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200111 @JsonbTransient
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +0100112 public Set<Role> targets;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100113
Matthias Andreas Benkard0351a8f2022-05-26 08:05:00 +0200114 @ManyToMany(mappedBy = "referees")
115 @JsonbTransient
116 public Collection<LazychatMessage> referrers;
117
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200118 @ManyToMany(mappedBy = "referees")
Matthias Andreas Benkard0b2aa3b2022-06-06 09:42:35 +0200119 @OrderBy("date ASC")
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200120 @Where(clause = "scope = 'comment'")
121 @JsonbTransient
122 public Collection<LazychatMessage> comments;
123
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200124 @OneToMany(
125 mappedBy = "post",
126 fetch = FetchType.LAZY,
127 cascade = CascadeType.ALL,
128 targetEntity = PostText.class)
129 @MapKey(name = "language")
130 public Map<String, Text> texts = new HashMap<>();
131
132 public Map<String, Text> getTexts() {
133 return texts;
134 }
135
Matthias Andreas Benkard371164a2020-03-23 06:21:25 +0100136 public abstract boolean isBookmark();
137
138 public abstract boolean isLazychatMessage();
139
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200140 @CheckForNull
141 public abstract String getTitle();
142
143 @CheckForNull
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200144 public abstract String getUri();
145
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200146 public Visibility getVisibility() {
147 if (targets.isEmpty()) {
148 return Visibility.PRIVATE;
149 } else if (targets.contains(Role.getWorld())) {
150 return Visibility.PUBLIC;
151 } else {
152 // FIXME: There should really be a check whether targets.equals(owner.defaultTargets) here.
153 // Otherwise the actual visibility is DISCRETIONARY.
154 return Visibility.SEMIPRIVATE;
155 }
156 }
157
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100158 protected static <T extends Post> CriteriaBuilder<T> queryViewable(
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100159 Class<T> entityClass,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200160 @CheckForNull User reader,
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100161 @CheckForNull User owner,
162 @CheckForNull Integer cursor,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100163 EntityManager em,
164 CriteriaBuilderFactory cbf,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200165 boolean forward,
166 @CheckForNull String searchQuery) {
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100167
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100168 CriteriaBuilder<T> cb = cbf.create(em, entityClass).select("post");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100169
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200170 if (reader == null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100171 cb =
172 cb.from(entityClass, "post")
173 .innerJoin("post.targets", "role")
174 .where("'world'")
175 .isMemberOf("role.tags");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100176 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100177 cb = cb.from(User.class, "user").where("user").eq(reader);
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200178 if (entityClass.isAssignableFrom(Post.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100179 cb = cb.innerJoin("user.visiblePosts", "post");
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200180 } else if (entityClass.isAssignableFrom(Bookmark.class)) {
Matthias Andreas Benkard6fca8dc2022-04-09 07:12:57 +0200181 cb = cb.innerJoin("user.visibleBookmarks", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200182 } else if (entityClass.isAssignableFrom(LazychatMessage.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100183 cb = cb.innerJoin("user.visibleLazychatMessages", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200184 } else {
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200185 throw new IllegalArgumentException();
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100186 }
187 }
188
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100189 cb = cb.fetch("post.owner");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100190
191 if (owner != null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100192 cb = cb.where("post.owner").eq(owner);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100193 }
194
195 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100196 cb = cb.orderByDesc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100197 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100198 cb = cb.orderByAsc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100199 }
200
201 if (cursor != null) {
202 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100203 cb = cb.where("post.id").le(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100204 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100205 cb = cb.where("post.id").gt(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100206 }
207 }
208
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200209 if (searchQuery != null && !searchQuery.isBlank()) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100210 cb =
211 cb.whereExists()
212 .from(PostText.class, "postText")
213 .where("postText.post")
214 .eqExpression("post")
215 .whereOr()
216 .whereExpression(
217 "post_matches_websearch(postText.searchTerms, 'de', :searchQueryText) = true")
218 .whereExpression(
219 "post_matches_websearch(postText.searchTerms, 'en', :searchQueryText) = true")
220 .endOr()
221 .end()
222 .setParameter("searchQueryText", searchQuery);
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200223 }
224
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100225 cb = cb.where("post.scope").eq(Scope.top_level);
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100226
Matthias Andreas Benkard8dcc6ae2022-06-04 16:02:25 +0200227 cb = cb.leftJoinFetch("post.comments", "comment");
228 cb = cb.fetch("comment.texts");
229
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100230 return cb;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100231 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100232
Matthias Andreas Benkard6cfe16b2020-04-18 15:36:04 +0200233 public final boolean isVisibleTo(@Nullable User user) {
234 // FIXME: Make this more efficient.
235 return getVisibility() == Visibility.PUBLIC || (user != null && visibleTo.contains(user));
236 }
237
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200238 @CheckForNull
239 public final String getDescriptionHtml() {
240 var text = getText();
241 if (text == null) {
242 return null;
243 }
244 return text.getDescriptionHtml();
245 }
246
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100247 public final boolean isTopLevel() {
248 return scope == Scope.top_level;
249 }
250
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200251 public static class Day<T extends Post<? extends PostText<?>>> {
252 public final @CheckForNull LocalDate date;
253 public final List<T> posts;
254
255 private Day(LocalDate date, List<T> posts) {
256 this.date = date;
257 this.posts = posts;
258 }
259
260 public void cacheDescriptions() {
261 for (var post : posts) {
262 post.getTexts().values().forEach(PostText::getDescriptionHtml);
263 }
264 }
265 }
266
267 public static class PostPage<T extends Post<? extends PostText<?>>> {
Matthias Andreas Benkard593765d2020-04-18 20:44:07 +0200268 public @CheckForNull final Integer prevCursor;
269 public @CheckForNull final Integer cursor;
270 public @CheckForNull final Integer nextCursor;
271 public final List<T> posts;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100272
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200273 private static final TimeZone timeZone = TimeZone.getDefault();
274
275 public PostPage(
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100276 @CheckForNull Integer c0,
277 @CheckForNull Integer c1,
278 @CheckForNull Integer c2,
279 List<T> resultList) {
280 this.prevCursor = c0;
281 this.cursor = c1;
282 this.nextCursor = c2;
283 this.posts = resultList;
284 }
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200285
Matthias Andreas Benkard60c08922020-06-13 19:22:25 +0200286 public void cacheDescriptions() {
287 days().forEach(Day::cacheDescriptions);
288 }
289
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200290 public List<Day<T>> days() {
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200291 return posts.stream()
292 .collect(Collectors.groupingBy(post -> post.date.toLocalDate()))
293 .entrySet()
294 .stream()
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200295 .map(x -> new Day<T>(x.getKey(), x.getValue()))
296 .sorted(Comparator.comparing((Day<T> day) -> day.date).reversed())
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200297 .collect(Collectors.toUnmodifiableList());
298 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100299 }
300
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200301 public static PostPage<Post<? extends PostText<?>>> findViewable(
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100302 PostFilter postFilter,
303 EntityManager em,
304 CriteriaBuilderFactory cbf,
305 @CheckForNull User viewer,
306 @CheckForNull User owner) {
307 return findViewable(postFilter, em, cbf, viewer, owner, null, null, null);
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200308 }
309
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200310 public static PostPage<Post<? extends PostText<?>>> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200311 PostFilter postFilter,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100312 EntityManager em,
313 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200314 @CheckForNull User viewer,
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200315 @CheckForNull User owner,
316 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200317 @CheckForNull Integer count,
318 @CheckForNull String searchQuery) {
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200319 Class<? extends Post> entityClass;
320 switch (postFilter) {
321 case BOOKMARKS_ONLY:
322 entityClass = Bookmark.class;
323 break;
324 case LAZYCHAT_MESSAGES_ONLY:
325 entityClass = LazychatMessage.class;
326 break;
327 default:
328 entityClass = Post.class;
329 }
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100330 return findViewable(entityClass, em, cbf, viewer, owner, cursor, count, searchQuery);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100331 }
332
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200333 protected static <T extends Post<? extends PostText<?>>> PostPage<T> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200334 Class<? extends T> entityClass,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100335 EntityManager em,
336 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200337 @CheckForNull User viewer,
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100338 @CheckForNull User owner,
339 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200340 @CheckForNull Integer count,
341 @CheckForNull String searchQuery) {
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100342
343 if (cursor != null) {
344 Objects.requireNonNull(count);
345 }
346
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100347 var forwardCriteria =
348 queryViewable(entityClass, viewer, owner, cursor, em, cbf, true, searchQuery);
349 var forwardQuery = forwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100350
351 if (count != null) {
352 forwardQuery.setMaxResults(count + 1);
353 }
354
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100355 @CheckForNull Integer prevCursor = null;
356 @CheckForNull Integer nextCursor = null;
357
358 if (cursor != null) {
359 // Look backwards as well so we can find the prevCursor.
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200360 var backwardCriteria =
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100361 queryViewable(entityClass, viewer, owner, cursor, em, cbf, false, searchQuery);
362 var backwardQuery = backwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100363 backwardQuery.setMaxResults(count);
364 var backwardResults = backwardQuery.getResultList();
365 if (!backwardResults.isEmpty()) {
366 prevCursor = backwardResults.get(backwardResults.size() - 1).id;
367 }
368 }
369
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100370 var forwardResults = new ArrayList<T>(forwardQuery.getResultList());
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100371 if (count != null) {
372 if (forwardResults.size() == count + 1) {
373 nextCursor = forwardResults.get(count).id;
374 forwardResults.remove((int) count);
375 }
376 }
377
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200378 // Fetch texts (to avoid n+1 selects).
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100379 fetchTexts(forwardResults);
380
381 return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults);
382 }
383
384 public static <T extends Post<?>> void fetchTexts(Collection<T> posts) {
385 var postIds = posts.stream().map(x -> x.id).collect(toList());
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200386
387 if (!postIds.isEmpty()) {
388 find("SELECT p FROM Post p LEFT JOIN FETCH p.texts WHERE p.id IN (?1)", postIds).stream()
389 .count();
390 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100391 }
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200392
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200393 @CheckForNull
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100394 public Text getText() {
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200395 var texts = getTexts();
396 if (texts.isEmpty()) {
397 return null;
398 } else if (texts.containsKey("")) {
399 return texts.get("");
400 } else if (texts.containsKey("en")) {
401 return texts.get("en");
402 } else {
403 return texts.values().stream().findAny().get();
404 }
405 }
406
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200407 public enum Visibility {
408 PUBLIC,
409 SEMIPRIVATE,
410 DISCRETIONARY,
411 PRIVATE,
412 }
413
414 @Override
415 public boolean equals(Object o) {
416 if (this == o) {
417 return true;
418 }
419 if (!(o instanceof Post)) {
420 return false;
421 }
Matthias Andreas Benkardde46b232021-02-06 07:51:57 +0100422 Post<?> post = (Post<?>) o;
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200423 return Objects.equals(id, post.id);
424 }
425
426 @Override
427 public int hashCode() {
428 return Objects.hash(id);
429 }
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +0100430}