forked from Archives/Athou_commafeed
add a setting to mark entries of a feed as read after a number of days (#2041)
This commit is contained in:
@@ -18,15 +18,19 @@ import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
import com.commafeed.backend.model.FeedEntryTag;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.QFeed;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntryContent;
|
||||
import com.commafeed.backend.model.QFeedEntryStatus;
|
||||
import com.commafeed.backend.model.QFeedEntryTag;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.frontend.model.UnreadCount;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
|
||||
@Singleton
|
||||
@@ -34,8 +38,10 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
private final FeedEntryTagDAO feedEntryTagDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
@@ -232,4 +238,58 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
return deleteQuery(STATUS).where(STATUS.id.in(ids)).execute();
|
||||
}
|
||||
|
||||
public long autoMarkAsRead(int limit) {
|
||||
Instant now = Instant.now();
|
||||
|
||||
BooleanBuilder where = new BooleanBuilder();
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.isNotNull());
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.gt(0));
|
||||
|
||||
NumberExpression<Integer> daysDiff = Expressions.numberTemplate(Integer.class, "TIMESTAMPDIFF(DAY, {0}, {1})", ENTRY.published,
|
||||
now);
|
||||
where.and(daysDiff.goe(SUBSCRIPTION.autoMarkAsReadAfterDays));
|
||||
|
||||
where.and(buildUnreadPredicate());
|
||||
|
||||
List<Tuple> tuples = query().select(ENTRY, STATUS, SUBSCRIPTION)
|
||||
.from(ENTRY)
|
||||
.join(ENTRY.feed, FEED)
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(SUBSCRIPTION))
|
||||
.where(where)
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
|
||||
long updated = 0;
|
||||
|
||||
// Update existing statuses
|
||||
List<Long> statusIdsToUpdate = tuples.stream()
|
||||
.map(t -> t.get(STATUS))
|
||||
.filter(s -> s != null && s.getId() != null)
|
||||
.map(FeedEntryStatus::getId)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
if (!statusIdsToUpdate.isEmpty()) {
|
||||
updated += updateQuery(STATUS).where(STATUS.id.in(statusIdsToUpdate)).set(STATUS.read, true).execute();
|
||||
}
|
||||
|
||||
// Insert new statuses for entries without existing status
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntryStatus status = tuple.get(STATUS);
|
||||
if (status == null || status.getId() == null) {
|
||||
FeedEntry entry = tuple.get(ENTRY);
|
||||
FeedSubscription sub = tuple.get(SUBSCRIPTION);
|
||||
FeedEntryStatus newStatus = new FeedEntryStatus(sub.getUser(), sub, entry);
|
||||
newStatus.setRead(true);
|
||||
persist(newStatus);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,4 +49,7 @@ public class FeedSubscription extends AbstractModel {
|
||||
@Column(name = "push_notifications_enabled")
|
||||
private boolean pushNotificationsEnabled;
|
||||
|
||||
@Column(name = "auto_mark_as_read_after_days")
|
||||
private Integer autoMarkAsReadAfterDays;
|
||||
|
||||
}
|
||||
|
||||
@@ -133,4 +133,16 @@ public class DatabaseCleaningService {
|
||||
} while (deleted != 0);
|
||||
log.info("cleanup done: {} old read statuses deleted", total);
|
||||
}
|
||||
|
||||
public void autoMarkAsRead() {
|
||||
log.info("marking entries as read based on autoMarkAsReadAfterDays");
|
||||
long total = 0;
|
||||
long marked;
|
||||
do {
|
||||
marked = unitOfWork.call(() -> feedEntryStatusDAO.autoMarkAsRead(batchSize));
|
||||
total += marked;
|
||||
log.debug("marked {} entries as read", total);
|
||||
} while (marked != 0);
|
||||
log.info("cleanup done: marked {} entries as read", total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.commafeed.backend.task;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
public class AutoMarkAsReadTask extends ScheduledTask {
|
||||
|
||||
private final DatabaseCleaningService cleaner;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
cleaner.autoMarkAsRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInitialDelay() {
|
||||
return 25;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPeriod() {
|
||||
return 60;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeUnit getTimeUnit() {
|
||||
return TimeUnit.MINUTES;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,6 +68,9 @@ public class Subscription implements Serializable {
|
||||
@Schema(description = "whether to send push notifications for new entries of this feed", required = true)
|
||||
private boolean pushNotificationsEnabled;
|
||||
|
||||
@Schema(description = "automatically mark entries as read after this many days (null to disable)")
|
||||
private Integer autoMarkAsReadAfterDays;
|
||||
|
||||
public static Subscription build(FeedSubscription subscription, UnreadCount unreadCount) {
|
||||
FeedCategory category = subscription.getCategory();
|
||||
Feed feed = subscription.getFeed();
|
||||
@@ -89,6 +92,7 @@ public class Subscription implements Serializable {
|
||||
sub.setFilter(subscription.getFilter());
|
||||
sub.setFilterLegacy(subscription.getFilterLegacy());
|
||||
sub.setPushNotificationsEnabled(subscription.isPushNotificationsEnabled());
|
||||
sub.setAutoMarkAsReadAfterDays(subscription.getAutoMarkAsReadAfterDays());
|
||||
return sub;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,7 @@ public class FeedModificationRequest implements Serializable {
|
||||
@Schema(description = "whether to send push notifications for new entries of this feed")
|
||||
private boolean pushNotificationsEnabled;
|
||||
|
||||
@Schema(description = "automatically mark entries as read after this many days (null to disable)")
|
||||
private Integer autoMarkAsReadAfterDays;
|
||||
|
||||
}
|
||||
|
||||
@@ -437,6 +437,7 @@ public class FeedREST {
|
||||
}
|
||||
|
||||
subscription.setPushNotificationsEnabled(req.isPushNotificationsEnabled());
|
||||
subscription.setAutoMarkAsReadAfterDays(req.getAutoMarkAsReadAfterDays());
|
||||
|
||||
if (StringUtils.isNotBlank(req.getName())) {
|
||||
subscription.setTitle(req.getName());
|
||||
|
||||
@@ -32,5 +32,15 @@
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
<changeSet id="add-auto-mark-as-read-after-days" author="athou">
|
||||
<addColumn tableName="FEEDSUBSCRIPTIONS">
|
||||
<column name="auto_mark_as_read_after_days" type="INT">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
</addColumn>
|
||||
<createIndex indexName="feedsubscriptions_automark_index" tableName="FEEDSUBSCRIPTIONS">
|
||||
<column name="auto_mark_as_read_after_days" />
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.commafeed.TestConstants;
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
import com.commafeed.frontend.model.Entries;
|
||||
import com.commafeed.frontend.model.Entry;
|
||||
import com.commafeed.frontend.model.request.FeedModificationRequest;
|
||||
import com.commafeed.frontend.model.request.StarRequest;
|
||||
import com.commafeed.frontend.resource.CategoryREST;
|
||||
import com.commafeed.integration.BaseIT;
|
||||
@@ -182,4 +183,30 @@ class DatabaseCleaningIT extends BaseIT {
|
||||
Assertions.assertEquals(0, entriesAfter.getEntries().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AutoMarkAsRead {
|
||||
@Test
|
||||
void entriesAreMarkedAsReadAfterSpecifiedDays() {
|
||||
// Subscribe to feed
|
||||
Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl());
|
||||
|
||||
// verify we have 2 unread entries
|
||||
Entries entries = getFeedEntries(subscriptionId);
|
||||
Assertions.assertEquals(2, entries.getEntries().stream().filter(e -> !e.isRead()).count());
|
||||
|
||||
// set auto-mark as read
|
||||
FeedModificationRequest req = new FeedModificationRequest();
|
||||
req.setId(subscriptionId);
|
||||
req.setAutoMarkAsReadAfterDays(1);
|
||||
RestAssured.given().body(req).contentType(ContentType.JSON).post("rest/feed/modify").then().statusCode(200);
|
||||
|
||||
// run auto-mark as read
|
||||
databaseCleaningService.autoMarkAsRead();
|
||||
|
||||
// verify all entries are now read
|
||||
entries = getFeedEntries(subscriptionId);
|
||||
Assertions.assertEquals(0, entries.getEntries().stream().filter(e -> !e.isRead()).count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user