blob: 02a11faa86bb75eb6a91f09d06660855f6bfb469 [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 Benkard0246c3e2020-01-27 05:39:08 +010047import javax.persistence.SequenceGenerator;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010048import javax.persistence.Table;
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +010049import org.hibernate.annotations.Type;
50import org.hibernate.annotations.TypeDef;
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 Benkard67c60672021-01-30 14:43:39 +010055@TypeDef(name = "pg_enum", typeClass = PostgreSQLEnumType.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)
79 @Type(type = "pg_enum")
80 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 Benkardd5498fc2020-08-23 21:51:00 +0200112 @OneToMany(
113 mappedBy = "post",
114 fetch = FetchType.LAZY,
115 cascade = CascadeType.ALL,
116 targetEntity = PostText.class)
117 @MapKey(name = "language")
118 public Map<String, Text> texts = new HashMap<>();
119
120 public Map<String, Text> getTexts() {
121 return texts;
122 }
123
Matthias Andreas Benkard371164a2020-03-23 06:21:25 +0100124 public abstract boolean isBookmark();
125
126 public abstract boolean isLazychatMessage();
127
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200128 @CheckForNull
129 public abstract String getTitle();
130
131 @CheckForNull
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200132 public abstract String getUri();
133
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200134 public Visibility getVisibility() {
135 if (targets.isEmpty()) {
136 return Visibility.PRIVATE;
137 } else if (targets.contains(Role.getWorld())) {
138 return Visibility.PUBLIC;
139 } else {
140 // FIXME: There should really be a check whether targets.equals(owner.defaultTargets) here.
141 // Otherwise the actual visibility is DISCRETIONARY.
142 return Visibility.SEMIPRIVATE;
143 }
144 }
145
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100146 protected static <T extends Post> CriteriaBuilder<T> queryViewable(
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100147 Class<T> entityClass,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200148 @CheckForNull User reader,
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100149 @CheckForNull User owner,
150 @CheckForNull Integer cursor,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100151 EntityManager em,
152 CriteriaBuilderFactory cbf,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200153 boolean forward,
154 @CheckForNull String searchQuery) {
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100155
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100156 CriteriaBuilder<T> cb = cbf.create(em, entityClass).select("post");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100157
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200158 if (reader == null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100159 cb =
160 cb.from(entityClass, "post")
161 .innerJoin("post.targets", "role")
162 .where("'world'")
163 .isMemberOf("role.tags");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100164 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100165 cb = cb.from(User.class, "user").where("user").eq(reader);
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200166 if (entityClass.isAssignableFrom(Post.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100167 cb = cb.innerJoin("user.visiblePosts", "post");
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200168 } else if (entityClass.isAssignableFrom(Bookmark.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100169 cb = cb.innerJoin("user.visibleBookmark", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200170 } else if (entityClass.isAssignableFrom(LazychatMessage.class)) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100171 cb = cb.innerJoin("user.visibleLazychatMessages", "post");
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200172 } else {
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200173 throw new IllegalArgumentException();
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100174 }
175 }
176
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100177 cb = cb.fetch("post.owner");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100178
179 if (owner != null) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100180 cb = cb.where("post.owner").eq(owner);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100181 }
182
183 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100184 cb = cb.orderByDesc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100185 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100186 cb = cb.orderByAsc("post.id");
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100187 }
188
189 if (cursor != null) {
190 if (forward) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100191 cb = cb.where("post.id").le(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100192 } else {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100193 cb = cb.where("post.id").gt(cursor);
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100194 }
195 }
196
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200197 if (searchQuery != null && !searchQuery.isBlank()) {
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100198 cb =
199 cb.whereExists()
200 .from(PostText.class, "postText")
201 .where("postText.post")
202 .eqExpression("post")
203 .whereOr()
204 .whereExpression(
205 "post_matches_websearch(postText.searchTerms, 'de', :searchQueryText) = true")
206 .whereExpression(
207 "post_matches_websearch(postText.searchTerms, 'en', :searchQueryText) = true")
208 .endOr()
209 .end()
210 .setParameter("searchQueryText", searchQuery);
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200211 }
212
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100213 cb = cb.where("post.scope").eq(Scope.top_level);
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100214
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100215 return cb;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100216 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100217
Matthias Andreas Benkard6cfe16b2020-04-18 15:36:04 +0200218 public final boolean isVisibleTo(@Nullable User user) {
219 // FIXME: Make this more efficient.
220 return getVisibility() == Visibility.PUBLIC || (user != null && visibleTo.contains(user));
221 }
222
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200223 @CheckForNull
224 public final String getDescriptionHtml() {
225 var text = getText();
226 if (text == null) {
227 return null;
228 }
229 return text.getDescriptionHtml();
230 }
231
Matthias Andreas Benkard67c60672021-01-30 14:43:39 +0100232 public final boolean isTopLevel() {
233 return scope == Scope.top_level;
234 }
235
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200236 public static class Day<T extends Post<? extends PostText<?>>> {
237 public final @CheckForNull LocalDate date;
238 public final List<T> posts;
239
240 private Day(LocalDate date, List<T> posts) {
241 this.date = date;
242 this.posts = posts;
243 }
244
245 public void cacheDescriptions() {
246 for (var post : posts) {
247 post.getTexts().values().forEach(PostText::getDescriptionHtml);
248 }
249 }
250 }
251
252 public static class PostPage<T extends Post<? extends PostText<?>>> {
Matthias Andreas Benkard593765d2020-04-18 20:44:07 +0200253 public @CheckForNull final Integer prevCursor;
254 public @CheckForNull final Integer cursor;
255 public @CheckForNull final Integer nextCursor;
256 public final List<T> posts;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100257
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200258 private static final TimeZone timeZone = TimeZone.getDefault();
259
260 public PostPage(
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100261 @CheckForNull Integer c0,
262 @CheckForNull Integer c1,
263 @CheckForNull Integer c2,
264 List<T> resultList) {
265 this.prevCursor = c0;
266 this.cursor = c1;
267 this.nextCursor = c2;
268 this.posts = resultList;
269 }
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200270
Matthias Andreas Benkard60c08922020-06-13 19:22:25 +0200271 public void cacheDescriptions() {
272 days().forEach(Day::cacheDescriptions);
273 }
274
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200275 public List<Day<T>> days() {
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200276 return posts.stream()
277 .collect(Collectors.groupingBy(post -> post.date.toLocalDate()))
278 .entrySet()
279 .stream()
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200280 .map(x -> new Day<T>(x.getKey(), x.getValue()))
281 .sorted(Comparator.comparing((Day<T> day) -> day.date).reversed())
Matthias Andreas Benkard1c2a8a72020-04-26 06:09:57 +0200282 .collect(Collectors.toUnmodifiableList());
283 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100284 }
285
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200286 public static PostPage<Post<? extends PostText<?>>> findViewable(
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100287 PostFilter postFilter,
288 EntityManager em,
289 CriteriaBuilderFactory cbf,
290 @CheckForNull User viewer,
291 @CheckForNull User owner) {
292 return findViewable(postFilter, em, cbf, viewer, owner, null, null, null);
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200293 }
294
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200295 public static PostPage<Post<? extends PostText<?>>> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200296 PostFilter postFilter,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100297 EntityManager em,
298 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200299 @CheckForNull User viewer,
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200300 @CheckForNull User owner,
301 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200302 @CheckForNull Integer count,
303 @CheckForNull String searchQuery) {
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200304 Class<? extends Post> entityClass;
305 switch (postFilter) {
306 case BOOKMARKS_ONLY:
307 entityClass = Bookmark.class;
308 break;
309 case LAZYCHAT_MESSAGES_ONLY:
310 entityClass = LazychatMessage.class;
311 break;
312 default:
313 entityClass = Post.class;
314 }
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100315 return findViewable(entityClass, em, cbf, viewer, owner, cursor, count, searchQuery);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100316 }
317
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +0200318 protected static <T extends Post<? extends PostText<?>>> PostPage<T> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200319 Class<? extends T> entityClass,
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100320 EntityManager em,
321 CriteriaBuilderFactory cbf,
Matthias Andreas Benkardcf0fe882020-04-19 18:33:37 +0200322 @CheckForNull User viewer,
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100323 @CheckForNull User owner,
324 @CheckForNull Integer cursor,
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200325 @CheckForNull Integer count,
326 @CheckForNull String searchQuery) {
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100327
328 if (cursor != null) {
329 Objects.requireNonNull(count);
330 }
331
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100332 var forwardCriteria =
333 queryViewable(entityClass, viewer, owner, cursor, em, cbf, true, searchQuery);
334 var forwardQuery = forwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100335
336 if (count != null) {
337 forwardQuery.setMaxResults(count + 1);
338 }
339
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100340 @CheckForNull Integer prevCursor = null;
341 @CheckForNull Integer nextCursor = null;
342
343 if (cursor != null) {
344 // Look backwards as well so we can find the prevCursor.
Matthias Andreas Benkard8563a3c2020-09-16 17:57:24 +0200345 var backwardCriteria =
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100346 queryViewable(entityClass, viewer, owner, cursor, em, cbf, false, searchQuery);
347 var backwardQuery = backwardCriteria.getQuery();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100348 backwardQuery.setMaxResults(count);
349 var backwardResults = backwardQuery.getResultList();
350 if (!backwardResults.isEmpty()) {
351 prevCursor = backwardResults.get(backwardResults.size() - 1).id;
352 }
353 }
354
Matthias Andreas Benkardab36adb2022-03-20 16:10:42 +0100355 var forwardResults = new ArrayList<T>(forwardQuery.getResultList());
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100356 if (count != null) {
357 if (forwardResults.size() == count + 1) {
358 nextCursor = forwardResults.get(count).id;
359 forwardResults.remove((int) count);
360 }
361 }
362
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200363 // Fetch texts (to avoid n+1 selects).
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100364 fetchTexts(forwardResults);
365
366 return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults);
367 }
368
369 public static <T extends Post<?>> void fetchTexts(Collection<T> posts) {
370 var postIds = posts.stream().map(x -> x.id).collect(toList());
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200371
372 if (!postIds.isEmpty()) {
373 find("SELECT p FROM Post p LEFT JOIN FETCH p.texts WHERE p.id IN (?1)", postIds).stream()
374 .count();
375 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100376 }
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200377
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200378 @CheckForNull
Matthias Andreas Benkardba3e58c2020-11-01 12:58:35 +0100379 public Text getText() {
Matthias Andreas Benkardd5498fc2020-08-23 21:51:00 +0200380 var texts = getTexts();
381 if (texts.isEmpty()) {
382 return null;
383 } else if (texts.containsKey("")) {
384 return texts.get("");
385 } else if (texts.containsKey("en")) {
386 return texts.get("en");
387 } else {
388 return texts.values().stream().findAny().get();
389 }
390 }
391
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200392 public enum Visibility {
393 PUBLIC,
394 SEMIPRIVATE,
395 DISCRETIONARY,
396 PRIVATE,
397 }
398
399 @Override
400 public boolean equals(Object o) {
401 if (this == o) {
402 return true;
403 }
404 if (!(o instanceof Post)) {
405 return false;
406 }
Matthias Andreas Benkardde46b232021-02-06 07:51:57 +0100407 Post<?> post = (Post<?>) o;
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200408 return Objects.equals(id, post.id);
409 }
410
411 @Override
412 public int hashCode() {
413 return Objects.hash(id);
414 }
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +0100415}