Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -1153,4 +1154,26 @@ public RegistryVersionJson getRegistryVersion() {
json.setVersion(registryVersion);
return json;
}

public ChangesResultJson getChanges(LocalDateTime since, LocalDateTime before, int size, int offset) {
var page = repositories.findChanges(since, before, size, offset);
var result = new ChangesResultJson();
result.setOffset(offset);
result.setTotalSize((int) page.getTotalElements());
result.setChanges(page.getContent().stream()
.map(ev -> {
var entry = new ChangeEntryJson();
entry.setNamespace(ev.getExtension().getNamespace().getName());
entry.setName(ev.getExtension().getName());
entry.setVersion(ev.getVersion());
entry.setTargetPlatform(ev.getTargetPlatform());
entry.setState(ev.getState().name().toLowerCase());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is there a reason that we're modifying the enums on return? As these aren't aribitrary, we should probably just preserve the data as is and let the client do what they want with that data rather than make the assumption.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so initially I wanted to match our convention, given that in other cases such as for the user role enum we return lowercased values, but thinking about it a bit more I'm realizing we do that there because these values weren't enums before.. so yeah I think returning uppercased enum values here its fine

wdyt @netomi ?

entry.setTimestamp(TimeUtil.toUTCString(ev.getTimestamp()));
entry.setLastUpdated(TimeUtil.toUTCString(ev.getLastUpdated()));
entry.setExtension(ev.toExtensionJson());
return entry;
})
.toList());
return result;
}
}
58 changes: 58 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/RegistryAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

import java.io.InputStream;
import java.net.URI;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -1358,6 +1359,63 @@ public ResponseEntity<ResultJson> deleteReview(@PathVariable String namespace, @
}
}

@GetMapping(
path = "/api/-/changes",
produces = MediaType.APPLICATION_JSON_VALUE
)
@CrossOrigin
@Operation(summary = "Provides a paginated feed of registry changes")
@ApiResponse(
responseCode = "200",
description = "The changes are returned in JSON format"
)
@ApiResponse(
responseCode = "400",
description = "The request contains an invalid parameter value"
)
public ResponseEntity<ChangesResultJson> getChanges(
@RequestParam(required = false)
@Parameter(description = "Only include changes at or after this timestamp (ISO-8601 UTC, e.g. 2024-01-01T00:00:00Z)")
String since,
@RequestParam(required = false)
@Parameter(description = "Only include changes before this timestamp, exclusive (ISO-8601 UTC, e.g. 2024-12-31T23:59:59Z)")
String until,
@RequestParam(defaultValue = "100")
@Parameter(description = "Maximal number of entries to return", schema = @Schema(type = "integer", minimum = "0", defaultValue = "100"))
int size,
@RequestParam(defaultValue = "0")
@Parameter(description = "Number of entries to skip (usually a multiple of the page size)", schema = @Schema(type = "integer", minimum = "0", defaultValue = "0"))
int offset
) {
if (size < 0) {
return new ResponseEntity<>(ChangesResultJson.error(negativeParameterMessage("size")), HttpStatus.BAD_REQUEST);
}
if (offset < 0) {
return new ResponseEntity<>(ChangesResultJson.error(negativeOffsetMessage()), HttpStatus.BAD_REQUEST);
}

LocalDateTime sinceDate = null;
LocalDateTime untilDate = null;
if (since != null) {
try {
sinceDate = TimeUtil.fromUTCString(since);
} catch (Exception e) {
return new ResponseEntity<>(ChangesResultJson.error("Invalid 'since' parameter: " + since), HttpStatus.BAD_REQUEST);
}
}
if (until != null) {
try {
untilDate = TimeUtil.fromUTCString(until);
} catch (Exception e) {
return new ResponseEntity<>(ChangesResultJson.error("Invalid 'until' parameter: " + until), HttpStatus.BAD_REQUEST);
}
}

return ResponseEntity.ok()
.cacheControl(CacheControl.noCache().cachePublic())
.body(local.getChanges(sinceDate, untilDate, size, offset));
}

