From 170be9b84b940a503a9fe1af90fa4c7d11410da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 21 May 2026 16:46:25 +0200 Subject: [PATCH 1/7] feat: adding changes feed api --- .../eclipse/openvsx/LocalRegistryService.java | 23 +++ .../java/org/eclipse/openvsx/RegistryAPI.java | 58 ++++++ .../openvsx/entities/ExtensionVersion.java | 27 +++ .../eclipse/openvsx/json/ChangeEntryJson.java | 106 +++++++++++ .../openvsx/json/ChangesResultJson.java | 66 +++++++ .../ExtensionVersionJooqRepository.java | 46 ++++- .../repositories/RepositoryService.java | 4 + .../openvsx/jooq/tables/ExtensionVersion.java | 10 + .../records/ExtensionVersionRecord.java | 32 +++- .../V1_69__Extension_Version_State.sql | 6 + .../org/eclipse/openvsx/RegistryAPITest.java | 173 ++++++++++++++++++ 11 files changed, 548 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/org/eclipse/openvsx/json/ChangeEntryJson.java create mode 100644 server/src/main/java/org/eclipse/openvsx/json/ChangesResultJson.java create mode 100644 server/src/main/resources/db/migration/V1_69__Extension_Version_State.sql diff --git a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java index ad2592a39..47ae089a0 100644 --- a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java @@ -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; @@ -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()); + entry.setTimestamp(TimeUtil.toUTCString(ev.getTimestamp())); + entry.setLastUpdated(TimeUtil.toUTCString(ev.getLastUpdated())); + entry.setExtension(ev.toExtensionJson()); + return entry; + }) + .toList()); + return result; + } } diff --git a/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java b/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java index b272a706b..4ab1429db 100644 --- a/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java @@ -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; @@ -1358,6 +1359,63 @@ public ResponseEntity 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 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 diff --git a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java index 953c93694..23801326d 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java @@ -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") @@ -77,6 +81,11 @@ public enum Type { private boolean active; + @Enumerated(EnumType.STRING) + private State state = State.ACTIVE; + + private LocalDateTime lastUpdated; + private boolean potentiallyMalicious; private String displayName; @@ -319,6 +328,24 @@ public boolean isActive() { public void setActive(boolean active) { this.active = active; + setState(active ? State.ACTIVE : State.INACTIVE); + } + + 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() { diff --git a/server/src/main/java/org/eclipse/openvsx/json/ChangeEntryJson.java b/server/src/main/java/org/eclipse/openvsx/json/ChangeEntryJson.java new file mode 100644 index 000000000..aaa20a875 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/json/ChangeEntryJson.java @@ -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; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/json/ChangesResultJson.java b/server/src/main/java/org/eclipse/openvsx/json/ChangesResultJson.java new file mode 100644 index 000000000..b309ec220 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/json/ChangesResultJson.java @@ -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 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 getChanges() { + return changes; + } + + public void setChanges(List changes) { + this.changes = changes; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java index 3595eb8c3..b050e7520 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java @@ -16,6 +16,8 @@ import org.eclipse.openvsx.json.VersionTargetPlatformsJson; import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.VersionAlias; + +import java.time.LocalDateTime; import org.jooq.Record; import org.jooq.*; import org.jooq.impl.DSL; @@ -369,7 +371,7 @@ public List findAllActiveByExtensionName(String targetPlatform return fetch(query); } - private SelectQuery findAllActive() { + private SelectQuery baseExtensionVersionQuery() { var query = dsl.selectQuery(); query.addSelect( NAMESPACE.ID, @@ -394,6 +396,7 @@ private SelectQuery findAllActive() { EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -415,14 +418,21 @@ private SelectQuery findAllActive() { EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, SIGNATURE_KEY_PAIR.PUBLIC_ID ); query.addFrom(EXTENSION_VERSION); query.addJoin(EXTENSION, EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)); query.addJoin(NAMESPACE, NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)); query.addJoin(PERSONAL_ACCESS_TOKEN, JoinType.LEFT_OUTER_JOIN, PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)); - query.addJoin(USER_DATA, USER_DATA.ID.eq(PERSONAL_ACCESS_TOKEN.USER_DATA)); + query.addJoin(USER_DATA, JoinType.LEFT_OUTER_JOIN, USER_DATA.ID.eq(PERSONAL_ACCESS_TOKEN.USER_DATA)); query.addJoin(SIGNATURE_KEY_PAIR, JoinType.LEFT_OUTER_JOIN, SIGNATURE_KEY_PAIR.ID.eq(EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID)); + return query; + } + + private SelectQuery findAllActive() { + var query = baseExtensionVersionQuery(); query.addConditions(EXTENSION_VERSION.ACTIVE.eq(true)); return query; } @@ -1472,6 +1482,38 @@ public boolean isDeleteAllVersions(String namespaceName, String extensionName, L return Objects.equals(actual, all); } + public Page findChanges(LocalDateTime since, LocalDateTime before, int size, int offset) { + var conditions = new ArrayList(); + if (since != null) { + conditions.add(EXTENSION_VERSION.TIMESTAMP.greaterOrEqual(since)); + } + if (before != null) { + conditions.add(EXTENSION_VERSION.TIMESTAMP.lessThan(before)); + } + + var countField = DSL.count(); + var total = dsl.select(countField) + .from(EXTENSION_VERSION) + .where(conditions) + .fetchOne(countField); + + var query = baseExtensionVersionQuery(); + query.addConditions(conditions); + query.addOrderBy(EXTENSION_VERSION.TIMESTAMP.desc()); + query.addLimit(size); + query.addOffset(offset); + + var content = query.fetch().map(row -> { + var extVersion = toExtensionVersionFull(row); + extVersion.setActive(row.get(EXTENSION_VERSION.ACTIVE)); + extVersion.setState(ExtensionVersion.State.valueOf(row.get(EXTENSION_VERSION.STATE))); + extVersion.setLastUpdated(row.get(EXTENSION_VERSION.LAST_UPDATED)); + return extVersion; + }); + + return new PageImpl<>(content, Pageable.unpaged(), total != null ? total : 0); + } + private interface FieldMapper { Field map(Field field); } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java index 4a0fcbc12..b6b1e073d 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -444,6 +444,10 @@ public Page findActiveVersions(QueryRequest request) { return extensionVersionJooqRepo.findActiveVersions(request); } + public Page findChanges(LocalDateTime since, LocalDateTime before, int size, int offset) { + return extensionVersionJooqRepo.findChanges(since, before, size, offset); + } + public List findActiveExtensionVersions(Collection extensionIds, String targetPlatform) { return extensionVersionJooqRepo.findAllActiveByExtensionIdAndTargetPlatform(extensionIds, targetPlatform); } diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java index 83a84e43a..93cfe5d79 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java @@ -235,6 +235,16 @@ public Class getRecordType() { */ public final TableField POTENTIALLY_MALICIOUS = createField(DSL.name("potentially_malicious"), SQLDataType.BOOLEAN, this, ""); + /** + * The column public.extension_version.state. + */ + public final TableField STATE = createField(DSL.name("state"), SQLDataType.VARCHAR(32).nullable(false), this, ""); + + /** + * The column public.extension_version.last_updated. + */ + public final TableField LAST_UPDATED = createField(DSL.name("last_updated"), SQLDataType.LOCALDATETIME(6).nullable(false), this, ""); + private ExtensionVersion(Name alias, Table aliased) { this(alias, aliased, (Field[]) null, null); } diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java index d184b5756..0d7e71dee 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java @@ -525,6 +525,34 @@ public Boolean getPotentiallyMalicious() { return (Boolean) get(35); } + /** + * Setter for public.extension_version.state. + */ + public void setState(String value) { + set(36, value); + } + + /** + * Getter for public.extension_version.state. + */ + public String getState() { + return (String) get(36); + } + + /** + * Setter for public.extension_version.last_updated. + */ + public void setLastUpdated(LocalDateTime value) { + set(37, value); + } + + /** + * Getter for public.extension_version.last_updated. + */ + public LocalDateTime getLastUpdated() { + return (LocalDateTime) get(37); + } + // ------------------------------------------------------------------------- // Primary key information // ------------------------------------------------------------------------- @@ -548,7 +576,7 @@ public ExtensionVersionRecord() { /** * Create a detached, initialised ExtensionVersionRecord */ - public ExtensionVersionRecord(Long id, String bugs, String description, String displayName, String galleryColor, String galleryTheme, String homepage, String license, String markdown, Boolean preview, String qna, String repository, LocalDateTime timestamp, String version, Long extensionId, Long publishedWithId, Boolean active, String dependencies, String bundledExtensions, String engines, String categories, String tags, String extensionKind, Boolean preRelease, String targetPlatform, String localizedLanguages, String sponsorLink, Long signatureKeyPairId, Integer semverMajor, Integer semverMinor, Integer semverPatch, String semverPreRelease, Boolean semverIsPreRelease, String semverBuildMetadata, Boolean universalTargetPlatform, Boolean potentiallyMalicious) { + public ExtensionVersionRecord(Long id, String bugs, String description, String displayName, String galleryColor, String galleryTheme, String homepage, String license, String markdown, Boolean preview, String qna, String repository, LocalDateTime timestamp, String version, Long extensionId, Long publishedWithId, Boolean active, String dependencies, String bundledExtensions, String engines, String categories, String tags, String extensionKind, Boolean preRelease, String targetPlatform, String localizedLanguages, String sponsorLink, Long signatureKeyPairId, Integer semverMajor, Integer semverMinor, Integer semverPatch, String semverPreRelease, Boolean semverIsPreRelease, String semverBuildMetadata, Boolean universalTargetPlatform, Boolean potentiallyMalicious, String state, LocalDateTime lastUpdated) { super(ExtensionVersion.EXTENSION_VERSION); setId(id); @@ -587,6 +615,8 @@ public ExtensionVersionRecord(Long id, String bugs, String description, String d setSemverBuildMetadata(semverBuildMetadata); setUniversalTargetPlatform(universalTargetPlatform); setPotentiallyMalicious(potentiallyMalicious); + setState(state); + setLastUpdated(lastUpdated); resetChangedOnNotNull(); } } diff --git a/server/src/main/resources/db/migration/V1_69__Extension_Version_State.sql b/server/src/main/resources/db/migration/V1_69__Extension_Version_State.sql new file mode 100644 index 000000000..ca39656a5 --- /dev/null +++ b/server/src/main/resources/db/migration/V1_69__Extension_Version_State.sql @@ -0,0 +1,6 @@ +ALTER TABLE extension_version ADD COLUMN state VARCHAR(32) NOT NULL DEFAULT 'ACTIVE'; +UPDATE extension_version SET state = 'INACTIVE' WHERE active = false; + +ALTER TABLE extension_version ADD COLUMN last_updated TIMESTAMP; +UPDATE extension_version SET last_updated = timestamp; +ALTER TABLE extension_version ALTER COLUMN last_updated SET NOT NULL; diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index 0f8915e2d..b61d92a61 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -1904,6 +1904,156 @@ void testDeleteNonExistingReview() throws Exception { } + @Test + void testGetChanges() throws Exception { + var extVersion = mockExtensionVersionForChanges(); + Mockito.when(repositories.findChanges(null, null, 100, 0)) + .thenReturn(new PageImpl<>(List.of(extVersion), Pageable.unpaged(), 1)); + + mockMvc.perform(get("/api/-/changes")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> { + var entry = new ChangeEntryJson(); + entry.setNamespace("foo"); + entry.setName("bar"); + entry.setVersion("1.0.0"); + entry.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); + entry.setState("active"); + entry.setTimestamp("2000-01-01T10:00Z"); + entry.setLastUpdated("2000-01-01T10:00Z"); + c.setOffset(0); + c.setTotalSize(1); + c.setChanges(List.of(entry)); + }))); + } + + @Test + void testGetChangesEmpty() throws Exception { + Mockito.when(repositories.findChanges(null, null, 100, 0)) + .thenReturn(new PageImpl<>(Collections.emptyList(), Pageable.unpaged(), 0)); + + mockMvc.perform(get("/api/-/changes")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> { + c.setOffset(0); + c.setTotalSize(0); + c.setChanges(Collections.emptyList()); + }))); + } + + @Test + void testGetChangesInactiveVersion() throws Exception { + var extVersion = mockExtensionVersionForChanges(); + extVersion.setActive(false); + extVersion.setState(ExtensionVersion.State.INACTIVE); + Mockito.when(repositories.findChanges(null, null, 100, 0)) + .thenReturn(new PageImpl<>(List.of(extVersion), Pageable.unpaged(), 1)); + + mockMvc.perform(get("/api/-/changes")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> { + var entry = new ChangeEntryJson(); + entry.setState("inactive"); + c.setChanges(List.of(entry)); + }))); + } + + @Test + void testGetChangesWithPagination() throws Exception { + var extVersion = mockExtensionVersionForChanges(); + Mockito.when(repositories.findChanges(null, null, 10, 5)) + .thenReturn(new PageImpl<>(List.of(extVersion), Pageable.unpaged(), 20)); + + mockMvc.perform(get("/api/-/changes").param("size", "10").param("offset", "5")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> { + c.setOffset(5); + c.setTotalSize(20); + }))); + } + + @Test + void testGetChangesWithTimeFilter() throws Exception { + var extVersion = mockExtensionVersionForChanges(); + var since = LocalDateTime.parse("2024-01-01T00:00:00"); + var before = LocalDateTime.parse("2024-12-31T23:59:59"); + Mockito.when(repositories.findChanges(since, before, 100, 0)) + .thenReturn(new PageImpl<>(List.of(extVersion), Pageable.unpaged(), 1)); + + mockMvc.perform(get("/api/-/changes") + .param("since", "2024-01-01T00:00:00Z") + .param("until", "2024-12-31T23:59:59Z")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> { + c.setTotalSize(1); + }))); + } + + @Test + void testGetChangesZeroSize() throws Exception { + Mockito.when(repositories.findChanges(null, null, 0, 0)) + .thenReturn(new PageImpl<>(Collections.emptyList(), Pageable.unpaged(), 5)); + + mockMvc.perform(get("/api/-/changes").param("size", "0")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> { + c.setTotalSize(5); + c.setChanges(Collections.emptyList()); + }))); + } + + @Test + void testGetChangesOnlySince() throws Exception { + var extVersion = mockExtensionVersionForChanges(); + var since = LocalDateTime.parse("2024-01-01T00:00:00"); + Mockito.when(repositories.findChanges(since, null, 100, 0)) + .thenReturn(new PageImpl<>(List.of(extVersion), Pageable.unpaged(), 1)); + + mockMvc.perform(get("/api/-/changes").param("since", "2024-01-01T00:00:00Z")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> c.setTotalSize(1)))); + } + + @Test + void testGetChangesOnlyUntil() throws Exception { + var extVersion = mockExtensionVersionForChanges(); + var before = LocalDateTime.parse("2024-12-31T23:59:59"); + Mockito.when(repositories.findChanges(null, before, 100, 0)) + .thenReturn(new PageImpl<>(List.of(extVersion), Pageable.unpaged(), 1)); + + mockMvc.perform(get("/api/-/changes").param("until", "2024-12-31T23:59:59Z")) + .andExpect(status().isOk()) + .andExpect(content().json(changesJson(c -> c.setTotalSize(1)))); + } + + @Test + void testGetChangesNegativeSize() throws Exception { + mockMvc.perform(get("/api/-/changes").param("size", "-1")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(errorJson("The parameter 'size' must not be negative."))); + } + + @Test + void testGetChangesNegativeOffset() throws Exception { + mockMvc.perform(get("/api/-/changes").param("offset", "-1")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(errorJson("The parameter 'offset' must not be negative."))); + } + + @Test + void testGetChangesInvalidSince() throws Exception { + mockMvc.perform(get("/api/-/changes").param("since", "not-a-date")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(errorJson("Invalid 'since' parameter: not-a-date"))); + } + + @Test + void testGetChangesInvalidUntil() throws Exception { + mockMvc.perform(get("/api/-/changes").param("until", "not-a-date")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(errorJson("Invalid 'until' parameter: not-a-date"))); + } + //---------- UTILITY ----------// private void mockActiveVersion() { @@ -2476,6 +2626,29 @@ private String successJson(String message) throws JsonProcessingException { return new ObjectMapper().writeValueAsString(json); } + private ExtensionVersion mockExtensionVersionForChanges() { + var namespace = new Namespace(); + namespace.setName("foo"); + var extension = new Extension(); + extension.setName("bar"); + extension.setNamespace(namespace); + var extVersion = new ExtensionVersion(); + extVersion.setVersion("1.0.0"); + extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); + extVersion.setTimestamp(LocalDateTime.parse("2000-01-01T10:00")); + extVersion.setActive(true); + extVersion.setState(ExtensionVersion.State.ACTIVE); + extVersion.setLastUpdated(LocalDateTime.parse("2000-01-01T10:00")); + extVersion.setExtension(extension); + return extVersion; + } + + private String changesJson(Consumer content) throws JsonProcessingException { + var json = new ChangesResultJson(); + content.accept(json); + return new ObjectMapper().writeValueAsString(json); + } + private String errorJson(String message) throws JsonProcessingException { var json = ResultJson.error(message); return new ObjectMapper().writeValueAsString(json); From ea8c7ebeab88c52adf76b59bb85be563a8af9de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 21 May 2026 17:12:45 +0200 Subject: [PATCH 2/7] fix: initialize lastUpdated on ExtensionVersion construction --- .../java/org/eclipse/openvsx/entities/ExtensionVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java index 23801326d..c3ca76c59 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java @@ -84,7 +84,7 @@ public enum State { @Enumerated(EnumType.STRING) private State state = State.ACTIVE; - private LocalDateTime lastUpdated; + private LocalDateTime lastUpdated = TimeUtil.getCurrentUTC(); private boolean potentiallyMalicious; From e92c13d8d72a437984b7ee9443a75903df315043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 21 May 2026 17:21:54 +0200 Subject: [PATCH 3/7] test: fix failing tests --- server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java | 1 + .../eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index b61d92a61..1e3bc2564 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -1954,6 +1954,7 @@ void testGetChangesInactiveVersion() throws Exception { .andExpect(content().json(changesJson(c -> { var entry = new ChangeEntryJson(); entry.setState("inactive"); + c.setTotalSize(1); c.setChanges(List.of(entry)); }))); } diff --git a/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java b/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java index 2271f1044..0c79034dd 100644 --- a/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java +++ b/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java @@ -372,6 +372,7 @@ void testExecuteQueries() { () -> repositories.hasScanCheckResult(1L, "SECRET_SCANNING"), () -> repositories.findScanCheckResults(scan), () -> repositories.findScanCheckResultsByScanId(1L), + () -> repositories.findChanges(NOW.minusYears(1), NOW.plusYears(1), 10, 0), // Extension version lookup including inactive () -> repositories.findExtensionVersionIncludingInactive(namespace.getName(), extension.getName(), extVersion.getTargetPlatform(), extVersion.getVersion()), // Rate limit tests From 1b69cee1e66cf6bf80390ed65e194c0365ff952d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Fri, 22 May 2026 11:54:15 +0200 Subject: [PATCH 4/7] feat(changes): use uppercase for state enum values --- .../main/java/org/eclipse/openvsx/LocalRegistryService.java | 2 +- .../java/org/eclipse/openvsx/entities/ExtensionVersion.java | 4 ++-- server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java index 47ae089a0..638a2c2ad 100644 --- a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java @@ -1167,7 +1167,7 @@ public ChangesResultJson getChanges(LocalDateTime since, LocalDateTime before, i entry.setName(ev.getExtension().getName()); entry.setVersion(ev.getVersion()); entry.setTargetPlatform(ev.getTargetPlatform()); - entry.setState(ev.getState().name().toLowerCase()); + entry.setState(ev.getState().name()); entry.setTimestamp(TimeUtil.toUTCString(ev.getTimestamp())); entry.setLastUpdated(TimeUtil.toUTCString(ev.getLastUpdated())); entry.setExtension(ev.toExtensionJson()); diff --git a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java index c3ca76c59..e4c1bc113 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java @@ -323,11 +323,10 @@ public void setPublishedWith(PersonalAccessToken publishedWith) { } public boolean isActive() { - return active; + return state == State.ACTIVE; } public void setActive(boolean active) { - this.active = active; setState(active ? State.ACTIVE : State.INACTIVE); } @@ -336,6 +335,7 @@ public State getState() { } public void setState(State state) { + this.active = state == State.ACTIVE; this.state = state; this.lastUpdated = TimeUtil.getCurrentUTC(); } diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index 1e3bc2564..0726f4578 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -1918,7 +1918,7 @@ void testGetChanges() throws Exception { entry.setName("bar"); entry.setVersion("1.0.0"); entry.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); - entry.setState("active"); + entry.setState("ACTIVE"); entry.setTimestamp("2000-01-01T10:00Z"); entry.setLastUpdated("2000-01-01T10:00Z"); c.setOffset(0); @@ -1953,7 +1953,7 @@ void testGetChangesInactiveVersion() throws Exception { .andExpect(status().isOk()) .andExpect(content().json(changesJson(c -> { var entry = new ChangeEntryJson(); - entry.setState("inactive"); + entry.setState("INACTIVE"); c.setTotalSize(1); c.setChanges(List.of(entry)); }))); From 5791dfae9b02878576bead4fa27cae9b1d710aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Fri, 22 May 2026 12:31:41 +0200 Subject: [PATCH 5/7] refactor(changes): centralize active/state/lastUpdated mapping in toExtensionVersionFull Assisted-By: anthropic:claude-opus-4-7[1m] --- .../ExtensionVersionJooqRepository.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java index b050e7520..0449ae3f0 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java @@ -460,6 +460,9 @@ private ExtensionVersion toExtensionVersionFull( extVersion.setBugs(row.get(extensionVersionMapper.map(EXTENSION_VERSION.BUGS))); extVersion.setMarkdown(row.get(extensionVersionMapper.map(EXTENSION_VERSION.MARKDOWN))); extVersion.setQna(row.get(extensionVersionMapper.map(EXTENSION_VERSION.QNA))); + extVersion.setActive(row.get(extensionVersionMapper.map(EXTENSION_VERSION.ACTIVE))); + extVersion.setState(ExtensionVersion.State.valueOf(row.get(extensionVersionMapper.map(EXTENSION_VERSION.STATE)))); + extVersion.setLastUpdated(row.get(extensionVersionMapper.map(EXTENSION_VERSION.LAST_UPDATED))); if(extension == null) { var newExtension = extVersion.getExtension(); @@ -845,6 +848,7 @@ public List findLatest(Collection extensionIds) { EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -866,6 +870,8 @@ public List findLatest(Collection extensionIds) { EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID, EXTENSION_VERSION.PUBLISHED_WITH_ID ); @@ -890,6 +896,7 @@ public List findLatest(Collection extensionIds) { latest.field(EXTENSION_VERSION.POTENTIALLY_MALICIOUS), latest.field(EXTENSION_VERSION.VERSION), latest.field(EXTENSION_VERSION.TARGET_PLATFORM), + latest.field(EXTENSION_VERSION.ACTIVE), latest.field(EXTENSION_VERSION.PREVIEW), latest.field(EXTENSION_VERSION.PRE_RELEASE), latest.field(EXTENSION_VERSION.TIMESTAMP), @@ -911,6 +918,8 @@ public List findLatest(Collection extensionIds) { latest.field(EXTENSION_VERSION.QNA), latest.field(EXTENSION_VERSION.DEPENDENCIES), latest.field(EXTENSION_VERSION.BUNDLED_EXTENSIONS), + latest.field(EXTENSION_VERSION.STATE), + latest.field(EXTENSION_VERSION.LAST_UPDATED), SIGNATURE_KEY_PAIR.PUBLIC_ID, USER_DATA.ID, USER_DATA.ROLE, @@ -1006,6 +1015,7 @@ public List findLatest(UserData user) { EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -1027,6 +1037,8 @@ public List findLatest(UserData user) { EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID, EXTENSION_VERSION.PUBLISHED_WITH_ID ); @@ -1054,6 +1066,7 @@ public List findLatest(UserData user) { latest.field(EXTENSION_VERSION.POTENTIALLY_MALICIOUS), latest.field(EXTENSION_VERSION.VERSION), latest.field(EXTENSION_VERSION.TARGET_PLATFORM), + latest.field(EXTENSION_VERSION.ACTIVE), latest.field(EXTENSION_VERSION.PREVIEW), latest.field(EXTENSION_VERSION.PRE_RELEASE), latest.field(EXTENSION_VERSION.TIMESTAMP), @@ -1075,6 +1088,8 @@ public List findLatest(UserData user) { latest.field(EXTENSION_VERSION.QNA), latest.field(EXTENSION_VERSION.DEPENDENCIES), latest.field(EXTENSION_VERSION.BUNDLED_EXTENSIONS), + latest.field(EXTENSION_VERSION.STATE), + latest.field(EXTENSION_VERSION.LAST_UPDATED), SIGNATURE_KEY_PAIR.PUBLIC_ID, USER_DATA.ID, USER_DATA.ROLE, @@ -1108,6 +1123,7 @@ public ExtensionVersion findLatest(UserData user, String namespace, String exten EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -1129,6 +1145,8 @@ public ExtensionVersion findLatest(UserData user, String namespace, String exten EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID, EXTENSION_VERSION.PUBLISHED_WITH_ID ); @@ -1156,6 +1174,7 @@ public ExtensionVersion findLatest(UserData user, String namespace, String exten latest.field(EXTENSION_VERSION.POTENTIALLY_MALICIOUS), latest.field(EXTENSION_VERSION.VERSION), latest.field(EXTENSION_VERSION.TARGET_PLATFORM), + latest.field(EXTENSION_VERSION.ACTIVE), latest.field(EXTENSION_VERSION.PREVIEW), latest.field(EXTENSION_VERSION.PRE_RELEASE), latest.field(EXTENSION_VERSION.TIMESTAMP), @@ -1177,6 +1196,8 @@ public ExtensionVersion findLatest(UserData user, String namespace, String exten latest.field(EXTENSION_VERSION.QNA), latest.field(EXTENSION_VERSION.DEPENDENCIES), latest.field(EXTENSION_VERSION.BUNDLED_EXTENSIONS), + latest.field(EXTENSION_VERSION.STATE), + latest.field(EXTENSION_VERSION.LAST_UPDATED), SIGNATURE_KEY_PAIR.PUBLIC_ID, USER_DATA.ID, USER_DATA.ROLE, @@ -1503,14 +1524,7 @@ public Page findChanges(LocalDateTime since, LocalDateTime bef query.addLimit(size); query.addOffset(offset); - var content = query.fetch().map(row -> { - var extVersion = toExtensionVersionFull(row); - extVersion.setActive(row.get(EXTENSION_VERSION.ACTIVE)); - extVersion.setState(ExtensionVersion.State.valueOf(row.get(EXTENSION_VERSION.STATE))); - extVersion.setLastUpdated(row.get(EXTENSION_VERSION.LAST_UPDATED)); - return extVersion; - }); - + var content = query.fetch().map(this::toExtensionVersionFull); return new PageImpl<>(content, Pageable.unpaged(), total != null ? total : 0); } From 246de4e226dcd3efa74dd64a90064a00e868fd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Fri, 22 May 2026 12:52:22 +0200 Subject: [PATCH 6/7] fix(entities): reject state transitions away from DELETED --- .../openvsx/entities/ExtensionVersion.java | 3 + .../entities/ExtensionVersionTest.java | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 server/src/test/java/org/eclipse/openvsx/entities/ExtensionVersionTest.java diff --git a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java index e4c1bc113..6814181d4 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java @@ -335,6 +335,9 @@ public State getState() { } public void setState(State state) { + if (this.state == State.DELETED && state != State.DELETED) { + throw new IllegalStateException("Cannot transition away from DELETED state"); + } this.active = state == State.ACTIVE; this.state = state; this.lastUpdated = TimeUtil.getCurrentUTC(); diff --git a/server/src/test/java/org/eclipse/openvsx/entities/ExtensionVersionTest.java b/server/src/test/java/org/eclipse/openvsx/entities/ExtensionVersionTest.java new file mode 100644 index 000000000..92c329d18 --- /dev/null +++ b/server/src/test/java/org/eclipse/openvsx/entities/ExtensionVersionTest.java @@ -0,0 +1,62 @@ +/** ****************************************************************************** + * 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.entities; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ExtensionVersionTest { + + @Test + void setStateRejectsTransitionAwayFromDeleted() { + var version = new ExtensionVersion(); + version.setState(ExtensionVersion.State.DELETED); + + assertThrows(IllegalStateException.class, () -> version.setState(ExtensionVersion.State.ACTIVE)); + assertThrows(IllegalStateException.class, () -> version.setState(ExtensionVersion.State.INACTIVE)); + assertThat(version.getState()).isEqualTo(ExtensionVersion.State.DELETED); + assertThat(version.isActive()).isFalse(); + } + + @Test + void setStateDeletedIsIdempotent() { + var version = new ExtensionVersion(); + version.setState(ExtensionVersion.State.DELETED); + + version.setState(ExtensionVersion.State.DELETED); + assertThat(version.getState()).isEqualTo(ExtensionVersion.State.DELETED); + } + + @Test + void setActiveOnDeletedVersionThrows() { + var version = new ExtensionVersion(); + version.setState(ExtensionVersion.State.DELETED); + + assertThrows(IllegalStateException.class, () -> version.setActive(false)); + assertThrows(IllegalStateException.class, () -> version.setActive(true)); + assertThat(version.getState()).isEqualTo(ExtensionVersion.State.DELETED); + assertThat(version.isActive()).isFalse(); + } + + @Test + void setActiveTransitionsBetweenActiveAndInactive() { + var version = new ExtensionVersion(); + + version.setActive(false); + assertThat(version.getState()).isEqualTo(ExtensionVersion.State.INACTIVE); + assertThat(version.isActive()).isFalse(); + + version.setActive(true); + assertThat(version.getState()).isEqualTo(ExtensionVersion.State.ACTIVE); + assertThat(version.isActive()).isTrue(); + } +} From 7d8ad74777887428697aeb33071f2d6a19d76fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Fri, 22 May 2026 13:10:47 +0200 Subject: [PATCH 7/7] fix(repo): project active/state/lastUpdated in remaining version selects --- .../repositories/ExtensionVersionJooqRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java index 0449ae3f0..b90d91136 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java @@ -713,6 +713,7 @@ public ExtensionVersion findLatest( EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -734,6 +735,8 @@ public ExtensionVersion findLatest( EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, SIGNATURE_KEY_PAIR.PUBLIC_ID ); query.addJoin(PERSONAL_ACCESS_TOKEN, JoinType.LEFT_OUTER_JOIN, PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)); @@ -777,6 +780,7 @@ public ExtensionVersion findLatest( EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -798,6 +802,8 @@ public ExtensionVersion findLatest( EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, SIGNATURE_KEY_PAIR.PUBLIC_ID ); query.addJoin(PERSONAL_ACCESS_TOKEN, JoinType.LEFT_OUTER_JOIN, PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)); @@ -1390,6 +1396,7 @@ private ExtensionVersion findInternal(SelectQuery query, String namespac EXTENSION_VERSION.VERSION, EXTENSION_VERSION.POTENTIALLY_MALICIOUS, EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.ACTIVE, EXTENSION_VERSION.PREVIEW, EXTENSION_VERSION.PRE_RELEASE, EXTENSION_VERSION.TIMESTAMP, @@ -1411,6 +1418,8 @@ private ExtensionVersion findInternal(SelectQuery query, String namespac EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.STATE, + EXTENSION_VERSION.LAST_UPDATED, SIGNATURE_KEY_PAIR.PUBLIC_ID ); query.addJoin(PERSONAL_ACCESS_TOKEN, JoinType.LEFT_OUTER_JOIN, PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID));