diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java index 75f51c05a..b07cd6c6f 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java @@ -208,6 +208,9 @@ public void reactivateExtensions(UserData user) { var affectedExtensions = new LinkedHashSet(); var versions = repositories.findVersionsByUser(user, false); for (var version : versions) { + if (version.getState() == ExtensionVersion.State.DELETED) { + continue; + } version.setActive(true); affectedExtensions.add(version.getExtension()); } @@ -236,7 +239,7 @@ public ResultJson deleteExtension( HttpStatus.CONFLICT); } - if (extension == null) { + if (extension == null || repositories.countVersions(namespaceName, extensionName) == 0) { var message = "Extension not found: " + NamingUtil.toExtensionId(namespaceName, extensionName); throw new ErrorResultException(message, HttpStatus.NOT_FOUND); } @@ -246,7 +249,13 @@ public ResultJson deleteExtension( results.add(deleteExtension(user, extension)); } else { for (var targetVersion : targetVersions) { - var extVersion = repositories.findVersion(user, targetVersion.version(), targetVersion.targetPlatform(), extensionName, namespaceName); + var extVersion = repositories.findVersion( + user, + targetVersion.version(), + targetVersion.targetPlatform(), + extensionName, + namespaceName + ); if (extVersion == null) { var message = "Extension not found: " + NamingUtil.toLogFormat(namespaceName, extensionName, targetVersion.targetPlatform(), targetVersion.version()); throw new ErrorResultException(message, HttpStatus.NOT_FOUND); @@ -293,8 +302,7 @@ protected ResultJson deleteExtension(UserData user, Extension extension) throws cache.evictExtensionJsons(deprecatedExtension); } - entityManager.remove(extension); - search.removeSearchEntry(extension); + updateExtension(extension); var result = ResultJson.success("Deleted " + NamingUtil.toExtensionId(extension)); logs.logAction(user, result); @@ -304,7 +312,6 @@ protected ResultJson deleteExtension(UserData user, Extension extension) throws protected ResultJson deleteExtension(UserData user, ExtensionVersion extVersion) { var extension = extVersion.getExtension(); removeExtensionVersion(extVersion); - extension.getVersions().remove(extVersion); updateExtension(extension); var result = ResultJson.success("Deleted " + NamingUtil.toLogFormat(extVersion)); @@ -319,6 +326,6 @@ private void removeExtensionVersion(ExtensionVersion extVersion) { repositories.findFiles(extVersion).map(RemoveFileJobRequest::new).forEach(scheduler::enqueue); repositories.deleteFiles(extVersion); - entityManager.remove(extVersion); + extVersion.setState(ExtensionVersion.State.DELETED); } } diff --git a/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java b/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java index 14b4c189c..6e6f60cfe 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java +++ b/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java @@ -169,14 +169,12 @@ public void deleteExtensionAndDependencies(Extension extension, UserData admin, cache.evictExtensionJsons(deprecatedExtension); } - entityManager.remove(extension); + extensions.updateExtension(extension); // evict the cache entries only after the changes have been commited cache.evictExtensionJsons(extension); cache.evictNamespaceDetails(extension); cache.evictLatestExtensionVersion(extension); - - search.removeSearchEntry(extension); logs.logAction(admin, ResultJson.success("Deleted " + NamingUtil.toExtensionId(extension))); } @@ -188,7 +186,6 @@ protected void deleteExtensionAndDependencies(ExtensionVersion extVersion, UserD } removeExtensionVersion(extVersion); - extension.getVersions().remove(extVersion); extensions.updateExtension(extension); logs.logAction(admin, ResultJson.success("Deleted " + NamingUtil.toLogFormat(extVersion))); } @@ -219,7 +216,7 @@ public ResultJson deleteExtension( public ResultJson deleteExtension(String namespaceName, String extensionName, UserData admin) throws ErrorResultException { var extension = repositories.findExtension(extensionName, namespaceName); - if (extension == null) { + if (extension == null || repositories.countVersions(namespaceName, extensionName) == 0) { var extensionId = NamingUtil.toExtensionId(namespaceName, extensionName); throw new ErrorResultException("Extension not found: " + extensionId, HttpStatus.NOT_FOUND); } @@ -270,15 +267,13 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws cache.evictExtensionJsons(deprecatedExtension); } - entityManager.remove(extension); + extensions.updateExtension(extension); // evict the cache entries only after the changes have been commited cache.evictExtensionJsons(extension); cache.evictNamespaceDetails(extension); cache.evictLatestExtensionVersion(extension); - search.removeSearchEntry(extension); - var result = ResultJson.success("Deleted " + NamingUtil.toExtensionId(extension)); logs.logAction(admin, result); return result; @@ -287,7 +282,6 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws protected ResultJson deleteExtension(ExtensionVersion extVersion, UserData admin) { var extension = extVersion.getExtension(); removeExtensionVersion(extVersion); - extension.getVersions().remove(extVersion); extensions.updateExtension(extension); var result = ResultJson.success("Deleted " + NamingUtil.toLogFormat(extVersion)); @@ -340,7 +334,7 @@ private void removeExtensionVersion(ExtensionVersion extVersion) { repositories.findFiles(extVersion).map(RemoveFileJobRequest::new).forEach(scheduler::enqueue); repositories.deleteFiles(extVersion); - entityManager.remove(extVersion); + extVersion.setState(ExtensionVersion.State.DELETED); } private String userNotFoundMessage(String user) { 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 f9148f256..8c202c536 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") @@ -75,8 +79,20 @@ public enum Type { @ManyToOne private PersonalAccessToken publishedWith; + /** + * Legacy persisted mirror of {@link #state} kept for compatibility with active-based queries. + * + * @deprecated Use {@link #getState()} / {@link #setState(State)}. This field is planned for + * removal once repositories and SQL queries fully migrate to state-based filtering. + */ + @Deprecated(forRemoval = true) private boolean active; + @Enumerated(EnumType.STRING) + private State state = State.ACTIVE; + + private LocalDateTime lastUpdated = TimeUtil.getCurrentUTC(); + private boolean potentiallyMalicious; private String displayName; @@ -313,12 +329,41 @@ public void setPublishedWith(PersonalAccessToken publishedWith) { this.publishedWith = publishedWith; } + /** + * @deprecated Use {@code getState() == State.ACTIVE}. + */ + @Deprecated(forRemoval = true) public boolean isActive() { - return active; + return state == State.ACTIVE; } + /** + * @deprecated Use {@link #setState(State)} with {@link State#ACTIVE} or {@link State#INACTIVE}. + */ + @Deprecated(forRemoval = true) public void setActive(boolean active) { - this.active = active; + setState(active ? State.ACTIVE : State.INACTIVE); + } + + public State getState() { + return state; + } + + 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(); + } + + 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/publish/PublishExtensionVersionHandler.java b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java index 999d1bd91..5798595b5 100644 --- a/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java @@ -171,7 +171,7 @@ private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Us entityManager.persist(extension); } else { - var existingVersion = repositories.findVersion(extVersion.getVersion(), extVersion.getTargetPlatform(), extension); + var existingVersion = repositories.findVersionIncludingDeleted(extVersion.getVersion(), extVersion.getTargetPlatform(), extension); if (existingVersion != null) { var extVersionId = NamingUtil.toLogFormat(namespaceName, extensionName, extVersion.getTargetPlatform(), extVersion.getVersion()); var message = "Extension " + extVersionId + " is already published"; 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 1476aee69..d5eb75f44 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java @@ -369,7 +369,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 +394,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 +416,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; } @@ -450,6 +458,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(); @@ -464,19 +475,22 @@ private ExtensionVersion toExtensionVersionFull( newNamespace.setPublicId(row.get(NAMESPACE.PUBLIC_ID)); } - var user = new UserData(); - user.setId(row.get(USER_DATA.ID)); - user.setRole(UserData.Role.valueOfIgnoreCase(row.get(USER_DATA.ROLE))); - user.setLoginName(row.get(USER_DATA.LOGIN_NAME)); - user.setFullName(row.get(USER_DATA.FULL_NAME)); - user.setAvatarUrl(row.get(USER_DATA.AVATAR_URL)); - user.setProviderUrl(row.get(USER_DATA.PROVIDER_URL)); - user.setProvider(row.get(USER_DATA.PROVIDER)); - - var token = new PersonalAccessToken(); - token.setUser(user); + var userId = row.get(USER_DATA.ID); + if (userId != null) { + var user = new UserData(); + user.setId(userId); + user.setRole(UserData.Role.valueOfIgnoreCase(row.get(USER_DATA.ROLE))); + user.setLoginName(row.get(USER_DATA.LOGIN_NAME)); + user.setFullName(row.get(USER_DATA.FULL_NAME)); + user.setAvatarUrl(row.get(USER_DATA.AVATAR_URL)); + user.setProviderUrl(row.get(USER_DATA.PROVIDER_URL)); + user.setProvider(row.get(USER_DATA.PROVIDER)); + + var token = new PersonalAccessToken(); + token.setUser(user); + extVersion.setPublishedWith(token); + } - extVersion.setPublishedWith(token); extVersion.setType(ExtensionVersion.Type.REGULAR); return extVersion; } @@ -556,6 +570,7 @@ public List findTargetPlatformsGroupedByVersion(Exte ) .from(EXTENSION_VERSION) .where(EXTENSION_VERSION.EXTENSION_ID.eq(extension.getId())) + .and(EXTENSION_VERSION.STATE.ne(ExtensionVersion.State.DELETED.name())) .groupBy( EXTENSION_VERSION.SEMVER_MAJOR, EXTENSION_VERSION.SEMVER_MINOR, @@ -596,6 +611,7 @@ public List findTargetPlatformsGroupedByVersion(Exte .join(PERSONAL_ACCESS_TOKEN).on(PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)) .where(EXTENSION_VERSION.EXTENSION_ID.eq(extension.getId())) .and(PERSONAL_ACCESS_TOKEN.USER_DATA.eq(user.getId())) + .and(EXTENSION_VERSION.STATE.ne(ExtensionVersion.State.DELETED.name())) .groupBy( EXTENSION_VERSION.SEMVER_MAJOR, EXTENSION_VERSION.SEMVER_MINOR, @@ -700,6 +716,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, @@ -721,6 +738,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)); @@ -764,6 +783,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, @@ -785,6 +805,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)); @@ -835,6 +857,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, @@ -856,6 +879,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 ); @@ -880,6 +905,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), @@ -901,6 +927,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, @@ -996,6 +1024,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, @@ -1017,6 +1046,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 ); @@ -1044,6 +1075,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), @@ -1065,6 +1097,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, @@ -1098,6 +1132,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, @@ -1119,6 +1154,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 ); @@ -1146,6 +1183,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), @@ -1167,6 +1205,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, @@ -1359,6 +1399,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, @@ -1380,6 +1421,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)); @@ -1441,6 +1484,19 @@ public Integer count(String namespaceName, String extensionName) { .fetchOne("count", Integer.class); } + public Integer countNonDeleted(String namespaceName, String extensionName) { + return dsl.select(DSL.count().as("count")) + .from(EXTENSION_VERSION) + .join(EXTENSION) + .on(EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)) + .join(NAMESPACE) + .on(NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)) + .where(NAMESPACE.NAME.equalIgnoreCase(namespaceName)) + .and(EXTENSION.NAME.equalIgnoreCase(extensionName)) + .and(EXTENSION_VERSION.STATE.ne(ExtensionVersion.State.DELETED.name())) + .fetchOne("count", Integer.class); + } + public boolean isDeleteAllVersions(String namespaceName, String extensionName, List targetVersions, UserData user) { if(targetVersions.isEmpty()) { return false; @@ -1452,6 +1508,7 @@ public boolean isDeleteAllVersions(String namespaceName, String extensionName, L .join(NAMESPACE).on(NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)) .and(NAMESPACE.NAME.equalIgnoreCase(namespaceName)) .and(EXTENSION.NAME.equalIgnoreCase(extensionName)) + .and(EXTENSION_VERSION.STATE.ne(ExtensionVersion.State.DELETED.name())) .fetchOne("all", Integer.class); var rows = targetVersions.stream().map((tv) -> DSL.row(tv.version(), tv.targetPlatform())).toArray(Row2[]::new); @@ -1467,6 +1524,7 @@ public boolean isDeleteAllVersions(String namespaceName, String extensionName, L .where(PERSONAL_ACCESS_TOKEN.USER_DATA.eq(user.getId())) .and(NAMESPACE.NAME.equalIgnoreCase(namespaceName)) .and(EXTENSION.NAME.equalIgnoreCase(extensionName)) + .and(EXTENSION_VERSION.STATE.ne(ExtensionVersion.State.DELETED.name())) .fetchOne("actual", Integer.class); return Objects.equals(actual, all); diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java index fee8d9040..2251e4488 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java @@ -27,10 +27,34 @@ public interface ExtensionVersionRepository extends Repository findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String extensionName, String namespace); Streamable findByPublishedWithAndActive(PersonalAccessToken publishedWith, boolean active); @@ -41,11 +65,21 @@ public interface ExtensionVersionRepository extends Repository findBySignatureKeyPairNotOrSignatureKeyPairIsNull(SignatureKeyPair keyPair); - @Query("select ev from ExtensionVersion ev where concat(',', ev.bundledExtensions, ',') like concat('%,', ?1, ',%')") - Streamable findByBundledExtensions(String extensionId); - - @Query("select ev from ExtensionVersion ev where concat(',', ev.dependencies, ',') like concat('%,', ?1, ',%')") - Streamable findByDependencies(String extensionId); + @Query(""" + select ev + from ExtensionVersion ev + where concat(',', ev.bundledExtensions, ',') like concat('%,', ?1, ',%') + and ev.state <> ?2 + """) + Streamable findByBundledExtensionsAndStateNot(String extensionId, ExtensionVersion.State state); + + @Query(""" + select ev + from ExtensionVersion ev + where concat(',', ev.dependencies, ',') like concat('%,', ?1, ',%') + and ev.state <> ?2 + """) + Streamable findByDependenciesAndStateNot(String extensionId, ExtensionVersion.State state); @Query("select min(ev.timestamp) from ExtensionVersion ev") LocalDateTime getOldestTimestamp(); @@ -56,5 +90,18 @@ public interface ExtensionVersionRepository extends Repository findByExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String extension, String namespace, Pageable page); + Page findByExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue( + String extension, + String namespace, + Pageable page + ); + Page findByTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String targetPlatform, String extension, String namespace, Pageable page); + + Page findByTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue( + String targetPlatform, + String extension, + String namespace, + Pageable page + ); } 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 5ee335dd0..cbe618d52 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -246,14 +246,44 @@ public int getMaxExtensionDownloadCount() { } public ExtensionVersion findVersion(String version, String targetPlatform, Extension extension) { + return extensionVersionRepo.findByVersionAndTargetPlatformAndExtensionAndStateNot( + version, + targetPlatform, + extension, + ExtensionVersion.State.DELETED + ); + } + + public ExtensionVersion findVersionIncludingDeleted(String version, String targetPlatform, Extension extension) { return extensionVersionRepo.findByVersionAndTargetPlatformAndExtension(version, targetPlatform, extension); } public ExtensionVersion findVersion(String version, String targetPlatform, String extensionName, String namespace) { + return extensionVersionRepo.findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndStateNot( + version, + targetPlatform, + extensionName, + namespace, + ExtensionVersion.State.DELETED + ); + } + + public ExtensionVersion findVersionIncludingDeleted(String version, String targetPlatform, String extensionName, String namespace) { return extensionVersionRepo.findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(version, targetPlatform, extensionName, namespace); } public ExtensionVersion findVersion(UserData user, String version, String targetPlatform, String extensionName, String namespace) { + return extensionVersionRepo.findByPublishedWithUserAndVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndStateNot( + user, + version, + targetPlatform, + extensionName, + namespace, + ExtensionVersion.State.DELETED + ); + } + + public ExtensionVersion findVersionIncludingDeleted(UserData user, String version, String targetPlatform, String extensionName, String namespace) { return extensionVersionRepo.findByPublishedWithUserAndVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(user, version, targetPlatform, extensionName, namespace); } @@ -266,11 +296,20 @@ public Streamable findActiveVersions(Extension extension) { } public Page findActiveVersionsSorted(String namespace, String extension, PageRequest page) { - return extensionVersionRepo.findByExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(extension, namespace, page.withSort(VERSIONS_SORT)); + return extensionVersionRepo.findByExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue( + extension, + namespace, + page.withSort(VERSIONS_SORT) + ); } public Page findActiveVersionsSorted(String namespace, String extension, String targetPlatform, PageRequest page) { - return extensionVersionRepo.findByTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(targetPlatform, extension, namespace, page.withSort(VERSIONS_SORT)); + return extensionVersionRepo.findByTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue( + targetPlatform, + extension, + namespace, + page.withSort(VERSIONS_SORT) + ); } public Page findActiveVersionStringsSorted(String namespace, String extension, String targetPlatform, PageRequest page) { @@ -290,11 +329,17 @@ public List findActiveVersionReferencesSorted(Collection } public Streamable findBundledExtensionsReference(Extension extension) { - return extensionVersionRepo.findByBundledExtensions(NamingUtil.toExtensionId(extension)); + return extensionVersionRepo.findByBundledExtensionsAndStateNot( + NamingUtil.toExtensionId(extension), + ExtensionVersion.State.DELETED + ); } public Streamable findDependenciesReference(Extension extension) { - return extensionVersionRepo.findByDependencies(NamingUtil.toExtensionId(extension)); + return extensionVersionRepo.findByDependenciesAndStateNot( + NamingUtil.toExtensionId(extension), + ExtensionVersion.State.DELETED + ); } public Streamable findExtensions(UserData user) { @@ -550,6 +595,10 @@ public Streamable findTargetPlatformVersions(String version, S } public int countVersions(String namespaceName, String extensionName) { + return extensionVersionJooqRepo.countNonDeleted(namespaceName, extensionName); + } + + public int countAllVersions(String namespaceName, String extensionName) { return extensionVersionJooqRepo.count(namespaceName, extensionName); } diff --git a/server/src/main/java/org/eclipse/openvsx/scanning/ExtensionScanCompletionService.java b/server/src/main/java/org/eclipse/openvsx/scanning/ExtensionScanCompletionService.java index 6099a7ef7..899a5baab 100644 --- a/server/src/main/java/org/eclipse/openvsx/scanning/ExtensionScanCompletionService.java +++ b/server/src/main/java/org/eclipse/openvsx/scanning/ExtensionScanCompletionService.java @@ -686,6 +686,12 @@ public boolean adminAllowScan(ExtensionScan scan) { return false; } + if (extVersion.getState() == ExtensionVersion.State.DELETED) { + logger.warn("Cannot activate deleted extension version for scan #{}: {}", + scan.getId(), NamingUtil.toLogFormat(extVersion)); + return false; + } + // Already active - just mark scan passed if (extVersion.isActive()) { logger.info("Extension already active for scan #{}: {}", diff --git a/server/src/main/java/org/eclipse/openvsx/search/SimilarityCheckService.java b/server/src/main/java/org/eclipse/openvsx/search/SimilarityCheckService.java index c77f2d2c3..08145801b 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/SimilarityCheckService.java +++ b/server/src/main/java/org/eclipse/openvsx/search/SimilarityCheckService.java @@ -82,7 +82,7 @@ public PublishCheck.Result check(PublishCheck.Context context) { var extensionName = scan.getExtensionName(); var displayName = scan.getExtensionDisplayName(); - if (config.isOnlyCheckNewExtensions() && repositories.countVersions(namespaceName, extensionName) > 0) { + if (config.isOnlyCheckNewExtensions() && repositories.countAllVersions(namespaceName, extensionName) > 0) { return PublishCheck.Result.pass(); } 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_70__Extension_Version_State.sql b/server/src/main/resources/db/migration/V1_70__Extension_Version_State.sql new file mode 100644 index 000000000..ca39656a5 --- /dev/null +++ b/server/src/main/resources/db/migration/V1_70__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 b6535b214..1cca6a161 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -1766,6 +1766,17 @@ void testPublishExistingExtension() throws Exception { .andExpect(content().json(errorJson("Extension foo.bar 1.0.0 is already published."))); } + @Test + void testPublishDeletedExtensionVersion() throws Exception { + mockForPublish("existing-inactive"); + var bytes = createExtensionPackage("bar", "1.0.0", null); + mockMvc.perform(post("/api/-/publish?token={token}", "my_token") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .content(bytes)) + .andExpect(status().isBadRequest()) + .andExpect(content().json(errorJson("Extension foo.bar 1.0.0 is already published, but currently isn't active and therefore not visible."))); + } + @Test void testPublishSameVersionDifferentTargetPlatformPreRelease() throws Exception { var extVersion = mockExtension(TargetPlatform.NAME_WIN32_X64); @@ -2452,16 +2463,20 @@ private void mockForPublish(String mode) { namespace.setName("foo"); Mockito.when(repositories.findNamespace("foo")) .thenReturn(namespace); - if (mode.equals("existing")) { + if (mode.equals("existing") || mode.equals("existing-inactive")) { var extension = new Extension(); extension.setName("bar"); var extVersion = new ExtensionVersion(); extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setVersion("1.0.0"); - extVersion.setActive(true); - Mockito.when(repositories.findExtensionForUpdate("bar", "foo")) + if (mode.equals("existing-inactive")) { + extVersion.setState(ExtensionVersion.State.DELETED); + } else { + extVersion.setActive(true); + } + Mockito.when(repositories.findExtension("bar", namespace)) .thenReturn(extension); - Mockito.when(repositories.findVersion("1.0.0", TargetPlatform.NAME_UNIVERSAL, extension)) + Mockito.when(repositories.findVersionIncludingDeleted("1.0.0", TargetPlatform.NAME_UNIVERSAL, extension)) .thenReturn(extVersion); } Mockito.when(repositories.countActiveReviews(any(Extension.class))) @@ -2486,7 +2501,7 @@ private void mockForPublish(String mode) { // Mock findMemberships(user) for similarity check Mockito.when(repositories.findMemberships(token.getUser())) .thenReturn(Streamable.of(ownerMem)); - } else if (mode.equals("contributor") || mode.equals("sole-contributor") || mode.equals("existing")) { + } else if (mode.equals("contributor") || mode.equals("sole-contributor") || mode.equals("existing") || mode.equals("existing-inactive")) { Mockito.when(repositories.canPublishInNamespace(token.getUser(), namespace)) .thenReturn(true); Mockito.when(repositories.isVerified(namespace, token.getUser())) diff --git a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java index 4b9c8796f..b5837ee74 100644 --- a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java @@ -730,6 +730,8 @@ private List mockExtension(UserData user, int numberOfVersions extension.getVersions().addAll(versions); Mockito.when(repositories.findVersions(extension)) .thenReturn(Streamable.of(versions)); + Mockito.when(repositories.countVersions(namespace.getName(), extension.getName())) + .thenReturn(numberOfVersions); Mockito.when(repositories.findLatestVersions(user)).thenReturn(List.of(versions.get(versions.size() - 1))); Mockito.when(repositories.isDeleteAllVersions(eq("foobar"), eq("baz"), any(List.class), eq(user))).then(new Answer() { @Override 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(); + } +} 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 8880209c2..73aa3d4d7 100644 --- a/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java +++ b/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java @@ -215,11 +215,15 @@ void testExecuteQueries() { () -> repositories.searchUsers("search", "role", Pageable.ofSize(25)), () -> repositories.findVersion("version", "targetPlatform", extension), () -> repositories.findVersion("version", "targetPlatform", "extensionName", "namespace"), + () -> repositories.findVersionIncludingDeleted("version", "targetPlatform", extension), () -> repositories.findVersions(extension), + () -> repositories.findVersionIncludingDeleted("version", "targetPlatform", "extensionName", "namespace"), () -> repositories.findVersionsByAccessToken(personalAccessToken, true), () -> repositories.getMaxExtensionDownloadCount(), () -> repositories.getOldestExtensionTimestamp(), + () -> repositories.countAllVersions("namespaceName", "extensionName"), () -> repositories.findExtensions(LONG_LIST), + () -> repositories.findVersionIncludingDeleted(userData, "version", "targetPlatform", "extensionName", "namespace"), () -> repositories.findExtensions(userData), () -> repositories.findFilesByType(List.of(extVersion), STRING_LIST), () -> repositories.countVersions("namespaceName", "extensionName"), diff --git a/server/src/test/java/org/eclipse/openvsx/search/SimilarityCheckServiceTest.java b/server/src/test/java/org/eclipse/openvsx/search/SimilarityCheckServiceTest.java index d352a4124..0aee34719 100644 --- a/server/src/test/java/org/eclipse/openvsx/search/SimilarityCheckServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/search/SimilarityCheckServiceTest.java @@ -148,13 +148,13 @@ void shouldDelegateSimilarExtensionsToService() { void shouldSkipCheckForExistingExtensionWhenConfiguredForNewOnly() { // When configured for new extensions only, skip if extension already has versions (>1 means existing). when(config.isOnlyCheckNewExtensions()).thenReturn(true); - when(repositories.countVersions("ns", "ext")).thenReturn(2); + when(repositories.countAllVersions("ns", "ext")).thenReturn(2); var context = createContext("ns", "ext", "Display"); var result = similarityCheckService.check(context); assertThat(result.passed()).isTrue(); - verify(repositories).countVersions("ns", "ext"); + verify(repositories).countAllVersions("ns", "ext"); verifyNoInteractions(similarityService); } @@ -165,7 +165,7 @@ void shouldCheckNewExtensionEvenWhenConfiguredForNewOnly() { when(config.isAllowSimilarityToOwnNames()).thenReturn(false); when(config.getSimilarityThreshold()).thenReturn(0.15); when(config.isOnlyProtectVerifiedNames()).thenReturn(false); - when(repositories.countVersions("ns", "ext")).thenReturn(0); + when(repositories.countAllVersions("ns", "ext")).thenReturn(0); when(similarityService.findSimilarExtensions("ext", "ns", "Display", List.of(), 0.15, false, 10)) .thenReturn(List.of()); @@ -173,7 +173,7 @@ void shouldCheckNewExtensionEvenWhenConfiguredForNewOnly() { var result = similarityCheckService.check(context); assertThat(result.passed()).isTrue(); - verify(repositories).countVersions("ns", "ext"); + verify(repositories).countAllVersions("ns", "ext"); verify(similarityService).findSimilarExtensions("ext", "ns", "Display", List.of(), 0.15, false, 10); }