@GetMapping(
path = "/api/-/public-key/{publicId}",
produces = MediaType.TEXT_PLAIN_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public enum Type {
EXTENDED
}

public enum State {
ACTIVE, INACTIVE, DELETED
}

@Id
@GeneratedValue(generator = "extensionVersionSeq")
@SequenceGenerator(name = "extensionVersionSeq", sequenceName = "extension_version_seq")
Expand Down Expand Up @@ -77,6 +81,11 @@ public enum Type {

private boolean active;

@Enumerated(EnumType.STRING)
private State state = State.ACTIVE;

private LocalDateTime lastUpdated = TimeUtil.getCurrentUTC();

private boolean potentiallyMalicious;

private String displayName;
Expand Down Expand Up @@ -319,6 +328,24 @@ public boolean isActive() {

public void setActive(boolean active) {
this.active = active;
setState(active ? State.ACTIVE : State.INACTIVE);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: If for whatever reason the Version gets marked as DELETED without being set as inactive, this would clobber the DELETED flag on being set inactive. This would also clear a deleted flag if the flag was set DELETED first then had the active flag set to false. I think that setting the state automatically can be a bit dangerous as it could create non-obvious side effects, and we should instead just be intentional.

Copy link
Copy Markdown
Member Author

@gnugomez gnugomez May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think I would break it down to more methods, to be more idiomatic, although the final picture here will be dropping the active boolean flag and only using the state.. maybe doing so now could be an option since the flyway migration will set the state looking at the active boolean flag.

}

public State getState() {
return state;
}

public void setState(State state) {
this.state = state;
this.lastUpdated = TimeUtil.getCurrentUTC();
}

public LocalDateTime getLastUpdated() {
return lastUpdated;
}

public void setLastUpdated(LocalDateTime lastUpdated) {
this.lastUpdated = lastUpdated;
}

public boolean isPotentiallyMalicious() {
Expand Down
106 changes: 106 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/json/ChangeEntryJson.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/********************************************************************************
* Copyright (c) 2026 Eclipse Foundation and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(name = "ChangeEntry", description = "A single registry change entry")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChangeEntryJson {

@Schema(description = "Namespace of the extension")
private String namespace;

@Schema(description = "Name of the extension")
private String name;

@Schema(description = "Version string")
private String version;

@Schema(description = "Target platform (e.g. universal, linux-x64)")
private String targetPlatform;

@Schema(description = "Current state of this extension version (active, inactive, deleted)")
private String state;

@Schema(description = "Timestamp of the version publication (ISO-8601 UTC)")
private String timestamp;

@Schema(description = "Timestamp of the last state change (ISO-8601 UTC)")
private String lastUpdated;

@Schema(description = "Full extension metadata")
private ExtensionJson extension;

public String getNamespace() {
return namespace;
}

public void setNamespace(String namespace) {
this.namespace = namespace;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getVersion() {
return version;
}

public void setVersion(String version) {
this.version = version;
}

public String getTargetPlatform() {
return targetPlatform;
}

public void setTargetPlatform(String targetPlatform) {
this.targetPlatform = targetPlatform;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getTimestamp() {
return timestamp;
}

public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}

public String getLastUpdated() {
return lastUpdated;
}

public void setLastUpdated(String lastUpdated) {
this.lastUpdated = lastUpdated;
}

public ExtensionJson getExtension() {
return extension;
}

public void setExtension(ExtensionJson extension) {
this.extension = extension;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/********************************************************************************
* Copyright (c) 2026 Eclipse Foundation and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

import java.util.List;

@Schema(name = "ChangesResult", description = "Paginated list of registry changes")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChangesResultJson extends ResultJson {

public static ChangesResultJson error(String message) {
var result = new ChangesResultJson();
result.setError(message);
return result;
}

@Schema(description = "Number of skipped entries according to the changes request")
@NotNull
@Min(0)
private int offset;

@Schema(description = "Total number of changes matching the request")
@NotNull
@Min(0)
private int totalSize;

@Schema(description = "List of change entries, limited to the size specified in the request")
@NotNull
private List<ChangeEntryJson> changes;

public int getOffset() {
return offset;
}

public void setOffset(int offset) {
this.offset = offset;
}

public int getTotalSize() {
return totalSize;
}

public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}

public List<ChangeEntryJson> getChanges() {
return changes;
}

public void setChanges(List<ChangeEntryJson> changes) {
this.changes = changes;
}
}
Loading