Skip to content

Commit 170be9b

Browse files
committed
feat: adding changes feed api
1 parent 3908a3c commit 170be9b

11 files changed

Lines changed: 548 additions & 3 deletions

File tree

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
4141

4242
import java.io.InputStream;
43+
import java.time.LocalDateTime;
4344
import java.util.*;
4445
import java.util.stream.Collectors;
4546

@@ -1153,4 +1154,26 @@ public RegistryVersionJson getRegistryVersion() {
11531154
json.setVersion(registryVersion);
11541155
return json;
11551156
}
1157+
1158+
public ChangesResultJson getChanges(LocalDateTime since, LocalDateTime before, int size, int offset) {
1159+
var page = repositories.findChanges(since, before, size, offset);
1160+
var result = new ChangesResultJson();
1161+
result.setOffset(offset);
1162+
result.setTotalSize((int) page.getTotalElements());
1163+
result.setChanges(page.getContent().stream()
1164+
.map(ev -> {
1165+
var entry = new ChangeEntryJson();
1166+
entry.setNamespace(ev.getExtension().getNamespace().getName());
1167+
entry.setName(ev.getExtension().getName());
1168+
entry.setVersion(ev.getVersion());
1169+
entry.setTargetPlatform(ev.getTargetPlatform());
1170+
entry.setState(ev.getState().name().toLowerCase());
1171+
entry.setTimestamp(TimeUtil.toUTCString(ev.getTimestamp()));
1172+
entry.setLastUpdated(TimeUtil.toUTCString(ev.getLastUpdated()));
1173+
entry.setExtension(ev.toExtensionJson());
1174+
return entry;
1175+
})
1176+
.toList());
1177+
return result;
1178+
}
11561179
}

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import java.io.InputStream;
3838
import java.net.URI;
39+
import java.time.LocalDateTime;
3940
import java.util.ArrayList;
4041
import java.util.List;
4142
import java.util.concurrent.TimeUnit;
@@ -1358,6 +1359,63 @@ public ResponseEntity<ResultJson> deleteReview(@PathVariable String namespace, @
13581359
}
13591360
}
13601361

