Skip to content

Commit afa1bc1

Browse files
netomignugomez
andauthored
Feat: Add read only mode (#1835)
* add FeatureFlagService and aspect for read-only mode, add MutatingOperation annotation to indicate endpoint that modify the DB, add annotation to all relevant endpoints * fix warning with existing aspect and add fixme for broken aspect * refactor to SettingsService in package settings * feat(admin): adding settings page (#1818) * add api endpoints, rename feature flag to settings * consistently use settings instead of feature flags, reduce notification timeout, add label * bump jedis to 7.4.1 and refactor channel listener to a common base class * add update listener to SettingsService * fix ide warnings * add persistency layer for settings * add expiry for setting cache * update setting key to trigger rebuild * feat(admin/settings): adding friction on save * improve error handling * check for read-only mode in relevant background jobs * add changelog entry --------- Co-authored-by: Jordi Gómez Hidalgo <31970428+gnugomez@users.noreply.github.com> Co-authored-by: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
1 parent e35e171 commit afa1bc1

48 files changed

Lines changed: 1247 additions & 219 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

server/gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jackson = "2.18.6"
1414
java = "25"
1515
jaxb-api = "2.3.1"
1616
jaxb-impl = "2.3.8"
17-
jedis = "6.2.0"
17+
jedis = "7.4.1"
1818
jobrunr = "7.5.3"
1919
jooq = "3.19.34"
2020
jsonpath = "2.9.0"

server/src/main/java/org/eclipse/openvsx/RegistryAPI.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import jakarta.servlet.http.HttpServletRequest;
2121
import org.apache.commons.lang3.StringUtils;
2222
import org.eclipse.openvsx.entities.SemanticVersion;
23+
import org.eclipse.openvsx.settings.MutatingOperation;
2324
import org.eclipse.openvsx.json.*;
2425
import org.eclipse.openvsx.search.ISearchService;
2526
import org.eclipse.openvsx.search.SortBy;
@@ -1137,6 +1138,7 @@ public ResponseEntity<QueryResultJson> postQuery(
11371138
produces = MediaType.APPLICATION_JSON_VALUE
11381139
)
11391140
@Operation(summary = "Create a namespace")
1141+
@MutatingOperation
11401142
@ApiResponse(
11411143
responseCode = "201",
11421144
description = "Successfully created the namespace",
@@ -1197,6 +1199,7 @@ public ResponseEntity<ResultJson> createNamespace(
11971199
required = true
11981200
)
11991201
)
1202+
@MutatingOperation
12001203
@ApiResponse(
12011204
responseCode = "201",
12021205
description = "Successfully created the namespace",
@@ -1264,6 +1267,7 @@ public ResponseEntity<ResultJson> createNamespace(
12641267
required = true
12651268
)
12661269
)
1270+
@MutatingOperation
12671271
@ApiResponse(
12681272
responseCode = "201",
12691273
description = "Successfully published the extension",
@@ -1311,6 +1315,7 @@ public ResponseEntity<ExtensionJson> publish(
13111315
required = true
13121316
)
13131317
)
1318+
@MutatingOperation
13141319
@ApiResponse(
13151320
responseCode = "201",
13161321
description = "Successfully published the extension",
@@ -1357,6 +1362,7 @@ public ResponseEntity<ExtensionJson> publish(InputStream content) {
13571362
produces = MediaType.APPLICATION_JSON_VALUE
13581363
)
13591364
@Operation(hidden = true)
1365+
@MutatingOperation
13601366
public ResponseEntity<ResultJson> postReview(
13611367
@RequestBody(required = false) ReviewJson review,
13621368
@PathVariable String namespace,
@@ -1391,6 +1397,7 @@ public ResponseEntity<ResultJson> postReview(
13911397
produces = MediaType.APPLICATION_JSON_VALUE
13921398
)
13931399
@Operation(hidden = true)
1400+
@MutatingOperation
13941401
public ResponseEntity<ResultJson> deleteReview(@PathVariable String namespace, @PathVariable String extension) {
13951402
var json = local.deleteReview(namespace, extension);
13961403
if (json.getError() == null) {

server/src/main/java/org/eclipse/openvsx/UserAPI.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.eclipse.openvsx.accesstoken.AccessTokenService;
1414
import org.eclipse.openvsx.eclipse.EclipseService;
1515
import org.eclipse.openvsx.entities.*;
16+
import org.eclipse.openvsx.settings.MutatingOperation;
1617
import org.eclipse.openvsx.json.*;
1718
import org.eclipse.openvsx.repositories.ExtensionScanRepository;
1819
import org.eclipse.openvsx.repositories.RepositoryService;
@@ -163,6 +164,7 @@ public List<AccessTokenJson> getAccessTokens() {
163164
path = "/user/token/create",
164165
produces = MediaType.APPLICATION_JSON_VALUE
165166
)
167+
@MutatingOperation
166168
public ResponseEntity<AccessTokenJson> createAccessToken(@RequestParam(required = false) String description) {
167169
if (description != null && description.length() > TOKEN_DESCRIPTION_SIZE) {
168170
var json = AccessTokenJson.error("The description must not be longer than " + TOKEN_DESCRIPTION_SIZE + " characters.");
@@ -180,6 +182,7 @@ public ResponseEntity<AccessTokenJson> createAccessToken(@RequestParam(required
180182
path = "/user/token/delete/{id}",
181183
produces = MediaType.APPLICATION_JSON_VALUE
182184
)
185+
@MutatingOperation
183186
public ResponseEntity<ResultJson> deleteAccessToken(@PathVariable long id) {
184187
var user = users.findLoggedInUser();
185188
if (user == null) {
@@ -345,6 +348,7 @@ public ResponseEntity<ExtensionJson> getOwnExtension(@PathVariable String namesp
345348
path = "/user/extension/{namespaceName}/{extensionName}/delete",
346349
produces = MediaType.APPLICATION_JSON_VALUE
347350
)
351+
@MutatingOperation
348352
public ResponseEntity<ResultJson> deleteExtension(
349353
@PathVariable String namespaceName,
350354
@PathVariable String extensionName,
@@ -400,6 +404,7 @@ public List<NamespaceJson> getOwnNamespaces() {
400404
path = "/user/namespace/{namespace}/details",
401405
produces = MediaType.APPLICATION_JSON_VALUE
402406
)
407+
@MutatingOperation
403408
public ResponseEntity<ResultJson> updateNamespaceDetails(@RequestBody NamespaceDetailsJson details) {
404409
var user = users.findLoggedInUser();
405410
if (user == null) {
@@ -423,6 +428,7 @@ public ResponseEntity<ResultJson> updateNamespaceDetails(@RequestBody NamespaceD
423428
produces = MediaType.APPLICATION_JSON_VALUE,
424429
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
425430
)
431+
@MutatingOperation
426432
public ResponseEntity<ResultJson> updateNamespaceDetailsLogo(
427433
@PathVariable String namespace,
428434
@RequestParam MultipartFile file
@@ -464,6 +470,7 @@ public ResponseEntity<NamespaceMembershipListJson> getNamespaceMembers(@PathVari
464470
path = "/user/namespace/{namespace}/role",
465471
produces = MediaType.APPLICATION_JSON_VALUE
466472
)
473+
@MutatingOperation
467474
public ResponseEntity<ResultJson> setNamespaceMember(@PathVariable String namespace, @RequestParam String user,
468475
@RequestParam String role, @RequestParam(required = false) String provider) {
469476
var requestingUser = users.findLoggedInUser();

server/src/main/java/org/eclipse/openvsx/accesstoken/ExpirePersonalAccessTokensHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
package org.eclipse.openvsx.accesstoken;
1111

1212
import org.eclipse.openvsx.migration.HandlerJobRequest;
13+
import org.eclipse.openvsx.settings.SettingsService;
14+
import org.jobrunr.jobs.annotations.Job;
1315
import org.jobrunr.jobs.lambdas.JobRequestHandler;
1416
import org.slf4j.Logger;
1517
import org.slf4j.LoggerFactory;
@@ -20,14 +22,21 @@ public class ExpirePersonalAccessTokensHandler implements JobRequestHandler<Hand
2022

2123
private final Logger logger = LoggerFactory.getLogger(ExpirePersonalAccessTokensHandler.class);
2224

25+
private final SettingsService settings;
2326
private final AccessTokenService tokens;
2427

25-
public ExpirePersonalAccessTokensHandler(AccessTokenService tokens) {
28+
public ExpirePersonalAccessTokensHandler(SettingsService settings, AccessTokenService tokens) {
29+
this.settings = settings;
2630
this.tokens = tokens;
2731
}
2832

2933
@Override
34+
@Job(name = "Expire access tokens", retries = 0)
3035
public void run(HandlerJobRequest<?> handlerJobRequest) throws Exception {
36+
if (settings.isReadOnly()) {
37+
return;
38+
}
39+
3140
var count = tokens.expireAccessTokens();
3241
if (count > 0) {
3342
logger.info("Expired {} personal access token(s)", count);

server/src/main/java/org/eclipse/openvsx/accesstoken/LegacyPersonalAccessTokenExpirationHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
package org.eclipse.openvsx.accesstoken;
1111

1212
import org.eclipse.openvsx.migration.HandlerJobRequest;
13+
import org.eclipse.openvsx.settings.SettingsService;
1314
import org.eclipse.openvsx.util.TimeUtil;
15+
import org.jobrunr.jobs.annotations.Job;
1416
import org.jobrunr.jobs.lambdas.JobRequestHandler;
1517
import org.slf4j.Logger;
1618
import org.slf4j.LoggerFactory;
@@ -21,16 +23,23 @@ public class LegacyPersonalAccessTokenExpirationHandler implements JobRequestHan
2123

2224
private final Logger logger = LoggerFactory.getLogger(LegacyPersonalAccessTokenExpirationHandler.class);
2325

26+
private final SettingsService settings;
2427
private final AccessTokenConfig config;
2528
private final AccessTokenService tokens;
2629

27-
public LegacyPersonalAccessTokenExpirationHandler(AccessTokenConfig config, AccessTokenService tokens) {
30+
public LegacyPersonalAccessTokenExpirationHandler(SettingsService settings, AccessTokenConfig config, AccessTokenService tokens) {
31+
this.settings = settings;
2832
this.config = config;
2933
this.tokens = tokens;
3034
}
3135

3236
@Override
37+
@Job(name = "Legacy token expiration", retries = 0)
3338
public void run(HandlerJobRequest<?> handlerJobRequest) throws Exception {
39+
if (settings.isReadOnly()) {
40+
return;
41+
}
42+
3443
if (config.isTokenExpiryEnabled()) {
3544
var expirationTime = TimeUtil.getCurrentUTC().plus(config.getExpiration());
3645
var count = tokens.setExpirationTimeForLegacyAccessTokens(expirationTime);

server/src/main/java/org/eclipse/openvsx/accesstoken/NotifyPersonalAccessTokenExpirationHandler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
import org.eclipse.openvsx.migration.HandlerJobRequest;
1313
import org.eclipse.openvsx.repositories.RepositoryService;
14+
import org.eclipse.openvsx.settings.SettingsService;
1415
import org.eclipse.openvsx.util.TimeUtil;
16+
import org.jobrunr.jobs.annotations.Job;
1517
import org.jobrunr.jobs.lambdas.JobRequestHandler;
1618
import org.slf4j.Logger;
1719
import org.slf4j.LoggerFactory;
@@ -23,22 +25,30 @@ public class NotifyPersonalAccessTokenExpirationHandler implements JobRequestHan
2325

2426
private final Logger logger = LoggerFactory.getLogger(NotifyPersonalAccessTokenExpirationHandler.class);
2527

28+
private final SettingsService settings;
2629
private final AccessTokenConfig config;
2730
private final AccessTokenService tokens;
2831
private final RepositoryService repositories;
2932

3033
public NotifyPersonalAccessTokenExpirationHandler(
34+
SettingsService settings,
3135
AccessTokenConfig config,
3236
AccessTokenService tokens,
3337
RepositoryService repositories
3438
) {
39+
this.settings = settings;
3540
this.config = config;
3641
this.tokens = tokens;
3742
this.repositories = repositories;
3843
}
3944

4045
@Override
46+
@Job(name = "Notify token expiration", retries = 0)
4147
public void run(HandlerJobRequest<?> handlerJobRequest) throws Exception {
48+
if (settings.isReadOnly()) {
49+
return;
50+
}
51+
4252
if (config.isTokenExpiryNotificationEnabled()) {
4353
var expireBefore = TimeUtil.getCurrentUTC().plus(config.getNotification());
4454
var page = PageRequest.of(0, config.getMaxTokenNotifications());

server/src/main/java/org/eclipse/openvsx/adapter/VSCodeIdDailyUpdateJobRequestHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* ****************************************************************************** */
1010
package org.eclipse.openvsx.adapter;
1111

12+
import org.eclipse.openvsx.settings.SettingsService;
13+
import org.jobrunr.jobs.annotations.Job;
1214
import org.jobrunr.jobs.lambdas.JobRequest;
1315
import org.jobrunr.jobs.lambdas.JobRequestHandler;
1416
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -18,14 +20,21 @@
1820
@ConditionalOnProperty(value = "ovsx.data.mirror.enabled", havingValue = "false", matchIfMissing = true)
1921
public class VSCodeIdDailyUpdateJobRequestHandler implements JobRequestHandler<JobRequest> {
2022

23+
private final SettingsService settings;
2124
private final VSCodeIdUpdateService service;
2225

23-
public VSCodeIdDailyUpdateJobRequestHandler(VSCodeIdUpdateService service) {
26+
public VSCodeIdDailyUpdateJobRequestHandler(SettingsService settings, VSCodeIdUpdateService service) {
27+
this.settings = settings;
2428
this.service = service;
2529
}
2630

2731
@Override
32+
@Job(name = "VS Code ID daily update", retries = 0)
2833
public void run(JobRequest request) throws Exception {
34+
if (settings.isReadOnly()) {
35+
return;
36+
}
37+
2938
service.updateAll();
3039
}
3140
}

0 commit comments

Comments
 (0)