forked from Archives/Athou_commafeed
add a cooldown on the force refresh action (#1556)
This commit is contained in:
@@ -209,6 +209,14 @@ public interface CommaFeedConfiguration {
|
||||
*/
|
||||
@WithDefault("500ms")
|
||||
Duration filteringExpressionEvaluationTimeout();
|
||||
|
||||
/**
|
||||
* Duration after which the "Fetch all my feeds now" action is available again after use to avoid spamming feeds.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("1m")
|
||||
Duration forceRefreshCooldownDuration();
|
||||
}
|
||||
|
||||
interface Database {
|
||||
|
||||
@@ -18,7 +18,8 @@ public class JacksonCustomizer implements ObjectMapperCustomizer {
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
|
||||
// read and write instants as milliseconds instead of nanoseconds
|
||||
objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
|
||||
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
|
||||
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
|
||||
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
|
||||
|
||||
// add support for serializing metrics
|
||||
|
||||
@@ -52,4 +52,7 @@ public class User extends AbstractModel {
|
||||
|
||||
@Column
|
||||
private Instant recoverPasswordTokenDate;
|
||||
|
||||
@Column
|
||||
private Instant lastForceRefresh;
|
||||
}
|
||||
|
||||
@@ -98,12 +98,19 @@ public class FeedSubscriptionService {
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshAll(User user) {
|
||||
public void refreshAll(User user) throws ForceFeedRefreshTooSoonException {
|
||||
Instant lastForceRefresh = user.getLastForceRefresh();
|
||||
if (lastForceRefresh != null && lastForceRefresh.plus(config.feedRefresh().forceRefreshCooldownDuration()).isAfter(Instant.now())) {
|
||||
throw new ForceFeedRefreshTooSoonException();
|
||||
}
|
||||
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||
for (FeedSubscription sub : subs) {
|
||||
Feed feed = sub.getFeed();
|
||||
feedRefreshEngine.refreshImmediately(feed);
|
||||
}
|
||||
|
||||
user.setLastForceRefresh(Instant.now());
|
||||
}
|
||||
|
||||
public void refreshAllUpForRefresh(User user) {
|
||||
@@ -130,4 +137,11 @@ public class FeedSubscriptionService {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class ForceFeedRefreshTooSoonException extends Exception {
|
||||
private ForceFeedRefreshTooSoonException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,4 +43,7 @@ public class ServerInfo implements Serializable {
|
||||
@Schema(requiredMode = RequiredMode.REQUIRED)
|
||||
private long treeReloadInterval;
|
||||
|
||||
@Schema(requiredMode = RequiredMode.REQUIRED)
|
||||
private long forceRefreshCooldownDuration;
|
||||
|
||||
}
|
||||
|
||||
@@ -41,4 +41,7 @@ public class UserModel implements Serializable {
|
||||
@Schema(description = "user is admin", requiredMode = RequiredMode.REQUIRED)
|
||||
private boolean admin;
|
||||
|
||||
@Schema(description = "user last force refresh", type = "number")
|
||||
private Instant lastForceRefresh;
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Objects;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.jboss.resteasy.reactive.Cache;
|
||||
import org.jboss.resteasy.reactive.RestForm;
|
||||
|
||||
@@ -40,6 +41,7 @@ import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterEx
|
||||
import com.commafeed.backend.service.FeedEntryService;
|
||||
import com.commafeed.backend.service.FeedService;
|
||||
import com.commafeed.backend.service.FeedSubscriptionService;
|
||||
import com.commafeed.backend.service.FeedSubscriptionService.ForceFeedRefreshTooSoonException;
|
||||
import com.commafeed.frontend.model.Entries;
|
||||
import com.commafeed.frontend.model.Entry;
|
||||
import com.commafeed.frontend.model.FeedInfo;
|
||||
@@ -276,8 +278,13 @@ public class FeedREST {
|
||||
@Operation(summary = "Queue all feeds of the user for refresh", description = "Manually add all feeds of the user to the refresh queue")
|
||||
public Response queueAllForRefresh() {
|
||||
User user = authenticationContext.getCurrentUser();
|
||||
feedSubscriptionService.refreshAll(user);
|
||||
return Response.ok().build();
|
||||
try {
|
||||
feedSubscriptionService.refreshAll(user);
|
||||
return Response.ok().build();
|
||||
} catch (ForceFeedRefreshTooSoonException e) {
|
||||
return Response.status(HttpStatus.SC_TOO_MANY_REQUESTS).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Path("/refresh")
|
||||
|
||||
@@ -61,6 +61,7 @@ public class ServerREST {
|
||||
infos.setWebsocketEnabled(config.websocket().enabled());
|
||||
infos.setWebsocketPingInterval(config.websocket().pingInterval().toMillis());
|
||||
infos.setTreeReloadInterval(config.websocket().treeReloadInterval().toMillis());
|
||||
infos.setForceRefreshCooldownDuration(config.feedRefresh().forceRefreshCooldownDuration().toMillis());
|
||||
return Response.ok(infos).build();
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ public class UserREST {
|
||||
userModel.setEmail(user.getEmail());
|
||||
userModel.setEnabled(!user.isDisabled());
|
||||
userModel.setApiKey(user.getApiKey());
|
||||
userModel.setLastForceRefresh(user.getLastForceRefresh());
|
||||
for (UserRole role : userRoleDAO.findAll(user)) {
|
||||
if (role.getRole() == Role.ADMIN) {
|
||||
userModel.setAdmin(true);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<changeSet id="lastForceRefresh" author="athou">
|
||||
<addColumn tableName="USERS">
|
||||
<column name="lastForceRefresh" type="${timestamp_type}" />
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -33,5 +33,6 @@
|
||||
<include file="changelogs/db.changelog-4.4.xml" />
|
||||
<include file="changelogs/db.changelog-5.1.xml" />
|
||||
<include file="changelogs/db.changelog-5.2.xml" />
|
||||
<include file="changelogs/db.changelog-5.3.xml" />
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -125,7 +125,7 @@ public abstract class BaseIT {
|
||||
.as(Entries.class);
|
||||
}
|
||||
|
||||
protected void forceRefreshAllFeeds() {
|
||||
RestAssured.given().get("rest/feed/refreshAll").then().statusCode(HttpStatus.SC_OK);
|
||||
protected int forceRefreshAllFeeds() {
|
||||
return RestAssured.given().get("rest/feed/refreshAll").then().extract().statusCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,11 +183,13 @@ class FeedIT extends BaseIT {
|
||||
|
||||
// mariadb/mysql timestamp precision is 1 second
|
||||
Instant threshold = Instant.now().minus(Duration.ofSeconds(1));
|
||||
forceRefreshAllFeeds();
|
||||
Assertions.assertEquals(HttpStatus.SC_OK, forceRefreshAllFeeds());
|
||||
|
||||
Awaitility.await()
|
||||
.atMost(Duration.ofSeconds(15))
|
||||
.until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().isAfter(threshold));
|
||||
|
||||
Assertions.assertEquals(HttpStatus.SC_TOO_MANY_REQUESTS, forceRefreshAllFeeds());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class ServerIT extends BaseIT {
|
||||
Assertions.assertTrue(serverInfos.isWebsocketEnabled());
|
||||
Assertions.assertEquals(900000, serverInfos.getWebsocketPingInterval());
|
||||
Assertions.assertEquals(30000, serverInfos.getTreeReloadInterval());
|
||||
Assertions.assertEquals(60000, serverInfos.getForceRefreshCooldownDuration());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user