1362+
@GetMapping(
1363+
path = "/api/-/changes",
1364+
produces = MediaType.APPLICATION_JSON_VALUE
1365+
)
1366+
@CrossOrigin
1367+
@Operation(summary = "Provides a paginated feed of registry changes")
1368+
@ApiResponse(
1369+
responseCode = "200",
1370+
description = "The changes are returned in JSON format"
1371+
)
1372+
@ApiResponse(
1373+
responseCode = "400",
1374+
description = "The request contains an invalid parameter value"
1375+
)
1376+
public ResponseEntity<ChangesResultJson> getChanges(
1377+
@RequestParam(required = false)
1378+
@Parameter(description = "Only include changes at or after this timestamp (ISO-8601 UTC, e.g. 2024-01-01T00:00:00Z)")
1379+
String since,
1380+
@RequestParam(required = false)
1381+
@Parameter(description = "Only include changes before this timestamp, exclusive (ISO-8601 UTC, e.g. 2024-12-31T23:59:59Z)")
1382+
String until,
1383+
@RequestParam(defaultValue = "100")
1384+
@Parameter(description = "Maximal number of entries to return", schema = @Schema(type = "integer", minimum = "0", defaultValue = "100"))
1385+
int size,
1386+
@RequestParam(defaultValue = "0")
1387+
@Parameter(description = "Number of entries to skip (usually a multiple of the page size)", schema = @Schema(type = "integer", minimum = "0", defaultValue = "0"))
1388+
int offset
1389+
) {
1390+
if (size < 0) {
1391+
return new ResponseEntity<>(ChangesResultJson.error(negativeParameterMessage("size")), HttpStatus.BAD_REQUEST);
1392+
}
1393+
if (offset < 0) {
1394+
return new ResponseEntity<>(ChangesResultJson.error(negativeOffsetMessage()), HttpStatus.BAD_REQUEST);
1395+
}
1396+
1397+
LocalDateTime sinceDate = null;
1398+
LocalDateTime untilDate = null;
1399+
if (since != null) {
1400+
try {
1401+
sinceDate = TimeUtil.fromUTCString(since);
1402+
} catch (Exception e) {
1403+
return new ResponseEntity<>(ChangesResultJson.error("Invalid 'since' parameter: " + since), HttpStatus.BAD_REQUEST);
1404+
}
1405+
}
1406+
if (until != null) {
1407+
try {
1408+
untilDate = TimeUtil.fromUTCString(until);
1409+
} catch (Exception e) {
1410+
return new ResponseEntity<>(ChangesResultJson.error("Invalid 'until' parameter: " + until), HttpStatus.BAD_REQUEST);
1411+
}
1412+
}
1413+
1414+
return ResponseEntity.ok()
1415+
.cacheControl(CacheControl.noCache().cachePublic())
1416+
.body(local.getChanges(sinceDate, untilDate, size, offset));
1417+
}
1418+
13611419
@GetMapping(
13621420
path = "/api/-/public-key/{publicId}",
13631421
produces = MediaType.TEXT_PLAIN_VALUE

server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public enum Type {
4343
EXTENDED
4444
}
4545

46+
public enum State {
47+
ACTIVE, INACTIVE, DELETED
48+
}
49+
4650
@Id
4751
@GeneratedValue(generator = "extensionVersionSeq")
4852
@SequenceGenerator(name = "extensionVersionSeq", sequenceName = "extension_version_seq")
@@ -77,6 +81,11 @@ public enum Type {
7781

7882
private boolean active;
7983

84+
@Enumerated(EnumType.STRING)
85+
private State state = State.ACTIVE;
86+
87+
private LocalDateTime lastUpdated;
88+
8089
private boolean potentiallyMalicious;
8190

8291
private String displayName;
@@ -319,6 +328,24 @@ public boolean isActive() {
319328

320329
public void setActive(boolean active) {
321330
this.active = active;
331+
setState(active ? State.ACTIVE : State.INACTIVE);
332+
}
333+
334+
public State getState() {
335+
return state;
336+
}
337+
338+
public void setState(State state) {
339+
this.state = state;
340+
this.lastUpdated = TimeUtil.getCurrentUTC();
341+
}
342+
343+
public LocalDateTime getLastUpdated() {
344+
return lastUpdated;
345+
}
346+
347+
public void setLastUpdated(LocalDateTime lastUpdated) {
348+
this.lastUpdated = lastUpdated;
322349
}
323350

324351
public boolean isPotentiallyMalicious() {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 Eclipse Foundation and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
********************************************************************************/
10+
package org.eclipse.openvsx.json;
11+
12+
import com.fasterxml.jackson.annotation.JsonInclude;
13+
import io.swagger.v3.oas.annotations.media.Schema;
14+
15+
@Schema(name = "ChangeEntry", description = "A single registry change entry")
16+
@JsonInclude(JsonInclude.Include.NON_NULL)
17+
public class ChangeEntryJson {
18+
19+
@Schema(description = "Namespace of the extension")
20+
private String namespace;
21+
22+
@Schema(description = "Name of the extension")
23+
private String name;
24+
25+
@Schema(description = "Version string")
26+
private String version;
27+
28+
@Schema(description = "Target platform (e.g. universal, linux-x64)")
29+
private String targetPlatform;
30+
31+
@Schema(description = "Current state of this extension version (active, inactive, deleted)")
32+
private String state;
33+
34+
@Schema(description = "Timestamp of the version publication (ISO-8601 UTC)")
35+
private String timestamp;
36+
37+
@Schema(description = "Timestamp of the last state change (ISO-8601 UTC)")
38+
private String lastUpdated;
39+
40+
@Schema(description = "Full extension metadata")
41+
private ExtensionJson extension;
42+
43+
public String getNamespace() {
44+
return namespace;
45+
}
46+
47+
public void setNamespace(String namespace) {
48+
this.namespace = namespace;
49+
}
50+
51+
public String getName() {
52+
return name;
53+
}
54+
55+
public void setName(String name) {
56+
this.name = name;
57+
}
58+
59+
public String getVersion() {
60+
return version;
61+
}
62+
63+
public void setVersion(String version) {
64+
this.version = version;
65+
}
66+
67+
public String getTargetPlatform() {
68+
return targetPlatform;
69+
}
70+
71+
public void setTargetPlatform(String targetPlatform) {
72+
this.targetPlatform = targetPlatform;
73+
}
74+
75+
public String getState() {
76+
return state;
77+
}
78+
79+
public void setState(String state) {
80+
this.state = state;
81+
}
82+
83+
public String getTimestamp() {
84+
return timestamp;
85+
}
86+
87+
public void setTimestamp(String timestamp) {
88+
this.timestamp = timestamp;
89+
}
90+
91+
public String getLastUpdated() {
92+
return lastUpdated;
93+
}
94+
95+
public void setLastUpdated(String lastUpdated) {
96+
this.lastUpdated = lastUpdated;
97+
}
98+
99+
public ExtensionJson getExtension() {
100+
return extension;
101+
}
102+
103+
public void setExtension(ExtensionJson extension) {
104+
this.extension = extension;
105+
}
106+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 Eclipse Foundation and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
********************************************************************************/
10+
package org.eclipse.openvsx.json;
11+
12+
import com.fasterxml.jackson.annotation.JsonInclude;
13+
import io.swagger.v3.oas.annotations.media.Schema;
14+
import jakarta.validation.constraints.Min;
15+
import jakarta.validation.constraints.NotNull;
16+
17+
import java.util.List;
18+
19+
@Schema(name = "ChangesResult", description = "Paginated list of registry changes")
20+
@JsonInclude(JsonInclude.Include.NON_NULL)
21+
public class ChangesResultJson extends ResultJson {
22+
23+
public static ChangesResultJson error(String message) {
24+
var result = new ChangesResultJson();
25+
result.setError(message);
26+
return result;
27+
}
28+
29+
@Schema(description = "Number of skipped entries according to the changes request")
30+
@NotNull
31+
@Min(0)
32+
private int offset;
33+
34+
@Schema(description = "Total number of changes matching the request")
35+
@NotNull
36+
@Min(0)
37+
private int totalSize;
38+
39+
@Schema(description = "List of change entries, limited to the size specified in the request")
40+
@NotNull
41+
private List<ChangeEntryJson> changes;
42+
43+
public int getOffset() {
44+
return offset;
45+
}
46+
47+
public void setOffset(int offset) {
48+
this.offset = offset;
49+
}
50+
51+
public int getTotalSize() {
52+
return totalSize;
53+
}
54+
55+
public void setTotalSize(int totalSize) {
56+
this.totalSize = totalSize;
57+
}
58+
59+
public List<ChangeEntryJson> getChanges() {
60+
return changes;
61+
}
62+
63+
public void setChanges(List<ChangeEntryJson> changes) {
64+
this.changes = changes;
65+
}
66+
}

0 commit comments

Comments
 (0)