From 61c1ad3aa94f22bc9580ca651eee99bc9797dc0e Mon Sep 17 00:00:00 2001 From: amvanbaren Date: Wed, 3 Jul 2024 13:49:59 +0300 Subject: [PATCH] Optimize database queries --- ...tryAPIGetVersionReferencesSimulation.scala | 17 ++ ...onReferencesTargetPlatformSimulation.scala | 17 ++ .../scala/org/eclipse/openvsx/Scenarios.scala | 65 +++-- .../src/gatling/scripts/test-registry-api.sh | 2 + .../org/eclipse/openvsx/ExtensionService.java | 11 +- .../eclipse/openvsx/LocalRegistryService.java | 108 +++----- .../java/org/eclipse/openvsx/UserAPI.java | 35 ++- .../java/org/eclipse/openvsx/UserService.java | 14 +- .../openvsx/adapter/LocalVSCodeService.java | 110 +++----- .../org/eclipse/openvsx/admin/AdminAPI.java | 35 ++- .../eclipse/openvsx/admin/AdminService.java | 47 ++-- .../ChangeNamespaceJobRequestHandler.java | 2 +- .../eclipse/openvsx/cache/CacheService.java | 15 +- .../eclipse/PublisherComplianceChecker.java | 14 +- .../openvsx/mirror/DataMirrorService.java | 23 +- .../PublishExtensionVersionHandler.java | 46 ++-- .../PublishExtensionVersionService.java | 2 +- .../repositories/ExtensionJooqRepository.java | 63 ++++- .../ExtensionReviewJooqRepository.java | 37 +++ .../ExtensionVersionJooqRepository.java | 248 +++++++++++++++--- .../ExtensionVersionRepository.java | 9 +- .../FileResourceJooqRepository.java | 121 ++++++++- .../repositories/FileResourceRepository.java | 10 +- .../repositories/NamespaceJooqRepository.java | 11 + .../NamespaceMembershipJooqRepository.java | 128 +++++++-- .../NamespaceMembershipRepository.java | 8 +- .../PersonalAccessTokenJooqRepository.java | 45 ++++ .../PersonalAccessTokenRepository.java | 5 + .../repositories/RepositoryService.java | 177 +++++++++---- .../SignatureKeyPairJooqRepository.java | 64 +++++ .../repositories/UserDataRepository.java | 11 +- .../storage/AzureBlobStorageService.java | 7 - .../storage/GoogleCloudStorageService.java | 13 - .../openvsx/storage/IStorageService.java | 2 - .../openvsx/storage/LocalStorageService.java | 80 ++++++ .../openvsx/storage/StorageUtilService.java | 60 +---- .../openvsx/util/BuiltInExtensionUtil.java | 2 +- .../org/eclipse/openvsx/util/UrlUtil.java | 5 +- .../org/eclipse/openvsx/IntegrationTest.java | 7 +- .../org/eclipse/openvsx/RegistryAPITest.java | 138 ++++------ .../java/org/eclipse/openvsx/UserAPITest.java | 30 ++- .../openvsx/adapter/VSCodeAPITest.java | 180 ++++++------- .../eclipse/openvsx/admin/AdminAPITest.java | 34 +-- .../openvsx/eclipse/EclipseServiceTest.java | 21 +- .../RepositoryServiceSmokeTest.java | 56 ++-- .../search/DatabaseSearchServiceTest.java | 3 - 46 files changed, 1386 insertions(+), 752 deletions(-) create mode 100644 server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesSimulation.scala create mode 100644 server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesTargetPlatformSimulation.scala create mode 100644 server/src/main/java/org/eclipse/openvsx/repositories/ExtensionReviewJooqRepository.java create mode 100644 server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenJooqRepository.java create mode 100644 server/src/main/java/org/eclipse/openvsx/repositories/SignatureKeyPairJooqRepository.java create mode 100644 server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java diff --git a/server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesSimulation.scala b/server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesSimulation.scala new file mode 100644 index 000000000..4abf71565 --- /dev/null +++ b/server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesSimulation.scala @@ -0,0 +1,17 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU 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 + +import io.gatling.core.Predef._ +import org.eclipse.openvsx.Scenarios._ + +class RegistryAPIGetVersionReferencesSimulation extends Simulation { + setUp(getVersionReferencesScenario().inject(atOnceUsers(users))).protocols(httpProtocol) +} diff --git a/server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesTargetPlatformSimulation.scala b/server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesTargetPlatformSimulation.scala new file mode 100644 index 000000000..f853fa177 --- /dev/null +++ b/server/src/gatling/scala/org/eclipse/openvsx/RegistryAPIGetVersionReferencesTargetPlatformSimulation.scala @@ -0,0 +1,17 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU 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 + +import io.gatling.core.Predef._ +import org.eclipse.openvsx.Scenarios._ + +class RegistryAPIGetVersionReferencesTargetPlatformSimulation extends Simulation { + setUp(getVersionReferencesTargetPlatformScenario().inject(atOnceUsers(users))).protocols(httpProtocol) +} diff --git a/server/src/gatling/scala/org/eclipse/openvsx/Scenarios.scala b/server/src/gatling/scala/org/eclipse/openvsx/Scenarios.scala index 964ff9f1e..9dbf3f4f7 100644 --- a/server/src/gatling/scala/org/eclipse/openvsx/Scenarios.scala +++ b/server/src/gatling/scala/org/eclipse/openvsx/Scenarios.scala @@ -46,8 +46,8 @@ object Scenarios { .exec(http("RegistryAPI.createNamespace") .post(s"/api/-/namespace/create") .headers(headers()) - .queryParam("token", """${access_token}""") - .body(StringBody("""{"name":"${namespace}"}""")).asJson + .queryParam("token", """#{access_token}""") + .body(StringBody("""{"name":"#{namespace}"}""")).asJson .requestTimeout(3.minutes) .check(status.is(201))) // useful for debugging responses @@ -95,7 +95,7 @@ object Scenarios { .exec(http("RegistryAPI.publish") .post("/api/-/publish") .headers(headers()) - .queryParam("token", """${access_token}""") + .queryParam("token", """#{access_token}""") .body(ByteArrayBody(session => { val path = extensionDir + "\\" + session("extension_file").as[String] File(path).toByteArray() @@ -123,10 +123,10 @@ object Scenarios { !File(extensionFile).exists }) { exec(http("getExtensionDownloadLink") - .get("""/api/${namespace}/${name}/${version}""") + .get("""/api/#{namespace}/#{name}/#{version}""") .check(jsonPath("$.files.download").find.saveAs("download"))) .exec(http("downloadExtension") - .get("""${download}""") + .get("""#{download}""") .check(bodyBytes.saveAs("file_bytes"))) .pause(30, 60) .exec {session => @@ -142,7 +142,7 @@ object Scenarios { .repeat(1000) { feed(csv("extensions.csv").circular) .exec(http("RegistryAPI.getExtension") - .get("""/api/${namespace}/${name}""") + .get("""/api/#{namespace}/#{name}""") .headers(headers())) // .check(status.is(200))) } @@ -154,7 +154,7 @@ object Scenarios { .repeat(1000) { feed(csv("extensions.csv").circular) .exec(http("RegistryAPI.getExtension") - .get("""/api/${namespace}/${name}/universal""") + .get("""/api/#{namespace}/#{name}/universal""") .headers(headers())) // .check(status.is(200))) } @@ -165,7 +165,7 @@ object Scenarios { .repeat(1000) { feed(csv("extension-versions.csv").circular) .exec(http("RegistryAPI.getExtension") - .get("""/api/${namespace}/${name}/${version}""") + .get("""/api/#{namespace}/#{name}/#{version}""") .headers(headers())) // .check(status.is(200))) } @@ -177,7 +177,30 @@ object Scenarios { .repeat(1000) { feed(csv("extension-versions.csv").circular) .exec(http("RegistryAPI.getExtension") - .get("""/api/${namespace}/${name}/universal/${version}""") + .get("""/api/#{namespace}/#{name}/universal/#{version}""") + .headers(headers())) + // .check(status.is(200))) + } + } + + def getVersionReferencesScenario(): ScenarioBuilder = { + scenario("RegistryAPI: Get Version References") + .repeat(1000) { + feed(csv("extensions.csv").circular) + .exec(http("RegistryAPI.getVersionReferences") + .get("""/api/#{namespace}/#{name}/version-references""") + .headers(headers())) + // .check(status.is(200))) + } + } + + def getVersionReferencesTargetPlatformScenario(): ScenarioBuilder = { + // TODO add more target platforms besides 'universal' + scenario("RegistryAPI: Get Version References by Target Platform") + .repeat(1000) { + feed(csv("extensions.csv").circular) + .exec(http("RegistryAPI.getVersionReferences") + .get("""/api/#{namespace}/#{name}/universal/version-references""") .headers(headers())) // .check(status.is(200))) } @@ -188,7 +211,7 @@ object Scenarios { .repeat(1000) { feed(csv("namespaces.csv").circular) .exec(http("RegistryAPI.getNamespace") - .get("""/api/${namespace}""") + .get("""/api/#{namespace}""") .headers(headers()) .check(status.is(200))) } @@ -199,7 +222,7 @@ object Scenarios { .repeat(1000) { feed(csv("namespaces.csv").circular) .exec(http("RegistryAPI.getNamespaceDetails") - .get("""/api/${namespace}/details""") + .get("""/api/#{namespace}/details""") .headers(headers()) .check(status.is(200))) } @@ -210,7 +233,7 @@ object Scenarios { .repeat(1000) { feed(csv("query-strings.csv").circular) .exec(http("RegistryAPI.getQuery") - .get("""/api/-/query?${query}""") + .get("""/api/-/query?#{query}""") .headers(headers()) .check(status.is(200))) } @@ -221,7 +244,7 @@ object Scenarios { .repeat(1000) { feed(csv("query-v2-strings.csv").circular) .exec(http("RegistryAPI.getQueryV2") - .get("""/api/v2/-/query?${query}""") + .get("""/api/v2/-/query?#{query}""") .headers(headers()) .check(status.is(200))) } @@ -260,7 +283,7 @@ object Scenarios { .repeat(1000) { feed(csv("extension-versions.csv").circular) .feed(Array( - Map("file" -> """${namespace}.${name}-${version}.vsix"""), + Map("file" -> """#{namespace}.#{name}-#{version}.vsix"""), Map("file" -> "package.json"), Map("file" -> "extension.vsixmanifest"), Map("file" -> "CHANGELOG.md"), @@ -347,7 +370,7 @@ object Scenarios { .repeat(1000) { feed(this.searchQueryFeeder().circular) .exec(http("RegistryAPI.search") - .get("""/api/-/search?${query}""") + .get("""/api/-/search?#{query}""") .headers(headers())) // .check(status.is(200))) } @@ -359,7 +382,7 @@ object Scenarios { feed(csv("namespaces.csv").circular) .feed(csv("access-tokens.csv").circular) .exec(http("RegistryAPI.verifyToken") - .get("""/api/${namespace}/verify-pat?token=${access_token}""") + .get("""/api/#{namespace}/verify-pat?token=#{access_token}""") .headers(headers()) .requestTimeout(3.minutes)) } @@ -375,7 +398,7 @@ object Scenarios { | { | "criteria":[ | {"filterType":8,"value":"Microsoft.VisualStudio.Code"}, - | {"filterType":10,"value":"${query}"}, + | {"filterType":10,"value":"#{query}"}, | {"filterType":12,"value":"4096"} | ], | "pageNumber":1, @@ -409,7 +432,7 @@ object Scenarios { .repeat(1000) { feed(csv("extension-versions.csv").circular) .exec(http("VSCodeAdapter.download") - .get("""/vscode/gallery/publishers/${namespace}/vsextensions/${name}/${version}/vspackage""") + .get("""/vscode/gallery/publishers/#{namespace}/vsextensions/#{name}/#{version}/vspackage""") .headers(headers()) .requestTimeout(3.minutes) .check(status.is(200))) @@ -421,7 +444,7 @@ object Scenarios { .repeat(1000) { feed(csv("extensions.csv").circular) .exec(http("VSCodeAdapter.getItemUrl") - .get("""/vscode/item?itemName=${namespace}.${name}""") + .get("""/vscode/item?itemName=#{namespace}.#{name}""") .headers(headers()) .requestTimeout(3.minutes) .check(status.is(200))) @@ -434,7 +457,7 @@ object Scenarios { feed(csv("extension-versions.csv").circular) .feed(Array(Map("file" -> "extension.vsixmanifest"), Map("file" -> "extension")).circular) .exec(http("VSCodeAdapter.browse") - .get("""/vscode/unpkg/${namespace}/${name}/${version}/${file}""") + .get("""/vscode/unpkg/#{namespace}/#{name}/#{version}/#{file}""") .headers(headers()) .requestTimeout(3.minutes) .check(status.is(200))) @@ -455,7 +478,7 @@ object Scenarios { Map("asset" -> "Microsoft.VisualStudio.Code.WebResources/extension/package.json") ).circular) .exec(http("VSCodeAdapter.getAsset") - .get("""/vscode/asset/${namespace}/${name}/${version}/${asset}""") + .get("""/vscode/asset/#{namespace}/#{name}/#{version}/#{asset}""") .headers(headers()) .requestTimeout(3.minutes) .check(status.is(200))) diff --git a/server/src/gatling/scripts/test-registry-api.sh b/server/src/gatling/scripts/test-registry-api.sh index 35eae7e3e..8158c4b26 100644 --- a/server/src/gatling/scripts/test-registry-api.sh +++ b/server/src/gatling/scripts/test-registry-api.sh @@ -7,6 +7,8 @@ cd ../../.. ./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetExtensionTargetPlatformSimulation ./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetExtensionVersionSimulation ./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetExtensionVersionTargetPlatformSimulation +./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetVersionReferencesSimulation +./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetVersionReferencesTargetPlatformSimulation ./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetFileSimulation ./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetFileTargetPlatformSimulation ./gradlew --rerun-tasks gatlingRun-org.eclipse.openvsx.RegistryAPIGetQuerySimulation diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java index e2d45014b..fb05fb473 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java @@ -147,14 +147,11 @@ public void updateExtension(Extension extension) { */ @Transactional public void reactivateExtensions(UserData user) { - var accessTokens = repositories.findAccessTokens(user); var affectedExtensions = new LinkedHashSet(); - for (var accessToken : accessTokens) { - var versions = repositories.findVersionsByAccessToken(accessToken, false); - for (var version : versions) { - version.setActive(true); - affectedExtensions.add(version.getExtension()); - } + var versions = repositories.findVersionsByUser(user, false); + for (var version : versions) { + version.setActive(true); + affectedExtensions.add(version.getExtension()); } for (var extension : affectedExtensions) { updateExtension(extension); diff --git a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java index a08e8a0b3..99c55138f 100644 --- a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java @@ -40,7 +40,6 @@ import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.eclipse.openvsx.cache.CacheService.*; import static org.eclipse.openvsx.entities.FileResource.*; @@ -105,11 +104,11 @@ public NamespaceJson getNamespace(String namespaceName) { json.name = namespace.getName(); json.extensions = new LinkedHashMap<>(); var serverUrl = UrlUtil.getBaseUrl(); - for (var ext : repositories.findActiveExtensions(namespace)) { - String url = createApiUrl(serverUrl, "api", namespace.getName(), ext.getName()); - json.extensions.put(ext.getName(), url); + for (var name : repositories.findActiveExtensionNames(namespace)) { + String url = createApiUrl(serverUrl, "api", namespace.getName(), name); + json.extensions.put(name, url); } - json.verified = repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER) > 0; + json.verified = repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER); json.access = "restricted"; return json; } @@ -210,8 +209,10 @@ private ExtensionVersion findExtensionVersion(String namespace, String extension @Override public ResponseEntity getFile(String namespace, String extensionName, String targetPlatform, String version, String fileName) { - var extVersion = findExtensionVersion(namespace, extensionName, targetPlatform, version); - var resource = isType(fileName) ? repositories.findFileByType(extVersion, fileName.toLowerCase()) : repositories.findFileByName(extVersion, fileName); + var resource = isType(fileName) + ? repositories.findFileByType(namespace, extensionName, targetPlatform, version, fileName.toLowerCase()) + : repositories.findFileByName(namespace, extensionName, targetPlatform, version, fileName); + if (resource == null) throw new NotFoundException(); if (resource.getType().equals(DOWNLOAD)) @@ -230,8 +231,8 @@ public boolean isType (String fileName){ } @Override - public ReviewListJson getReviews(String namespace, String extensionName) { - var extension = repositories.findExtension(extensionName, namespace); + public ReviewListJson getReviews(String namespaceName, String extensionName) { + var extension = repositories.findExtension(extensionName, namespaceName); if (extension == null || !extension.isActive()) throw new NotFoundException(); var list = new ReviewListJson(); @@ -399,7 +400,7 @@ public NamespaceDetailsJson getNamespaceDetails(String namespaceName) { } var json = namespace.toNamespaceDetailsJson(); - json.verified = repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER) > 0; + json.verified = repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER); json.logo = namespace.getLogoStorageType() != null ? storageUtil.getNamespaceLogoLocation(namespace).toString() : null; @@ -501,12 +502,7 @@ private String getLatestVersionKey(ExtensionVersion extVersion) { } private Map getPreviews(Set extensionIds) { - return repositories.findActiveExtensionVersions(extensionIds, null).stream() - .collect(Collectors.groupingBy(ev -> ev.getExtension().getId())) - .values() - .stream() - .map(list -> versions.getLatest(list, false)) - .collect(Collectors.toMap(ev -> ev.getExtension().getId(), ExtensionVersion::isPreview)); + return repositories.findLatestVersionsIsPreview(extensionIds); } private Map> getFileResources(List extensionVersions) { @@ -556,13 +552,13 @@ public ResultJson createNamespace(NamespaceJson json, UserData user) { } eclipse.checkPublisherAgreement(user); - var namespace = repositories.findNamespace(json.name); - if (namespace != null) { - throw new ErrorResultException("Namespace already exists: " + namespace.getName()); + var namespaceName = repositories.findNamespaceName(json.name); + if (namespaceName != null) { + throw new ErrorResultException("Namespace already exists: " + namespaceName); } // Create the requested namespace - namespace = new Namespace(); + var namespace = new Namespace(); namespace.setName(json.name); entityManager.persist(namespace); @@ -589,7 +585,7 @@ public ResultJson verifyToken(String namespaceName, String tokenValue) { var user = token.getUser(); if (!users.hasPublishPermission(user, namespace)) { - throw new ErrorResultException("Insufficient access rights for namespace: " + namespaceName); + throw new ErrorResultException("Insufficient access rights for namespace: " + namespace.getName()); } return ResultJson.success("Valid token"); @@ -618,8 +614,7 @@ public ExtensionJson publish(InputStream content, String tokenValue) throws Erro var json = toExtensionVersionJson(extVersion, null, true); json.success = "It can take a couple minutes before the extension version is available"; - var sameVersions = repositories.findVersions(extVersion.getVersion(), extVersion.getExtension()); - if (sameVersions.stream().anyMatch(ev -> ev.isPreRelease() != extVersion.isPreRelease())) { + if(repositories.hasSameVersion(extVersion)) { var existingRelease = extVersion.isPreRelease() ? "stable release" : "pre-release"; var thisRelease = extVersion.isPreRelease() ? "pre-release" : "stable release"; var extension = extVersion.getExtension(); @@ -645,8 +640,7 @@ public ResultJson postReview(ReviewJson review, String namespace, String extensi var extensionId = NamingUtil.toExtensionId(namespace, extensionName); return ResultJson.error("Extension not found: " + extensionId); } - var activeReviews = repositories.findActiveReviews(extension, user); - if (!activeReviews.isEmpty()) { + if (repositories.hasActiveReview(extension, user)) { return ResultJson.error("You must not submit more than one review for an extension."); } @@ -694,58 +688,41 @@ public ResultJson deleteReview(String namespace, String extensionName) { return ResultJson.success("Deleted review for " + NamingUtil.toExtensionId(extension)); } - private List getExtensions(SearchHits searchHits) { + private LinkedHashMap getLatestVersions(SearchHits searchHits) { var ids = searchHits.stream() .map(searchHit -> searchHit.getContent().id) .distinct() .collect(Collectors.toList()); - var extensions = findExtensions(ids).collect(Collectors.toList()); - var extensionIds = extensions.stream() - .map(Extension::getId) - .collect(Collectors.toSet()); - - ids.removeAll(extensionIds); + var versions = findLatestVersions(ids); + ids.removeAll(versions.keySet()); if(!ids.isEmpty()) { search.removeSearchEntries(ids); } - var inactiveExtensions = extensions.stream() - .filter(extension -> !extension.isActive()) - .collect(Collectors.toList()); - - if(!inactiveExtensions.isEmpty()) { - var inactiveIds = inactiveExtensions.stream() - .map(Extension::getId) - .collect(Collectors.toList()); + return versions; + } - search.removeSearchEntries(inactiveIds); - extensions.removeAll(inactiveExtensions); - } + private LinkedHashMap findLatestVersions(Collection ids) { + var extById = repositories.findLatestVersions(ids).stream() + .collect(Collectors.toMap(ev -> ev.getExtension().getId(), ev -> ev)); - return extensions; - } + // use LinkedHashMap to order latest versions by extension id + var map = new LinkedHashMap(); + ids.forEach(id -> { + var latest = extById.get(id); + if(latest != null) { + map.put(id, latest); + } + }); - private Stream findExtensions(Collection ids) { - var extById = new HashMap(); - repositories.findExtensions(ids) - .forEach(ext -> extById.put(ext.getId(), ext)); - return ids.stream() - .map(extById::get) - .filter(Objects::nonNull); + return map; } private List toSearchEntries(SearchHits searchHits, ISearchService.Options options) { var serverUrl = UrlUtil.getBaseUrl(); - var extensions = getExtensions(searchHits); - var latestVersions = extensions.stream() - .map(e -> { - var latest = repositories.findLatestVersion(e, null, false, true); - return new AbstractMap.SimpleEntry<>(e.getId(), latest); - }) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - + var latestVersions = getLatestVersions(searchHits); var membershipsByNamespaceId = getMemberships(latestVersions.values()); var searchEntries = latestVersions.entrySet().stream() .map(e -> { @@ -765,19 +742,18 @@ private List toSearchEntries(SearchHits search } }); if (options.includeAllVersions) { - var activeVersions = repositories.findActiveVersionReferencesSorted(extensions); + var activeVersions = repositories.findActiveVersionReferencesSorted(latestVersions.keySet()); var activeVersionsByExtensionId = activeVersions.stream().collect(Collectors.groupingBy(ev -> ev.getExtension().getId())); var versionFileUrls = storageUtil.getFileUrls(activeVersions, serverUrl, withFileTypes(DOWNLOAD)); - for(var extension : extensions) { - var extVersions = activeVersionsByExtensionId.get(extension.getId()); - var searchEntry = searchEntries.get(extension.getId()); + for(var extensionId : latestVersions.keySet()) { + var extVersions = activeVersionsByExtensionId.get(extensionId); + var searchEntry = searchEntries.get(extensionId); searchEntry.allVersions = getAllVersionReferences(extVersions, versionFileUrls, serverUrl); searchEntry.allVersionsUrl = UrlUtil.createAllVersionsUrl(searchEntry.namespace, searchEntry.name, options.targetPlatform, "version-references"); } } - return extensions.stream() - .map(Extension::getId) + return latestVersions.keySet().stream() .map(searchEntries::get) .collect(Collectors.toList()); } diff --git a/server/src/main/java/org/eclipse/openvsx/UserAPI.java b/server/src/main/java/org/eclipse/openvsx/UserAPI.java index 78ff380c8..f5fc5d1db 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/UserAPI.java @@ -11,12 +11,13 @@ import jakarta.servlet.http.HttpServletRequest; import org.eclipse.openvsx.eclipse.EclipseService; +import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.NamespaceMembership; +import org.eclipse.openvsx.entities.UserData; import org.eclipse.openvsx.json.*; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.security.CodedAuthException; import org.eclipse.openvsx.storage.StorageUtilService; -import org.eclipse.openvsx.util.CollectionUtil; import org.eclipse.openvsx.util.ErrorResultException; import org.eclipse.openvsx.util.NotFoundException; import org.eclipse.openvsx.util.UrlUtil; @@ -141,8 +142,7 @@ public List getAccessTokens() { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } var serverUrl = UrlUtil.getBaseUrl(); - return repositories.findAccessTokens(user) - .filter(token -> token.isActive()) + return repositories.findActiveAccessTokens(user) .map(token -> { var json = token.toAccessTokenJson(); json.deleteTokenUrl = createApiUrl(serverUrl, "user", "token", "delete", Long.toString(token.getId())); @@ -219,22 +219,19 @@ public List getOwnNamespaces() { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } - var memberships = repositories.findMemberships(user, NamespaceMembership.ROLE_OWNER) - .and(repositories.findMemberships(user, NamespaceMembership.ROLE_CONTRIBUTOR)); - - return memberships.map(membership -> { + return repositories.findMemberships(user).map(membership -> { var namespace = membership.getNamespace(); var json = new NamespaceJson(); json.name = namespace.getName(); json.extensions = new LinkedHashMap<>(); var serverUrl = UrlUtil.getBaseUrl(); - for (var ext : repositories.findActiveExtensions(namespace)) { - String url = createApiUrl(serverUrl, "api", namespace.getName(), ext.getName()); - json.extensions.put(ext.getName(), url); - } + repositories.findActiveExtensionsForUrls(namespace).forEach(extension -> { + String url = createApiUrl(serverUrl, "api", namespace.getName(), extension.getName()); + json.extensions.put(extension.getName(), url); + }); var isOwner = membership.getRole().equals(NamespaceMembership.ROLE_OWNER); - json.verified = isOwner || repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER) > 0; + json.verified = isOwner || repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER); if(isOwner) { json.membersUrl = createApiUrl(serverUrl, "user", "namespace", namespace.getName(), "members"); json.roleUrl = createApiUrl(serverUrl, "user", "namespace", namespace.getName(), "role"); @@ -271,12 +268,10 @@ public ResponseEntity getNamespaceMembers(@PathVari return new ResponseEntity<>(HttpStatus.FORBIDDEN); } - var namespace = repositories.findNamespace(name); - var userMembership = repositories.findMembership(user, namespace); - if (userMembership != null && userMembership.getRole().equals(NamespaceMembership.ROLE_OWNER)) { - var memberships = repositories.findMemberships(namespace); + var memberships = repositories.findMembershipsForOwner(user, name); + if (!memberships.isEmpty()) { var membershipList = new NamespaceMembershipListJson(); - membershipList.namespaceMemberships = memberships.map(membership -> membership.toJson()).toList(); + membershipList.namespaceMemberships = memberships.stream().map(NamespaceMembership::toJson).toList(); return new ResponseEntity<>(membershipList, HttpStatus.OK); } else { return new ResponseEntity<>(NamespaceMembershipListJson.error("You don't have the permission to see this."), HttpStatus.FORBIDDEN); @@ -310,9 +305,9 @@ public List getUsersStartWith(@PathVariable String name) { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } - var users = repositories.findUsersByLoginNameStartingWith(name) - .map(user -> user.toUserJson()); - return CollectionUtil.limit(users, 5); + return repositories.findUsersByLoginNameStartingWith(name, 5).stream() + .map(UserData::toUserJson) + .toList(); } @PostMapping( diff --git a/server/src/main/java/org/eclipse/openvsx/UserService.java b/server/src/main/java/org/eclipse/openvsx/UserService.java index d809441f6..5117762ba 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserService.java +++ b/server/src/main/java/org/eclipse/openvsx/UserService.java @@ -142,7 +142,7 @@ public String generateTokenValue() { String value; do { value = UUID.randomUUID().toString(); - } while (repositories.findAccessToken(value) != null); + } while (repositories.hasAccessToken(value)); return value; } @@ -154,22 +154,14 @@ public boolean hasPublishPermission(UserData user, Namespace namespace) { return true; } - var membership = repositories.findMembership(user, namespace); - if (membership == null) { - // The requesting user is not a member of the namespace. - return false; - } - var role = membership.getRole(); - return NamespaceMembership.ROLE_CONTRIBUTOR.equalsIgnoreCase(role) - || NamespaceMembership.ROLE_OWNER.equalsIgnoreCase(role); + return repositories.canPublishInNamespace(user, namespace); }); } @Transactional(rollbackOn = ErrorResultException.class) public ResultJson setNamespaceMember(UserData requestingUser, String namespaceName, String provider, String userLogin, String role) { var namespace = repositories.findNamespace(namespaceName); - var userMembership = repositories.findMembership(requestingUser, namespace); - if (userMembership == null || !userMembership.getRole().equals(NamespaceMembership.ROLE_OWNER)) { + if (!repositories.isNamespaceOwner(requestingUser, namespace)) { throw new ErrorResultException("You must be an owner of this namespace."); } var targetUser = repositories.findUserByLoginName(provider, userLogin); diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java b/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java index 632c09e56..5433a160b 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java @@ -289,39 +289,23 @@ public ResponseEntity getAsset( return new ResponseEntity<>(("Built-in extension namespace '" + namespace + "' not allowed").getBytes(StandardCharsets.UTF_8), null, HttpStatus.BAD_REQUEST); } - var extVersion = repositories.findVersion(version, targetPlatform, extensionName, namespace); - if (extVersion == null || !extVersion.isActive()) { - throw new NotFoundException(); - } - - var asset = (restOfTheUrl != null && restOfTheUrl.length() > 0) ? (assetType + "/" + restOfTheUrl) : assetType; + var asset = (restOfTheUrl != null && !restOfTheUrl.isEmpty()) ? (assetType + "/" + restOfTheUrl) : assetType; if((asset.equals(FILE_PUBLIC_KEY) || asset.equals(FILE_SIGNATURE)) && !integrityService.isEnabled()) { throw new NotFoundException(); } if(asset.equals(FILE_PUBLIC_KEY)) { - if(extVersion.getSignatureKeyPair() == null) { + var publicId = repositories.findSignatureKeyPairPublicId(namespace, extensionName, targetPlatform, version); + if(publicId == null) { throw new NotFoundException(); } else { return ResponseEntity .status(HttpStatus.FOUND) - .location(URI.create(UrlUtil.getPublicKeyUrl(extVersion))) + .location(URI.create(UrlUtil.getPublicKeyUrl(publicId))) .build(); } } - var resource = getFileFromDB(extVersion, asset); - if (resource == null) { - throw new NotFoundException(); - } - if (resource.getType().equals(FileResource.DOWNLOAD)) { - storageUtil.increaseDownloadCount(resource); - } - - return storageUtil.getFileResponse(resource); - } - - private FileResource getFileFromDB(ExtensionVersion extVersion, String assetType) { var assets = Map.of( FILE_VSIX, DOWNLOAD, FILE_MANIFEST, MANIFEST, @@ -333,18 +317,27 @@ private FileResource getFileFromDB(ExtensionVersion extVersion, String assetType FILE_SIGNATURE, DOWNLOAD_SIG ); + FileResource resource; var type = assets.get(assetType); if(type != null) { - return repositories.findFileByType(extVersion, type); + resource = repositories.findFileByType(namespace, extensionName, targetPlatform, version, type); } else { - var name = assetType.startsWith(FILE_WEB_RESOURCES) - ? assetType.substring((FILE_WEB_RESOURCES.length())) + var name = asset.startsWith(FILE_WEB_RESOURCES) + ? asset.substring((FILE_WEB_RESOURCES.length())) : null; - return name != null && name.startsWith("extension/") // is web resource - ? repositories.findFileByTypeAndName(extVersion, FileResource.RESOURCE, name) + resource = name != null && name.startsWith("extension/") // is web resource + ? repositories.findFileByTypeAndName(namespace, extensionName, targetPlatform, version, FileResource.RESOURCE, name) : null; } + if (resource == null) { + throw new NotFoundException(); + } + if (resource.getType().equals(FileResource.DOWNLOAD)) { + storageUtil.increaseDownloadCount(resource); + } + + return storageUtil.getFileResponse(resource); } @Override @@ -353,34 +346,32 @@ public String getItemUrl(String namespaceName, String extensionName) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Built-in extension namespace '" + namespaceName + "' not allowed"); } - var extension = repositories.findExtension(extensionName, namespaceName); - if (extension == null || !extension.isActive()) { + var extension = repositories.findActiveExtension(extensionName, namespaceName); + if (extension == null) { throw new NotFoundException(); } - return UrlUtil.createApiUrl(webuiUrl, "extension", namespaceName, extensionName); + return UrlUtil.createApiUrl(webuiUrl, "extension", extension.getNamespace().getName(), extension.getName()); } @Override - public String download(String namespace, String extension, String version, String targetPlatform) { - if(BuiltInExtensionUtil.isBuiltIn(namespace)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Built-in extension namespace '" + namespace + "' not allowed"); - } - - var extVersion = repositories.findVersion(version, targetPlatform, extension, namespace); - if (extVersion == null || !extVersion.isActive()) { - throw new NotFoundException(); + public String download(String namespaceName, String extensionName, String version, String targetPlatform) { + if(BuiltInExtensionUtil.isBuiltIn(namespaceName)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Built-in extension namespace '" + BuiltInExtensionUtil.getBuiltInNamespace() + "' not allowed"); } - var resource = repositories.findFileByType(extVersion, FileResource.DOWNLOAD); + var resource = repositories.findFileByType(namespaceName, extensionName, targetPlatform, version, FileResource.DOWNLOAD); if (resource == null) { throw new NotFoundException(); } if(resource.getStorageType().equals(STORAGE_DB)) { - var apiUrl = UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "vscode", "asset", namespace, extension, version, FILE_VSIX); - if(!TargetPlatform.isUniversal(targetPlatform)) { - apiUrl = UrlUtil.addQuery(apiUrl, "targetPlatform", targetPlatform); + var extVersion = resource.getExtension(); + var extension = extVersion.getExtension(); + var namespace = extension.getNamespace(); + var apiUrl = UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "vscode", "asset", namespace.getName(), extension.getName(), extVersion.getVersion(), FILE_VSIX); + if(!TargetPlatform.isUniversal(extVersion.getTargetPlatform())) { + apiUrl = UrlUtil.addQuery(apiUrl, "targetPlatform", extVersion.getTargetPlatform()); } return apiUrl; @@ -393,14 +384,10 @@ public String download(String namespace, String extension, String version, Strin @Override public ResponseEntity browse(String namespaceName, String extensionName, String version, String path) { if(BuiltInExtensionUtil.isBuiltIn(namespaceName)) { - return new ResponseEntity<>(("Built-in extension namespace '" + namespaceName + "' not allowed").getBytes(StandardCharsets.UTF_8), null, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(("Built-in extension namespace '" + BuiltInExtensionUtil.getBuiltInNamespace() + "' not allowed").getBytes(StandardCharsets.UTF_8), null, HttpStatus.BAD_REQUEST); } - var extVersions = repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName); - var extVersion = extVersions.stream().max(Comparator.comparing(ExtensionVersion::isUniversalTargetPlatform) - .thenComparing(ExtensionVersion::getTargetPlatform, Comparator.reverseOrder())) - .orElse(null); - + var extVersion = repositories.findActiveExtensionVersion(version, extensionName, namespaceName); if (extVersion == null) { throw new NotFoundException(); } @@ -415,41 +402,24 @@ public ResponseEntity browse(String namespaceName, String extensionName, .findFirst() .orElse(null); + var extension = extVersion.getExtension(); + var namespace = extension.getNamespace(); return exactMatch != null - ? browseFile(exactMatch, namespaceName, extensionName, extVersion.getTargetPlatform(), version) - : browseDirectory(resources, namespaceName, extensionName, version, path); + ? browseFile(exactMatch, extVersion) + : browseDirectory(resources, namespace.getName(), extension.getName(), extVersion.getVersion(), path); } private ResponseEntity browseFile( FileResource resource, - String namespaceName, - String extensionName, - String targetPlatform, - String version + ExtensionVersion extVersion ) { if (resource.getStorageType().equals(FileResource.STORAGE_DB)) { var headers = storageUtil.getFileResponseHeaders(resource.getName()); return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK); } else { - var namespace = new Namespace(); - namespace.setName(namespaceName); - - var extension = new Extension(); - extension.setName(extensionName); - extension.setNamespace(namespace); - - var extVersion = new ExtensionVersion(); - extVersion.setVersion(version); - extVersion.setTargetPlatform(targetPlatform); - extVersion.setExtension(extension); - - var fileResource = new FileResource(); - fileResource.setName(resource.getName()); - fileResource.setExtension(extVersion); - fileResource.setStorageType(resource.getStorageType()); - + resource.setExtension(extVersion); return ResponseEntity.status(HttpStatus.FOUND) - .location(storageUtil.getLocation(fileResource)) + .location(storageUtil.getLocation(resource)) .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePublic()) .build(); } diff --git a/server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java b/server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java index 0116e7f95..685372e29 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java @@ -12,6 +12,7 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.LocalRegistryService; import org.eclipse.openvsx.entities.AdminStatistics; +import org.eclipse.openvsx.entities.NamespaceMembership; import org.eclipse.openvsx.entities.PersistedLog; import org.eclipse.openvsx.json.*; import org.eclipse.openvsx.repositories.RepositoryService; @@ -90,8 +91,7 @@ public ResponseEntity getReportCsv( } private void validateToken(String tokenValue) { - var accessToken = repositories.findAccessToken(tokenValue); - if(accessToken == null || !accessToken.isActive() || accessToken.getUser() == null || !ROLE_ADMIN.equals(accessToken.getUser().getRole())) { + if(!repositories.isAdminToken(tokenValue)) { throw new ErrorResultException("Invalid access token", HttpStatus.FORBIDDEN); } } @@ -179,26 +179,26 @@ public ResponseEntity getExtension(@PathVariable String namespace @PathVariable String extensionName) { try { admins.checkAdminUser(); - - var extension = repositories.findExtension(extensionName, namespaceName); - if (extension == null) { - var json = ExtensionJson.error("Extension not found: " + NamingUtil.toExtensionId(namespaceName, extensionName)); - return new ResponseEntity<>(json, HttpStatus.NOT_FOUND); - } - ExtensionJson json; - var latest = repositories.findLatestVersion(extension, null, false, false); - if (latest == null) { + var latest = repositories.findLatestVersion(namespaceName, extensionName, null, false, false); + if (latest != null) { + json = local.toExtensionVersionJson(latest, null, false); + json.allTargetPlatformVersions = repositories.findTargetPlatformsGroupedByVersion(latest.getExtension()); + json.active = latest.getExtension().isActive(); + } else { + var extension = repositories.findExtension(extensionName, namespaceName); + if (extension == null) { + var error = "Extension not found: " + NamingUtil.toExtensionId(namespaceName, extensionName); + throw new ErrorResultException(error, HttpStatus.NOT_FOUND); + } + json = new ExtensionJson(); json.namespace = extension.getNamespace().getName(); json.name = extension.getName(); json.allVersions = Collections.emptyMap(); json.allTargetPlatformVersions = Collections.emptyList(); - } else { - json = local.toExtensionVersionJson(latest, null, false); - json.allTargetPlatformVersions = repositories.findTargetPlatformsGroupedByVersion(extension); + json.active = extension.isActive(); } - json.active = extension.isActive(); return ResponseEntity.ok(json); } catch (ErrorResultException exc) { return exc.toResponseEntity(ExtensionJson.class); @@ -296,10 +296,9 @@ public ResponseEntity changeNamespace(@RequestBody ChangeNamespaceJs public ResponseEntity getNamespaceMembers(@PathVariable String namespaceName) { try{ admins.checkAdminUser(); - var namespace = repositories.findNamespace(namespaceName); - var memberships = repositories.findMemberships(namespace); + var memberships = repositories.findMemberships(namespaceName); var membershipList = new NamespaceMembershipListJson(); - membershipList.namespaceMemberships = memberships.map(membership -> membership.toJson()).toList(); + membershipList.namespaceMemberships = memberships.stream().map(NamespaceMembership::toJson).toList(); return ResponseEntity.ok(membershipList); } catch (ErrorResultException exc) { return exc.toResponseEntity(NamespaceMembershipListJson.class); 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 5628a5ef7..dd39f7dd3 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java +++ b/server/src/main/java/org/eclipse/openvsx/admin/AdminService.java @@ -107,7 +107,6 @@ public ResultJson deleteExtension(String namespaceName, String extensionName, St } protected ResultJson deleteExtension(Extension extension, UserData admin) throws ErrorResultException { - var namespace = extension.getNamespace(); var bundledRefs = repositories.findBundledExtensionsReference(extension); if (!bundledRefs.isEmpty()) { throw new ErrorResultException("Extension " + NamingUtil.toExtensionId(extension) @@ -156,19 +155,11 @@ protected ResultJson deleteExtension(ExtensionVersion extVersion, UserData admin } private void removeExtensionVersion(ExtensionVersion extVersion) { - repositories.findFiles(extVersion).forEach(file -> { - enqueueRemoveFileJob(file); - entityManager.remove(file); - }); + repositories.findFiles(extVersion).map(RemoveFileJobRequest::new).forEach(scheduler::enqueue); + repositories.deleteFiles(extVersion); entityManager.remove(extVersion); } - private void enqueueRemoveFileJob(FileResource resource) { - if(!resource.getStorageType().equals(STORAGE_DB)) { - scheduler.enqueue(new RemoveFileJobRequest(resource)); - } - } - @Transactional(rollbackOn = ErrorResultException.class) public ResultJson editNamespaceMember(String namespaceName, String userName, String provider, String role, UserData admin) throws ErrorResultException { @@ -181,15 +172,11 @@ public ResultJson editNamespaceMember(String namespaceName, String userName, Str throw new ErrorResultException("User not found: " + provider + "/" + userName); } - ResultJson result; - if (role.equals("remove")) { - result = users.removeNamespaceMember(namespace, user); - } else { - result = users.addNamespaceMember(namespace, user, role); - } - for (var extension : repositories.findActiveExtensions(namespace)) { - search.updateSearchEntry(extension); - } + var result = role.equals("remove") + ? users.removeNamespaceMember(namespace, user) + : users.addNamespaceMember(namespace, user, role); + + search.updateSearchEntries(repositories.findActiveExtensions(namespace).toList()); logAdminAction(admin, result); return result; } @@ -201,11 +188,11 @@ public ResultJson createNamespace(NamespaceJson json) { throw new ErrorResultException(namespaceIssue.get().toString()); } - var namespace = repositories.findNamespace(json.name); - if (namespace != null) { - throw new ErrorResultException("Namespace already exists: " + namespace.getName()); + var namespaceName = repositories.findNamespaceName(json.name); + if (namespaceName != null) { + throw new ErrorResultException("Namespace already exists: " + namespaceName); } - namespace = new Namespace(); + var namespace = new Namespace(); namespace.setName(json.name); entityManager.persist(namespace); return ResultJson.success("Created namespace " + namespace.getName()); @@ -310,14 +297,14 @@ public ResultJson revokePublisherContributions(String provider, String loginName accessToken.setActive(false); deactivatedTokenCount++; } + } + var versions = repositories.findVersionsByUser(user, true); + for (var version : versions) { // Deactivate all published extension versions - var versions = repositories.findVersionsByAccessToken(accessToken, true); - for (var version : versions) { - version.setActive(false); - affectedExtensions.add(version.getExtension()); - deactivatedExtensionCount++; - } + version.setActive(false); + affectedExtensions.add(version.getExtension()); + deactivatedExtensionCount++; } // Update affected extensions diff --git a/server/src/main/java/org/eclipse/openvsx/admin/ChangeNamespaceJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/admin/ChangeNamespaceJobRequestHandler.java index 2c1b30356..5dbb8af52 100644 --- a/server/src/main/java/org/eclipse/openvsx/admin/ChangeNamespaceJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/admin/ChangeNamespaceJobRequestHandler.java @@ -80,7 +80,6 @@ private void execute(ChangeNamespaceJobRequest jobRequest) { return; } - var oldResources = repositories.findFileResources(oldNamespace); var newNamespaceOptional = Optional.ofNullable(repositories.findNamespace(json.newNamespace)); var createNewNamespace = newNamespaceOptional.isEmpty(); var newNamespace = newNamespaceOptional.orElseGet(() -> { @@ -90,6 +89,7 @@ private void execute(ChangeNamespaceJobRequest jobRequest) { return namespace; }); + var oldResources = repositories.findFileResources(oldNamespace); var copyResources = oldResources.stream() .findFirst() .map(storageUtil::shouldStoreExternally) diff --git a/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java b/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java index e102904f3..c22b540b1 100644 --- a/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java +++ b/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java @@ -37,20 +37,20 @@ public class CacheService { public static final String GENERATOR_LATEST_EXTENSION_VERSION = "latestExtensionVersionCacheKeyGenerator"; private final CacheManager cacheManager; - private final RepositoryService repositoryService; + private final RepositoryService repositories; private final ExtensionJsonCacheKeyGenerator extensionJsonCacheKey; private final LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKey; private final ObservationRegistry observations; public CacheService( CacheManager cacheManager, - RepositoryService repositoryService, + RepositoryService repositories, ExtensionJsonCacheKeyGenerator extensionJsonCacheKey, LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKey, ObservationRegistry observations ) { this.cacheManager = cacheManager; - this.repositoryService = repositoryService; + this.repositories = repositories; this.extensionJsonCacheKey = extensionJsonCacheKey; this.latestExtensionVersionCacheKey = latestExtensionVersionCacheKey; this.observations = observations; @@ -74,15 +74,8 @@ public void evictExtensionJsons() { invalidateCache(CACHE_EXTENSION_JSON); } - public void evictExtensionJsons(String namespaceName, String extensionName) { - evictExtensionJsons(repositoryService.findExtension(extensionName, namespaceName)); - } - public void evictExtensionJsons(UserData user) { - repositoryService.findVersions(user) - .map(ExtensionVersion::getExtension) - .toSet() - .forEach(this::evictExtensionJsons); + repositories.findExtensions(user).forEach(this::evictExtensionJsons); } public void evictExtensionJsons(Extension extension) { diff --git a/server/src/main/java/org/eclipse/openvsx/eclipse/PublisherComplianceChecker.java b/server/src/main/java/org/eclipse/openvsx/eclipse/PublisherComplianceChecker.java index c89d99314..5432b2f23 100644 --- a/server/src/main/java/org/eclipse/openvsx/eclipse/PublisherComplianceChecker.java +++ b/server/src/main/java/org/eclipse/openvsx/eclipse/PublisherComplianceChecker.java @@ -10,25 +10,23 @@ package org.eclipse.openvsx.eclipse; import jakarta.persistence.EntityManager; -import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.ExtensionService; import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.PersonalAccessToken; import org.eclipse.openvsx.entities.UserData; -import org.eclipse.openvsx.json.UserJson; import org.eclipse.openvsx.repositories.RepositoryService; -import org.eclipse.openvsx.util.ErrorResultException; import org.eclipse.openvsx.util.NamingUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.event.EventListener; -import org.springframework.data.util.Streamable; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionTemplate; import java.util.LinkedHashSet; +import java.util.List; +import java.util.stream.Collectors; @Component public class PublisherComplianceChecker { @@ -63,8 +61,10 @@ public void checkPublishers(ApplicationStartedEvent event) { if (!checkCompliance || !eclipseService.isActive()) return; - repositories.findAllUsers().forEach(user -> { - var accessTokens = repositories.findAccessTokens(user); + var publisherTokens = repositories.findAllAccessTokens().stream() + .collect(Collectors.groupingBy(PersonalAccessToken::getUser)); + publisherTokens.keySet().forEach(user -> { + var accessTokens = publisherTokens.get(user); if (!accessTokens.isEmpty() && !isCompliant(user)) { // Found a non-compliant publisher: deactivate all extension versions transactions.execute(status -> { @@ -92,7 +92,7 @@ private boolean isCompliant(UserData user) { && profile.publisherAgreements.openVsx.version != null; } - private void deactivateExtensions(Streamable accessTokens) { + private void deactivateExtensions(List accessTokens) { var affectedExtensions = new LinkedHashSet(); for (var accessToken : accessTokens) { var versions = repositories.findVersionsByAccessToken(accessToken, true); diff --git a/server/src/main/java/org/eclipse/openvsx/mirror/DataMirrorService.java b/server/src/main/java/org/eclipse/openvsx/mirror/DataMirrorService.java index 43b84bbfe..b85f143d6 100644 --- a/server/src/main/java/org/eclipse/openvsx/mirror/DataMirrorService.java +++ b/server/src/main/java/org/eclipse/openvsx/mirror/DataMirrorService.java @@ -145,8 +145,6 @@ public UserData createMirrorUser() { public UserData getOrAddUser(UserJson json) { var user = repositories.findUserByLoginName(json.provider, json.loginName); if (user == null) { - // TODO do we need all of the data? - // TODO Is this legal (GDPR, other laws)? I'm not a lawyer. user = new UserData(); user.setLoginName(json.loginName); user.setFullName(json.fullName); @@ -160,13 +158,10 @@ public UserData getOrAddUser(UserJson json) { } public String getOrAddAccessTokenValue(UserData user, String description) { - return repositories.findAccessTokens(user) - .filter(PersonalAccessToken::isActive) - .filter(token -> token.getDescription().equals(description)) - .stream() - .findFirst() - .map(PersonalAccessToken::getValue) - .orElse(users.createAccessToken(user, description).value); + var token = repositories.findAccessToken(user, description); + return token == null + ? users.createAccessToken(user, description).value + : token.getValue(); } @Transactional @@ -245,9 +240,8 @@ public void mirrorNamespaceMetadata(String namespaceName) { var localVerified = local.getNamespace(namespaceName).verified; if(!localVerified && remoteVerified) { // verify the namespace by adding an owner to it - var namespace = repositories.findNamespace(namespaceName); - var memberships = repositories.findMemberships(namespace); - users.addNamespaceMember(namespace, memberships.toList().get(0).getUser(), NamespaceMembership.ROLE_OWNER); + var membership = repositories.findFirstMembership(namespaceName); + users.addNamespaceMember(membership.getNamespace(), membership.getUser(), NamespaceMembership.ROLE_OWNER); } if(localVerified && !remoteVerified) { // unverify namespace by changing owner(s) back to contributor @@ -258,14 +252,13 @@ public void mirrorNamespaceMetadata(String namespaceName) { } public void ensureNamespaceMembership(UserData user, Namespace namespace) { - var membership = repositories.findMembership(user, namespace); - if (membership == null) { + if (!repositories.hasMembership(user, namespace)) { users.addNamespaceMember(namespace, user, NamespaceMembership.ROLE_CONTRIBUTOR); } } public void ensureNamespace(String namespaceName) { - if(repositories.findNamespace(namespaceName) == null) { + if(!repositories.namespaceExists(namespaceName)) { var json = new NamespaceJson(); json.name = namespaceName; admin.createNamespace(json); 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 1124686e0..b66f43852 100644 --- a/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java @@ -34,8 +34,8 @@ import java.io.IOException; import java.time.LocalDateTime; +import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; @Component public class PublishExtensionVersionHandler { @@ -79,12 +79,14 @@ public ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Per var dependencies = processor.getExtensionDependencies(); var bundledExtensions = processor.getBundledExtensions(); if (checkDependencies) { - dependencies = dependencies.stream() - .map(this::checkDependency) - .collect(Collectors.toList()); - bundledExtensions = bundledExtensions.stream() - .map(this::checkBundledExtension) - .collect(Collectors.toList()); + var parsedDependencies = dependencies.stream() + .map(id -> parseExtensionId(id, "extensionDependencies")) + .toList(); + + if(!parsedDependencies.isEmpty()) { + checkDependencies(parsedDependencies); + } + bundledExtensions.forEach(id -> parseExtensionId(id, "extensionPack")); } extVersion.setDependencies(dependencies); @@ -166,30 +168,22 @@ private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Us }); } - private String checkDependency(String dependency) { - return Observation.createNotStarted("PublishExtensionVersionHandler#checkDependency", observations).observe(() -> { - var split = dependency.split("\\."); - if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { - throw new ErrorResultException("Invalid 'extensionDependencies' format. Expected: '${namespace}.${name}'"); + private void checkDependencies(List dependencies) { + Observation.createNotStarted("PublishExtensionVersionHandler#checkDependencies", observations).observe(() -> { + var unresolvedDependency = repositories.findFirstUnresolvedDependency(dependencies); + if (unresolvedDependency != null) { + throw new ErrorResultException("Cannot resolve dependency: " + unresolvedDependency); } - var extensionCount = repositories.countExtensions(split[1], split[0]); - if (extensionCount == 0) { - throw new ErrorResultException("Cannot resolve dependency: " + dependency); - } - - return dependency; }); } - private String checkBundledExtension(String bundledExtension) { - return Observation.createNotStarted("PublishExtensionVersionHandler#checkBundledExtension", observations).observe(() -> { - var split = bundledExtension.split("\\."); - if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { - throw new ErrorResultException("Invalid 'extensionPack' format. Expected: '${namespace}.${name}'"); - } + private String[] parseExtensionId(String extensionId, String formatType) { + var split = extensionId.split("\\."); + if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { + throw new ErrorResultException("Invalid '" + formatType + "' format. Expected: '${namespace}.${name}'"); + } - return bundledExtension; - }); + return split; } @Async diff --git a/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionService.java b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionService.java index b72d61eab..68450a3b7 100644 --- a/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionService.java +++ b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionService.java @@ -47,7 +47,7 @@ public PublishExtensionVersionService( @Transactional public void deleteFileResources(ExtensionVersion extVersion) { - repositories.findFiles(extVersion).forEach(entityManager::remove); + repositories.deleteFiles(extVersion); } @Retryable diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java index d269dab07..ae3574cd3 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionJooqRepository.java @@ -56,12 +56,11 @@ public List findAllActiveByPublicId(Collection publicIds, Str public Extension findActiveByNameIgnoreCaseAndNamespaceNameIgnoreCase(String name, String namespaceName) { var query = findAllActive(); query.addConditions( - DSL.upper(EXTENSION.NAME).eq(DSL.upper(name)), - DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName)) + EXTENSION.NAME.equalIgnoreCase(name), + NAMESPACE.NAME.equalIgnoreCase(namespaceName) ); - var record = query.fetchOne(); - return record != null ? toExtension(record) : null; + return query.fetchOne(this::toExtension); } public List findAllPublicIds() { @@ -71,29 +70,24 @@ public List findAllPublicIds() { public Extension findPublicId(String namespace, String extension) { var query = findPublicId(); query.addConditions( - DSL.upper(EXTENSION.NAME).eq(DSL.upper(extension)), - DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespace)) + EXTENSION.NAME.equalIgnoreCase(extension), + NAMESPACE.NAME.equalIgnoreCase(namespace) ); - var record = query.fetchOne(); - return record != null ? toPublicId(record) : null; + return query.fetchOne(this::toPublicId); } public Extension findPublicId(String publicId) { var query = findPublicId(); query.addConditions(EXTENSION.PUBLIC_ID.eq(publicId)); - - var record = query.fetchOne(); - return record != null ? toPublicId(record) : null; + return query.fetchOne(this::toPublicId); } public Extension findNamespacePublicId(String publicId) { var query = findPublicId(); query.addConditions(NAMESPACE.PUBLIC_ID.eq(publicId)); query.addLimit(1); - - var record = query.fetchOne(); - return record != null ? toPublicId(record) : null; + return query.fetchOne(this::toPublicId); } private SelectQuery findPublicId() { @@ -226,4 +220,45 @@ public List fetchSitemapRows() { ); }); } + + public List findActiveExtensionNames(Namespace namespace) { + return dsl.select(EXTENSION.NAME) + .from(EXTENSION) + .where(EXTENSION.NAMESPACE_ID.eq(namespace.getId())) + .and(EXTENSION.ACTIVE.eq(true)) + .orderBy(EXTENSION.NAME.asc()) + .fetch(EXTENSION.NAME); + } + + public String findFirstUnresolvedDependency(List dependencies) { + if(dependencies.isEmpty()) { + return null; + } + + var ids = DSL.values(dependencies.stream().map(d -> DSL.row(d[0], d[1])).toArray(Row2[]::new)).as("ids", "namespace", "extension"); + var namespace = ids.field("namespace", String.class); + var extension = ids.field("extension", String.class); + var unresolvedDependency = DSL.concat(namespace, DSL.value("."), extension).as("unresolved_dependency"); + return dsl.select(unresolvedDependency) + .from(ids) + .leftJoin(NAMESPACE).on(NAMESPACE.NAME.eq(namespace)) + .leftJoin(EXTENSION).on(EXTENSION.NAME.eq(extension)) + .where(NAMESPACE.NAME.isNull()).or(EXTENSION.NAME.isNull()) + .limit(1) + .fetchOne(unresolvedDependency); + } + + public List findActiveExtensionsForUrls(Namespace namespace) { + return dsl.select(EXTENSION.ID, EXTENSION.NAME) + .from(EXTENSION) + .where(EXTENSION.NAMESPACE_ID.eq(namespace.getId())) + .and(EXTENSION.ACTIVE.eq(true)) + .fetch(record -> { + var extension = new Extension(); + extension.setId(record.get(EXTENSION.ID)); + extension.setName(record.get(EXTENSION.NAME)); + extension.setNamespace(namespace); + return extension; + }); + } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionReviewJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionReviewJooqRepository.java new file mode 100644 index 000000000..4a289c3c9 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionReviewJooqRepository.java @@ -0,0 +1,37 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU 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.repositories; + +import org.eclipse.openvsx.entities.Extension; +import org.eclipse.openvsx.entities.UserData; +import org.jooq.DSLContext; +import org.springframework.stereotype.Component; + +import static org.eclipse.openvsx.jooq.tables.ExtensionReview.EXTENSION_REVIEW; + +@Component +public class ExtensionReviewJooqRepository { + + private final DSLContext dsl; + + public ExtensionReviewJooqRepository(DSLContext dsl) { + this.dsl = dsl; + } + + public boolean hasActiveReview(Extension extension, UserData user) { + return dsl.fetchExists( + dsl.selectOne() + .from(EXTENSION_REVIEW) + .where(EXTENSION_REVIEW.EXTENSION_ID.eq(extension.getId())) + .and(EXTENSION_REVIEW.USER_ID.eq(user.getId())) + .and(EXTENSION_REVIEW.ACTIVE.eq(true)) + ); + } +} 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 4ec04859f..e93d62472 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.repositories; +import io.micrometer.observation.annotation.Observed; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.QueryRequest; @@ -157,12 +158,12 @@ public Map> findActiveVersionStringsSorted(Collection e .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); } - public List findActiveVersionReferencesSorted(Collection extensions, int numberOfRows) { - if(extensions.isEmpty()) { + public List findActiveVersionReferencesSorted(Collection extensionIds, int numberOfRows) { + if(extensionIds.isEmpty()) { return Collections.emptyList(); } - var ids = DSL.values(extensions.stream().map(Extension::getId).map(DSL::row).toArray(Row1[]::new)).as("ids", "id"); + var ids = DSL.values(extensionIds.stream().map(DSL::row).toArray(Row1[]::new)).as("ids", "id"); var namespaceCol = NAMESPACE.NAME.as("namespace"); var extensionCol = EXTENSION.NAME.as("extension"); var top = dsl.select( @@ -279,13 +280,13 @@ public Page findActiveVersions(QueryRequest request) { conditions.add(NAMESPACE.PUBLIC_ID.eq(request.namespaceUuid)); } if (!StringUtils.isEmpty(request.namespaceName)) { - conditions.add(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(request.namespaceName))); + conditions.add(NAMESPACE.NAME.equalIgnoreCase(request.namespaceName)); } if (!StringUtils.isEmpty(request.extensionUuid)) { conditions.add(EXTENSION.PUBLIC_ID.eq(request.extensionUuid)); } if (!StringUtils.isEmpty(request.extensionName)) { - conditions.add(DSL.upper(EXTENSION.NAME).eq(DSL.upper(request.extensionName))); + conditions.add(EXTENSION.NAME.equalIgnoreCase(request.extensionName)); } if(request.targetPlatform != null) { conditions.add(EXTENSION_VERSION.TARGET_PLATFORM.eq(request.targetPlatform)); @@ -344,44 +345,25 @@ public Page findActiveVersions(QueryRequest request) { return new PageImpl<>(fetch(query), PageRequest.of(request.offset / request.size, request.size), total); } - public List findAllActiveByVersionAndExtensionNameAndNamespaceName(String version, String extensionName, String namespaceName) { + public ExtensionVersion findActiveByVersionAndExtensionNameAndNamespaceName(String version, String extensionName, String namespaceName) { var query = findAllActive(); query.addConditions( EXTENSION_VERSION.VERSION.eq(version), - DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName)), - DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName)) + EXTENSION.NAME.equalIgnoreCase(extensionName), + NAMESPACE.NAME.equalIgnoreCase(namespaceName) ); - - return fetch(query); - } - - public List findAllActiveByExtensionNameAndNamespaceName(String targetPlatform, String extensionName, String namespaceName) { - var query = findAllActive(); - query.addConditions( - DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName)), - DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName)) + query.addOrderBy( + EXTENSION_VERSION.UNIVERSAL_TARGET_PLATFORM.desc(), + EXTENSION_VERSION.TARGET_PLATFORM.asc() ); - - if(targetPlatform != null) { - query.addConditions(EXTENSION_VERSION.TARGET_PLATFORM.eq(targetPlatform)); - } - - return fetch(query); - } - - public List findAllActiveByNamespaceName(String targetPlatform, String namespaceName) { - var query = findAllActive(); - query.addConditions(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName))); - if(targetPlatform != null) { - query.addConditions(EXTENSION_VERSION.TARGET_PLATFORM.eq(targetPlatform)); - } - - return fetch(query); + query.addLimit(1); + var results = fetch(query); + return !results.isEmpty() ? results.get(0) : null; } public List findAllActiveByExtensionName(String targetPlatform, String extensionName) { var query = findAllActive(); - query.addConditions(DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName))); + query.addConditions(EXTENSION.NAME.equalIgnoreCase(extensionName)); if(targetPlatform != null) { query.addConditions(EXTENSION_VERSION.TARGET_PLATFORM.eq(targetPlatform)); } @@ -672,6 +654,194 @@ public ExtensionVersion findLatest( return query.fetchOne((record) -> toExtensionVersionFull(record, extension, null)); } + @Observed + public ExtensionVersion findLatest( + String namespaceName, + String extensionName, + String targetPlatform, + boolean onlyPreRelease, + boolean onlyActive + ) { + var query = findLatestQuery(targetPlatform, onlyPreRelease, onlyActive); + query.addSelect( + NAMESPACE.ID, + NAMESPACE.PUBLIC_ID, + NAMESPACE.NAME, + NAMESPACE.DISPLAY_NAME, + EXTENSION.ID, + EXTENSION.PUBLIC_ID, + EXTENSION.NAME, + EXTENSION.AVERAGE_RATING, + EXTENSION.REVIEW_COUNT, + EXTENSION.DOWNLOAD_COUNT, + EXTENSION.PUBLISHED_DATE, + EXTENSION.LAST_UPDATED_DATE, + EXTENSION.ACTIVE, + USER_DATA.ID, + USER_DATA.ROLE, + USER_DATA.LOGIN_NAME, + USER_DATA.FULL_NAME, + USER_DATA.AVATAR_URL, + USER_DATA.PROVIDER_URL, + USER_DATA.PROVIDER, + EXTENSION_VERSION.ID, + EXTENSION_VERSION.VERSION, + EXTENSION_VERSION.POTENTIALLY_MALICIOUS, + EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.PREVIEW, + EXTENSION_VERSION.PRE_RELEASE, + EXTENSION_VERSION.TIMESTAMP, + EXTENSION_VERSION.DISPLAY_NAME, + EXTENSION_VERSION.DESCRIPTION, + EXTENSION_VERSION.ENGINES, + EXTENSION_VERSION.CATEGORIES, + EXTENSION_VERSION.TAGS, + EXTENSION_VERSION.EXTENSION_KIND, + EXTENSION_VERSION.LICENSE, + EXTENSION_VERSION.HOMEPAGE, + EXTENSION_VERSION.REPOSITORY, + EXTENSION_VERSION.SPONSOR_LINK, + EXTENSION_VERSION.BUGS, + EXTENSION_VERSION.MARKDOWN, + EXTENSION_VERSION.GALLERY_COLOR, + EXTENSION_VERSION.GALLERY_THEME, + EXTENSION_VERSION.LOCALIZED_LANGUAGES, + EXTENSION_VERSION.QNA, + EXTENSION_VERSION.DEPENDENCIES, + EXTENSION_VERSION.BUNDLED_EXTENSIONS, + SIGNATURE_KEY_PAIR.PUBLIC_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(SIGNATURE_KEY_PAIR, JoinType.LEFT_OUTER_JOIN, SIGNATURE_KEY_PAIR.ID.eq(EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID)); + query.addJoin(EXTENSION, EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)); + query.addJoin(NAMESPACE, NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)); + query.addConditions( + NAMESPACE.NAME.equalIgnoreCase(namespaceName), + EXTENSION.NAME.equalIgnoreCase(extensionName) + ); + return query.fetchOne(record -> { + var extVersion = toExtensionVersionFull(record); + extVersion.getExtension().setActive(record.get(EXTENSION.ACTIVE)); + extVersion.getExtension().getNamespace().setDisplayName(record.get(NAMESPACE.DISPLAY_NAME)); + return extVersion; + }); + } + + public Map findLatestIsPreview(Collection extensionIds) { + var latestQuery = findLatestQuery(null, false, true); + latestQuery.addSelect(EXTENSION_VERSION.PREVIEW); + latestQuery.addConditions(EXTENSION_VERSION.EXTENSION_ID.eq(EXTENSION.ID)); + var latest = latestQuery.asTable(); + + var query = dsl.selectQuery(); + query.addSelect( + EXTENSION.ID, + latest.field(EXTENSION_VERSION.PREVIEW) + ); + query.addFrom(EXTENSION, DSL.lateral(latest)); + query.addConditions(EXTENSION.ID.in(extensionIds)); + + return query.fetch(record -> { + var id = record.get(EXTENSION.ID); + var preview = record.get(latest.field(EXTENSION_VERSION.PREVIEW)); + return new AbstractMap.SimpleEntry<>(id, preview); + }) + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + public List findLatest(Collection extensionIds) { + var latestQuery = findLatestQuery(null, false, false); + latestQuery.addSelect( + EXTENSION_VERSION.ID, + EXTENSION_VERSION.VERSION, + EXTENSION_VERSION.POTENTIALLY_MALICIOUS, + EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.PREVIEW, + EXTENSION_VERSION.PRE_RELEASE, + EXTENSION_VERSION.TIMESTAMP, + EXTENSION_VERSION.DISPLAY_NAME, + EXTENSION_VERSION.DESCRIPTION, + EXTENSION_VERSION.ENGINES, + EXTENSION_VERSION.CATEGORIES, + EXTENSION_VERSION.TAGS, + EXTENSION_VERSION.EXTENSION_KIND, + EXTENSION_VERSION.LICENSE, + EXTENSION_VERSION.HOMEPAGE, + EXTENSION_VERSION.REPOSITORY, + EXTENSION_VERSION.SPONSOR_LINK, + EXTENSION_VERSION.BUGS, + EXTENSION_VERSION.MARKDOWN, + EXTENSION_VERSION.GALLERY_COLOR, + EXTENSION_VERSION.GALLERY_THEME, + EXTENSION_VERSION.LOCALIZED_LANGUAGES, + EXTENSION_VERSION.QNA, + EXTENSION_VERSION.DEPENDENCIES, + EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID, + EXTENSION_VERSION.PUBLISHED_WITH_ID + ); + latestQuery.addConditions(EXTENSION_VERSION.EXTENSION_ID.eq(EXTENSION.ID)); + var latest = latestQuery.asTable(); + + var query = dsl.selectQuery(); + query.addSelect( + NAMESPACE.ID, + NAMESPACE.NAME, + NAMESPACE.PUBLIC_ID, + EXTENSION.ID, + EXTENSION.NAME, + EXTENSION.PUBLIC_ID, + EXTENSION.AVERAGE_RATING, + EXTENSION.REVIEW_COUNT, + EXTENSION.DOWNLOAD_COUNT, + EXTENSION.PUBLISHED_DATE, + EXTENSION.LAST_UPDATED_DATE, + latest.field(EXTENSION_VERSION.ID), + latest.field(EXTENSION_VERSION.POTENTIALLY_MALICIOUS), + latest.field(EXTENSION_VERSION.VERSION), + latest.field(EXTENSION_VERSION.TARGET_PLATFORM), + latest.field(EXTENSION_VERSION.PREVIEW), + latest.field(EXTENSION_VERSION.PRE_RELEASE), + latest.field(EXTENSION_VERSION.TIMESTAMP), + latest.field(EXTENSION_VERSION.DISPLAY_NAME), + latest.field(EXTENSION_VERSION.DESCRIPTION), + latest.field(EXTENSION_VERSION.ENGINES), + latest.field(EXTENSION_VERSION.CATEGORIES), + latest.field(EXTENSION_VERSION.TAGS), + latest.field(EXTENSION_VERSION.EXTENSION_KIND), + latest.field(EXTENSION_VERSION.LICENSE), + latest.field(EXTENSION_VERSION.HOMEPAGE), + latest.field(EXTENSION_VERSION.REPOSITORY), + latest.field(EXTENSION_VERSION.SPONSOR_LINK), + latest.field(EXTENSION_VERSION.BUGS), + latest.field(EXTENSION_VERSION.MARKDOWN), + latest.field(EXTENSION_VERSION.GALLERY_COLOR), + latest.field(EXTENSION_VERSION.GALLERY_THEME), + latest.field(EXTENSION_VERSION.LOCALIZED_LANGUAGES), + latest.field(EXTENSION_VERSION.QNA), + latest.field(EXTENSION_VERSION.DEPENDENCIES), + latest.field(EXTENSION_VERSION.BUNDLED_EXTENSIONS), + SIGNATURE_KEY_PAIR.PUBLIC_ID, + USER_DATA.ID, + USER_DATA.ROLE, + USER_DATA.LOGIN_NAME, + USER_DATA.FULL_NAME, + USER_DATA.AVATAR_URL, + USER_DATA.PROVIDER_URL, + USER_DATA.PROVIDER + ); + query.addFrom(NAMESPACE); + query.addJoin(EXTENSION, EXTENSION.NAMESPACE_ID.eq(NAMESPACE.ID)); + query.addJoin(latest, JoinType.CROSS_APPLY, DSL.condition(true)); + query.addJoin(SIGNATURE_KEY_PAIR, JoinType.LEFT_OUTER_JOIN, SIGNATURE_KEY_PAIR.ID.eq(latest.field(EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID))); + query.addJoin(PERSONAL_ACCESS_TOKEN, JoinType.LEFT_OUTER_JOIN, PERSONAL_ACCESS_TOKEN.ID.eq(latest.field(EXTENSION_VERSION.PUBLISHED_WITH_ID))); + query.addJoin(USER_DATA, USER_DATA.ID.eq(PERSONAL_ACCESS_TOKEN.USER_DATA)); + query.addConditions(EXTENSION.ID.in(extensionIds)); + return query.fetch(record -> toExtensionVersionFull(record, null, new TableFieldMapper(latest))); + } + public List findLatest(Namespace namespace) { var latestQuery = findLatestQuery(null, false, true); latestQuery.addSelect( @@ -968,6 +1138,16 @@ public List findDistinctTargetPlatforms(Extension extension) { .fetch(EXTENSION_VERSION.TARGET_PLATFORM); } + public boolean hasSameVersion(ExtensionVersion extVersion) { + return dsl.fetchExists( + dsl.selectOne() + .from(EXTENSION_VERSION) + .where(EXTENSION_VERSION.EXTENSION_ID.eq(extVersion.getExtension().getId())) + .and(EXTENSION_VERSION.VERSION.eq(extVersion.getVersion())) + .and(EXTENSION_VERSION.PRE_RELEASE.eq(!extVersion.isPreRelease())) + ); + } + private interface FieldMapper { Field map(Field field); } 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 13c8ba973..89e9b6a4f 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java @@ -18,7 +18,6 @@ import org.springframework.data.util.Streamable; import java.time.LocalDateTime; -import java.util.List; public interface ExtensionVersionRepository extends Repository { @@ -26,8 +25,6 @@ public interface ExtensionVersionRepository extends Repository findByExtensionAndActiveTrue(Extension extension); - List findByExtensionAndActiveTrue(Extension extension, Pageable page); - Streamable findByVersionAndExtension(String version, Extension extension); ExtensionVersion findByVersionAndTargetPlatformAndExtension(String version, String targetPlatform, Extension extension); @@ -36,12 +33,10 @@ public interface ExtensionVersionRepository extends Repository findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String extensionName, String namespace); - Streamable findByPublishedWithUser(UserData user); - - Streamable findByPublishedWith(PersonalAccessToken publishedWith); - Streamable findByPublishedWithAndActive(PersonalAccessToken publishedWith, boolean active); + Streamable findByPublishedWithUserAndActive(UserData user, boolean active); + Streamable findAll(); Streamable findBySignatureKeyPairNotOrSignatureKeyPairIsNull(SignatureKeyPair keyPair); diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceJooqRepository.java index 54c3d0823..8ff1455c4 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceJooqRepository.java @@ -9,10 +9,15 @@ * ****************************************************************************** */ package org.eclipse.openvsx.repositories; +import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.FileResource; +import org.eclipse.openvsx.entities.Namespace; +import org.eclipse.openvsx.util.TargetPlatform; +import org.eclipse.openvsx.util.VersionAlias; import org.jooq.DSLContext; import org.jooq.Record; +import org.jooq.SelectQuery; import org.springframework.stereotype.Component; import java.util.Collection; @@ -21,7 +26,7 @@ import java.util.Map; import java.util.stream.Collectors; -import static org.eclipse.openvsx.jooq.Tables.FILE_RESOURCE; +import static org.eclipse.openvsx.jooq.Tables.*; @Component public class FileResourceJooqRepository { @@ -54,7 +59,14 @@ public List findAll(Collection extensionIds, Collection findAllResources(long extVersionId, String prefix) { - return dsl.select(FILE_RESOURCE.ID, FILE_RESOURCE.EXTENSION_ID, FILE_RESOURCE.NAME, FILE_RESOURCE.TYPE, FILE_RESOURCE.STORAGE_TYPE, FILE_RESOURCE.CONTENT) + return dsl.select( + FILE_RESOURCE.ID, + FILE_RESOURCE.EXTENSION_ID, + FILE_RESOURCE.NAME, + FILE_RESOURCE.TYPE, + FILE_RESOURCE.STORAGE_TYPE, + FILE_RESOURCE.CONTENT + ) .from(FILE_RESOURCE) .where(FILE_RESOURCE.TYPE.eq(FileResource.RESOURCE)) .and(FILE_RESOURCE.EXTENSION_ID.eq(extVersionId)) @@ -90,4 +102,109 @@ private FileResource toFileResource(Record record, ExtensionVersion extVersion) return fileResource; } + + public FileResource findByName(String namespace, String extension, String targetPlatform, String version, String name) { + var onlyPreRelease = VersionAlias.PRE_RELEASE.equals(version); + var query = findByQuery(namespace, extension, version, targetPlatform, onlyPreRelease); + query.addConditions(FILE_RESOURCE.NAME.equalIgnoreCase(name)); + query.addOrderBy(FILE_RESOURCE.TYPE); + + return query.fetchOne(this::mapFindByQueryResult); + } + + public FileResource findByType(String namespace, String extension, String targetPlatform, String version, String type) { + var onlyPreRelease = VersionAlias.PRE_RELEASE.equals(version); + var query = findByQuery(namespace, extension, version, targetPlatform, onlyPreRelease); + query.addConditions(FILE_RESOURCE.TYPE.eq(type)); + return query.fetchOne(this::mapFindByQueryResult); + } + + public FileResource findByTypeAndName(String namespace, String extension, String targetPlatform, String version, String type, String name) { + var onlyPreRelease = VersionAlias.PRE_RELEASE.equals(version); + var query = findByQuery(namespace, extension, version, targetPlatform, onlyPreRelease); + query.addConditions( + FILE_RESOURCE.TYPE.eq(type), + FILE_RESOURCE.NAME.equalIgnoreCase(name) + ); + return query.fetchOne(this::mapFindByQueryResult); + } + + private SelectQuery findByQuery( + String namespace, + String extension, + String version, + String targetPlatform, + boolean onlyPreRelease + ) { + var query = dsl.selectQuery(); + query.addSelect( + NAMESPACE.ID, + NAMESPACE.NAME, + EXTENSION.ID, + EXTENSION.NAME, + EXTENSION_VERSION.ID, + EXTENSION_VERSION.TARGET_PLATFORM, + EXTENSION_VERSION.VERSION, + FILE_RESOURCE.ID, + FILE_RESOURCE.NAME, + FILE_RESOURCE.TYPE, + FILE_RESOURCE.STORAGE_TYPE + ); + query.addFrom(FILE_RESOURCE); + query.addJoin(EXTENSION_VERSION, EXTENSION_VERSION.ID.eq(FILE_RESOURCE.EXTENSION_ID)); + query.addJoin(EXTENSION, EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)); + query.addJoin(NAMESPACE, NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)); + query.addConditions( + NAMESPACE.NAME.equalIgnoreCase(namespace), + EXTENSION.NAME.equalIgnoreCase(extension), + EXTENSION.ACTIVE.eq(true), + EXTENSION_VERSION.ACTIVE.eq(true) + ); + if(!VersionAlias.LATEST.equals(version) && !VersionAlias.PRE_RELEASE.equals(version)) { + query.addConditions(EXTENSION_VERSION.VERSION.eq(version)); + } + if(TargetPlatform.isValid(targetPlatform)) { + query.addConditions(EXTENSION_VERSION.TARGET_PLATFORM.eq(targetPlatform)); + } + if(onlyPreRelease) { + query.addConditions(EXTENSION_VERSION.PRE_RELEASE.eq(true)); + } + + query.addOrderBy( + EXTENSION_VERSION.SEMVER_MAJOR.desc(), + EXTENSION_VERSION.SEMVER_MINOR.desc(), + EXTENSION_VERSION.SEMVER_PATCH.desc(), + EXTENSION_VERSION.SEMVER_IS_PRE_RELEASE.asc(), + EXTENSION_VERSION.UNIVERSAL_TARGET_PLATFORM.desc(), + EXTENSION_VERSION.TARGET_PLATFORM.asc(), + EXTENSION_VERSION.TIMESTAMP.desc() + ); + query.addLimit(1); + return query; + } + + private FileResource mapFindByQueryResult(Record record) { + var namespace = new Namespace(); + namespace.setId(record.get(NAMESPACE.ID)); + namespace.setName(record.get(NAMESPACE.NAME)); + + var extension = new Extension(); + extension.setId(record.get(EXTENSION.ID)); + extension.setName(record.get(EXTENSION.NAME)); + extension.setNamespace(namespace); + + var extVersion = new ExtensionVersion(); + extVersion.setId(record.get(EXTENSION_VERSION.ID)); + extVersion.setTargetPlatform(record.get(EXTENSION_VERSION.TARGET_PLATFORM)); + extVersion.setVersion(record.get(EXTENSION_VERSION.VERSION)); + extVersion.setExtension(extension); + + var resource = new FileResource(); + resource.setId(record.get(FILE_RESOURCE.ID)); + resource.setName(record.get(FILE_RESOURCE.NAME)); + resource.setType(record.get(FILE_RESOURCE.TYPE)); + resource.setStorageType(record.get(FILE_RESOURCE.STORAGE_TYPE)); + resource.setExtension(extVersion); + return resource; + } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceRepository.java index 279d23113..0978fe436 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceRepository.java @@ -19,25 +19,21 @@ public interface FileResourceRepository extends Repository { - Streamable findByExtension(ExtensionVersion extVersion); + void deleteByExtension(ExtensionVersion extVersion); Streamable findByExtensionExtensionNamespace(Namespace namespace); Streamable findByStorageType(String storageType); - FileResource findFirstByExtensionAndNameIgnoreCaseOrderByType(ExtensionVersion extVersion, String name); - FileResource findByExtensionAndType(ExtensionVersion extVersion, String type); - FileResource findByExtensionAndTypeAndNameIgnoreCase(ExtensionVersion extVersion, String type, String name); - Streamable findByTypeAndStorageTypeAndNameIgnoreCaseIn(String type, String storageType, Collection names); - Streamable findByExtensionAndTypeIn(ExtensionVersion extVersion, Collection types); - void deleteByExtensionAndType(ExtensionVersion extVersion, String type); void deleteByType(String type); Streamable findByType(String type); + + Streamable findByExtensionAndStorageTypeNot(ExtensionVersion extVersion, String storageType); } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceJooqRepository.java index ec1615929..088ee10a1 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceJooqRepository.java @@ -53,4 +53,15 @@ public boolean publicIdExists(String publicId) { .fetch() .isNotEmpty(); } + + public String findNameByNameIgnoreCase(String name) { + return dsl.select(NAMESPACE.NAME) + .from(NAMESPACE) + .where(NAMESPACE.NAME.equalIgnoreCase(name)) + .fetchOne(NAMESPACE.NAME); + } + + public boolean exists(String name) { + return dsl.fetchExists(dsl.selectOne().from(NAMESPACE).where(NAMESPACE.NAME.equalIgnoreCase(name))); + } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipJooqRepository.java index daf946f51..0e53b9ea6 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipJooqRepository.java @@ -14,12 +14,13 @@ import org.eclipse.openvsx.entities.UserData; import org.jooq.DSLContext; import org.jooq.Record; +import org.jooq.SelectQuery; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.List; -import static org.eclipse.openvsx.jooq.Tables.NAMESPACE_MEMBERSHIP; +import static org.eclipse.openvsx.jooq.Tables.*; @Component public class NamespaceMembershipJooqRepository { @@ -39,24 +40,21 @@ public List findAllByNamespaceId(Collection namespace ) .from(NAMESPACE_MEMBERSHIP) .where(NAMESPACE_MEMBERSHIP.NAMESPACE.in(namespaceIds)) - .fetch() - .map(this::toNamespaceMembership); - } + .fetch(record -> { + var namespaceMembership = new NamespaceMembership(); + namespaceMembership.setId(record.get(NAMESPACE_MEMBERSHIP.ID)); + namespaceMembership.setRole(record.get(NAMESPACE_MEMBERSHIP.ROLE)); - private NamespaceMembership toNamespaceMembership(Record record) { - var namespaceMembership = new NamespaceMembership(); - namespaceMembership.setId(record.get(NAMESPACE_MEMBERSHIP.ID)); - namespaceMembership.setRole(record.get(NAMESPACE_MEMBERSHIP.ROLE)); + var namespace = new Namespace(); + namespace.setId(record.get(NAMESPACE_MEMBERSHIP.NAMESPACE)); + namespaceMembership.setNamespace(namespace); - var namespace = new Namespace(); - namespace.setId(record.get(NAMESPACE_MEMBERSHIP.NAMESPACE)); - namespaceMembership.setNamespace(namespace); + var user = new UserData(); + user.setId(record.get(NAMESPACE_MEMBERSHIP.USER_DATA)); + namespaceMembership.setUser(user); - var user = new UserData(); - user.setId(record.get(NAMESPACE_MEMBERSHIP.USER_DATA)); - namespaceMembership.setUser(user); - - return namespaceMembership; + return namespaceMembership; + }); } public boolean isVerified(Namespace namespace, UserData user) { @@ -72,4 +70,102 @@ public boolean isVerified(Namespace namespace, UserData user) { return result.isNotEmpty(); } + + public boolean hasRole(Namespace namespace, String role) { + return dsl.fetchExists(dsl.selectOne().from(NAMESPACE_MEMBERSHIP) + .where(NAMESPACE_MEMBERSHIP.NAMESPACE.eq(namespace.getId())) + .and(NAMESPACE_MEMBERSHIP.ROLE.equalIgnoreCase(role))); + } + + public boolean isOwner(UserData user, Namespace namespace) { + return dsl.fetchExists( + dsl.selectOne() + .from(NAMESPACE_MEMBERSHIP) + .where(NAMESPACE_MEMBERSHIP.NAMESPACE.eq(namespace.getId())) + .and(NAMESPACE_MEMBERSHIP.USER_DATA.eq(user.getId())) + .and(NAMESPACE_MEMBERSHIP.ROLE.eq(NamespaceMembership.ROLE_OWNER)) + ); + } + + public List findByNamespaceName(String namespaceName) { + var query = findMemberships(); + query.addConditions(NAMESPACE.NAME.equalIgnoreCase(namespaceName)); + return query.fetch(this::toNamespaceMembership); + } + + public List findMembershipsForOwner(UserData owner, String namespaceName) { + var namespaceOwnerQuery = dsl.select(NAMESPACE.ID) + .from(NAMESPACE) + .join(NAMESPACE_MEMBERSHIP).on(NAMESPACE_MEMBERSHIP.NAMESPACE.eq(NAMESPACE.ID)) + .where(NAMESPACE.NAME.equalIgnoreCase(namespaceName)) + .and(NAMESPACE_MEMBERSHIP.USER_DATA.eq(owner.getId())) + .and(NAMESPACE_MEMBERSHIP.ROLE.eq(NamespaceMembership.ROLE_OWNER)); + + var query = findMemberships(); + query.addConditions(NAMESPACE.ID.eq(namespaceOwnerQuery)); + return query.fetch(this::toNamespaceMembership); + } + + private SelectQuery findMemberships() { + var query = dsl.selectQuery(); + query.addSelect( + NAMESPACE.ID, + NAMESPACE.NAME, + NAMESPACE_MEMBERSHIP.ID, + NAMESPACE_MEMBERSHIP.ROLE, + USER_DATA.ID, + USER_DATA.LOGIN_NAME, + USER_DATA.FULL_NAME, + USER_DATA.AVATAR_URL, + USER_DATA.PROVIDER_URL, + USER_DATA.PROVIDER + ); + query.addFrom(NAMESPACE_MEMBERSHIP); + query.addJoin(NAMESPACE, NAMESPACE.ID.eq(NAMESPACE_MEMBERSHIP.NAMESPACE)); + query.addJoin(USER_DATA, USER_DATA.ID.eq(NAMESPACE_MEMBERSHIP.USER_DATA)); + return query; + } + + private NamespaceMembership toNamespaceMembership(Record record) { + var namespace = new Namespace(); + namespace.setId(record.get(NAMESPACE.ID)); + namespace.setName(record.get(NAMESPACE.NAME)); + + var user = new UserData(); + user.setId(record.get(USER_DATA.ID)); + user.setLoginName(record.get(USER_DATA.LOGIN_NAME)); + user.setFullName(record.get(USER_DATA.FULL_NAME)); + user.setAvatarUrl(record.get(USER_DATA.AVATAR_URL)); + user.setProvider(record.get(USER_DATA.PROVIDER)); + user.setProviderUrl(record.get(USER_DATA.PROVIDER_URL)); + + var membership = new NamespaceMembership(); + membership.setId(record.get(NAMESPACE_MEMBERSHIP.ID)); + membership.setRole(record.get(NAMESPACE_MEMBERSHIP.ROLE)); + membership.setNamespace(namespace); + membership.setUser(user); + + return membership; + } + + public boolean canPublish(UserData user, Namespace namespace) { + return dsl.fetchExists( + dsl.selectOne() + .from(NAMESPACE_MEMBERSHIP) + .where(NAMESPACE_MEMBERSHIP.NAMESPACE.eq(namespace.getId())) + .and(NAMESPACE_MEMBERSHIP.USER_DATA.eq(user.getId())) + .and(NAMESPACE_MEMBERSHIP.ROLE.equalIgnoreCase(NamespaceMembership.ROLE_CONTRIBUTOR) + .or(NAMESPACE_MEMBERSHIP.ROLE.equalIgnoreCase(NamespaceMembership.ROLE_OWNER)) + ) + ); + } + + public boolean hasMembership(UserData user, Namespace namespace) { + return dsl.fetchExists( + dsl.selectOne() + .from(NAMESPACE_MEMBERSHIP) + .where(NAMESPACE_MEMBERSHIP.NAMESPACE.eq(namespace.getId())) + .and(NAMESPACE_MEMBERSHIP.USER_DATA.eq(user.getId())) + ); + } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java index 00fe3c5f2..3c378d03a 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java @@ -19,15 +19,11 @@ public interface NamespaceMembershipRepository extends Repository findByNamespaceAndRoleIgnoreCase(Namespace namespace, String role); - long countByNamespaceAndRoleIgnoreCase(Namespace namespace, String role); - Streamable findByNamespace(Namespace namespace); - Streamable findByUser(UserData user); + Streamable findByUserOrderByNamespaceName(UserData user); - Streamable findByUserAndRoleIgnoreCaseOrderByNamespaceName(UserData user, String role); + NamespaceMembership findFirstByNamespaceNameIgnoreCase(String namespaceName); } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenJooqRepository.java new file mode 100644 index 000000000..d1410d8b9 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenJooqRepository.java @@ -0,0 +1,45 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU 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.repositories; + +import org.eclipse.openvsx.entities.UserData; +import org.jooq.DSLContext; +import org.springframework.stereotype.Component; + +import static org.eclipse.openvsx.jooq.Tables.PERSONAL_ACCESS_TOKEN; +import static org.eclipse.openvsx.jooq.Tables.USER_DATA; + +@Component +public class PersonalAccessTokenJooqRepository { + private final DSLContext dsl; + + public PersonalAccessTokenJooqRepository(DSLContext dsl) { + this.dsl = dsl; + } + + public boolean hasToken(String value) { + return dsl.fetchExists( + dsl.selectOne() + .from(PERSONAL_ACCESS_TOKEN) + .where(PERSONAL_ACCESS_TOKEN.VALUE.eq(value)) + ); + } + + public boolean isAdminToken(String value) { + return dsl.fetchExists( + dsl.selectOne() + .from(PERSONAL_ACCESS_TOKEN) + .join(USER_DATA).on(USER_DATA.ID.eq(PERSONAL_ACCESS_TOKEN.USER_DATA)) + .where(PERSONAL_ACCESS_TOKEN.VALUE.eq(value)) + .and(PERSONAL_ACCESS_TOKEN.ACTIVE.eq(true)) + .and(USER_DATA.ROLE.eq(UserData.ROLE_ADMIN)) + ); + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java index d32591c73..686aba6cd 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java @@ -16,12 +16,17 @@ public interface PersonalAccessTokenRepository extends Repository { + Streamable findAll(); + Streamable findByUser(UserData user); + Streamable findByUserAndActiveTrue(UserData user); + long countByUserAndActiveTrue(UserData user); PersonalAccessToken findById(long id); PersonalAccessToken findByValue(String value); + PersonalAccessToken findByUserAndDescriptionAndActiveTrue(UserData user, String description); } \ No newline at end of file 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 9fe060b31..62cdf8b23 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -18,6 +18,7 @@ import org.eclipse.openvsx.web.SitemapRow; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.util.Streamable; import org.springframework.stereotype.Component; @@ -27,8 +28,7 @@ import java.util.List; import java.util.Map; -import static org.eclipse.openvsx.entities.FileResource.DOWNLOAD; -import static org.eclipse.openvsx.entities.FileResource.DOWNLOAD_SIG; +import static org.eclipse.openvsx.entities.FileResource.*; @Component public class RepositoryService { @@ -50,16 +50,19 @@ public class RepositoryService { private final UserDataRepository userDataRepo; private final NamespaceMembershipRepository membershipRepo; private final PersonalAccessTokenRepository tokenRepo; + private final PersonalAccessTokenJooqRepository tokenJooqRepo; private final PersistedLogRepository persistedLogRepo; private final AzureDownloadCountProcessedItemRepository downloadCountRepo; private final ExtensionJooqRepository extensionJooqRepo; private final ExtensionVersionJooqRepository extensionVersionJooqRepo; private final FileResourceJooqRepository fileResourceJooqRepo; + private final ExtensionReviewJooqRepository extensionReviewJooqRepo; private final NamespaceMembershipJooqRepository membershipJooqRepo; private final AdminStatisticsRepository adminStatisticsRepo; private final AdminStatisticCalculationsRepository adminStatisticCalculationsRepo; private final MigrationItemRepository migrationItemRepo; private final SignatureKeyPairRepository signatureKeyPairRepo; + private final SignatureKeyPairJooqRepository signatureKeyPairJooqRepo; public RepositoryService( ObservationRegistry observations, @@ -72,16 +75,19 @@ public RepositoryService( UserDataRepository userDataRepo, NamespaceMembershipRepository membershipRepo, PersonalAccessTokenRepository tokenRepo, + PersonalAccessTokenJooqRepository tokenJooqRepo, PersistedLogRepository persistedLogRepo, AzureDownloadCountProcessedItemRepository downloadCountRepo, ExtensionJooqRepository extensionJooqRepo, ExtensionVersionJooqRepository extensionVersionJooqRepo, FileResourceJooqRepository fileResourceJooqRepo, + ExtensionReviewJooqRepository extensionReviewJooqRepo, NamespaceMembershipJooqRepository membershipJooqRepo, AdminStatisticsRepository adminStatisticsRepo, AdminStatisticCalculationsRepository adminStatisticCalculationsRepo, MigrationItemRepository migrationItemRepo, - SignatureKeyPairRepository signatureKeyPairRepo + SignatureKeyPairRepository signatureKeyPairRepo, + SignatureKeyPairJooqRepository signatureKeyPairJooqRepo ) { this.observations = observations; this.namespaceRepo = namespaceRepo; @@ -93,24 +99,27 @@ public RepositoryService( this.userDataRepo = userDataRepo; this.membershipRepo = membershipRepo; this.tokenRepo = tokenRepo; + this.tokenJooqRepo = tokenJooqRepo; this.persistedLogRepo = persistedLogRepo; this.downloadCountRepo = downloadCountRepo; this.extensionJooqRepo = extensionJooqRepo; this.extensionVersionJooqRepo = extensionVersionJooqRepo; this.fileResourceJooqRepo = fileResourceJooqRepo; + this.extensionReviewJooqRepo = extensionReviewJooqRepo; this.membershipJooqRepo = membershipJooqRepo; this.adminStatisticsRepo = adminStatisticsRepo; this.adminStatisticCalculationsRepo = adminStatisticCalculationsRepo; this.migrationItemRepo = migrationItemRepo; this.signatureKeyPairRepo = signatureKeyPairRepo; + this.signatureKeyPairJooqRepo = signatureKeyPairJooqRepo; } public Namespace findNamespace(String name) { return Observation.createNotStarted("RepositoryService#findNamespace", observations).observe(() -> namespaceRepo.findByNameIgnoreCase(name)); } - public Namespace findNamespaceByPublicId(String publicId) { - return namespaceRepo.findByPublicId(publicId); + public String findNamespaceName(String name) { + return namespaceJooqRepo.findNameByNameIgnoreCase(name); } public Streamable findOrphanNamespaces() { @@ -163,10 +172,6 @@ public long countExtensions() { return extensionRepo.count(); } - public long countExtensions(String name, String namespace) { - return Observation.createNotStarted("RepositoryService#countExtensions", observations).observe(() -> extensionRepo.countByNameIgnoreCaseAndNamespaceNameIgnoreCase(name, namespace)); - } - public int getMaxExtensionDownloadCount() { return extensionRepo.getMaxDownloadCount(); } @@ -211,8 +216,8 @@ public Map> findActiveVersionStringsSorted(Collection e return extensionVersionJooqRepo.findActiveVersionStringsSorted(extensionIds, targetPlatform, MAX_VERSIONS); } - public List findActiveVersionReferencesSorted(Collection extensions) { - return extensionVersionJooqRepo.findActiveVersionReferencesSorted(extensions, MAX_VERSIONS); + public List findActiveVersionReferencesSorted(Collection extensionIds) { + return extensionVersionJooqRepo.findActiveVersionReferencesSorted(extensionIds, MAX_VERSIONS); } public Streamable findBundledExtensionsReference(Extension extension) { @@ -231,24 +236,32 @@ public Streamable findVersionsByAccessToken(PersonalAccessToke return extensionVersionRepo.findByPublishedWithAndActive(publishedWith, active); } + public Streamable findVersionsByUser(UserData user, boolean active) { + return extensionVersionRepo.findByPublishedWithUserAndActive(user, active); + } + public LocalDateTime getOldestExtensionTimestamp() { return extensionVersionRepo.getOldestTimestamp(); } public Streamable findFiles(ExtensionVersion extVersion) { - return fileResourceRepo.findByExtension(extVersion); + return fileResourceRepo.findByExtensionAndStorageTypeNot(extVersion, STORAGE_DB); + } + + public void deleteFiles(ExtensionVersion extVersion) { + fileResourceRepo.deleteByExtension(extVersion); } public Streamable findFilesByStorageType(String storageType) { return fileResourceRepo.findByStorageType(storageType); } - public FileResource findFileByName(ExtensionVersion extVersion, String name) { - return fileResourceRepo.findFirstByExtensionAndNameIgnoreCaseOrderByType(extVersion, name); + public FileResource findFileByName(String namespace, String extension, String targetPlatform, String version, String name) { + return fileResourceJooqRepo.findByName(namespace, extension, targetPlatform, version, name); } - public FileResource findFileByTypeAndName(ExtensionVersion extVersion, String type, String name) { - return fileResourceRepo.findByExtensionAndTypeAndNameIgnoreCase(extVersion, type, name); + public FileResource findFileByTypeAndName(String namespace, String extension, String targetPlatform, String version, String type, String name) { + return fileResourceJooqRepo.findByTypeAndName(namespace, extension, targetPlatform, version, type, name); } public Streamable findDownloadsByStorageTypeAndName(String storageType, Collection names) { @@ -269,6 +282,14 @@ public FileResource findFileByType(ExtensionVersion extVersion, String type) { return fileResourceRepo.findByExtensionAndType(extVersion, type); } + public FileResource findFileByType(String namespace, String extension, String targetPlatform, String version, String type) { + if(FileResource.RESOURCE.equals(type)) { + throw new IllegalArgumentException("There are multiple files of type: " + FileResource.RESOURCE); + } + + return fileResourceJooqRepo.findByType(namespace, extension, targetPlatform, version, type); + } + public List findFilesByType(Collection extVersions, Collection types) { return Observation.createNotStarted("RepositoryService#findFilesByType", observations).observe(() -> fileResourceJooqRepo.findByType(extVersions, types)); } @@ -293,12 +314,8 @@ public UserData findUserByLoginName(String provider, String loginName) { return userDataRepo.findByProviderAndLoginName(provider, loginName); } - public Streamable findUsersByLoginNameStartingWith(String loginNameStart) { - return userDataRepo.findByLoginNameStartingWith(loginNameStart); - } - - public Streamable findAllUsers() { - return userDataRepo.findAll(); + public Page findUsersByLoginNameStartingWith(String loginNameStart, int limit) { + return userDataRepo.findByLoginNameStartingWith(loginNameStart, Pageable.ofSize(limit)); } public long countUsers() { @@ -309,6 +326,10 @@ public NamespaceMembership findMembership(UserData user, Namespace namespace) { return Observation.createNotStarted("RepositoryService#findMembership", observations).observe(() -> membershipRepo.findByUserAndNamespace(user, namespace)); } + public boolean hasMembership(UserData user, Namespace namespace) { + return membershipJooqRepo.hasMembership(user, namespace); + } + public boolean isVerified(Namespace namespace, UserData user) { return Observation.createNotStarted("RepositoryService#isVerified", observations).observe(() -> membershipJooqRepo.isVerified(namespace, user)); } @@ -317,22 +338,34 @@ public Streamable findMemberships(Namespace namespace, Stri return membershipRepo.findByNamespaceAndRoleIgnoreCase(namespace, role); } - public long countMemberships(Namespace namespace, String role) { - return membershipRepo.countByNamespaceAndRoleIgnoreCase(namespace, role); + public boolean hasMemberships(Namespace namespace, String role) { + return membershipJooqRepo.hasRole(namespace, role); } - public Streamable findMemberships(UserData user, String role) { - return membershipRepo.findByUserAndRoleIgnoreCaseOrderByNamespaceName(user, role); + public Streamable findMemberships(UserData user) { + return membershipRepo.findByUserOrderByNamespaceName(user); } public Streamable findMemberships(Namespace namespace) { return membershipRepo.findByNamespace(namespace); } + public List findMemberships(String namespaceName) { + return membershipJooqRepo.findByNamespaceName(namespaceName); + } + public Streamable findAccessTokens(UserData user) { return tokenRepo.findByUser(user); } + public Streamable findAllAccessTokens() { + return tokenRepo.findAll(); + } + + public Streamable findActiveAccessTokens(UserData user) { + return tokenRepo.findByUserAndActiveTrue(user); + } + public long countActiveAccessTokens(UserData user) { return tokenRepo.countByUserAndActiveTrue(user); } @@ -341,6 +374,10 @@ public PersonalAccessToken findAccessToken(String value) { return Observation.createNotStarted("RepositoryService#findAccessToken", observations).observe(() -> tokenRepo.findByValue(value)); } + public boolean isAdminToken(String value) { + return tokenJooqRepo.isAdminToken(value); + } + public PersonalAccessToken findAccessToken(long id) { return tokenRepo.findById(id); } @@ -379,20 +416,8 @@ public List findActiveExtensionVersions(Collection exten return extensionVersionJooqRepo.findAllActiveByExtensionIdAndTargetPlatform(extensionIds, targetPlatform); } - public List findActiveExtensionVersionsByVersion(String version, String extensionName, String namespaceName) { - return extensionVersionJooqRepo.findAllActiveByVersionAndExtensionNameAndNamespaceName(version, extensionName, namespaceName); - } - - public List findActiveExtensionVersionsByExtensionName(String targetPlatform, String extensionName, String namespaceName) { - return extensionVersionJooqRepo.findAllActiveByExtensionNameAndNamespaceName(targetPlatform, extensionName, namespaceName); - } - - public List findActiveExtensionVersionsByNamespaceName(String targetPlatform, String namespaceName) { - return extensionVersionJooqRepo.findAllActiveByNamespaceName(targetPlatform, namespaceName); - } - - public List findActiveExtensionVersionsByExtensionName(String targetPlatform, String extensionName) { - return extensionVersionJooqRepo.findAllActiveByExtensionName(targetPlatform, extensionName); + public ExtensionVersion findActiveExtensionVersion(String version, String extensionName, String namespaceName) { + return extensionVersionJooqRepo.findActiveByVersionAndExtensionNameAndNamespaceName(version, extensionName, namespaceName); } public List findFileResourcesByExtensionVersionIdAndType(Collection extensionVersionIds, Collection types) { @@ -459,10 +484,6 @@ public Streamable findTargetPlatformVersions(String version, S return extensionVersionRepo.findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(version, extensionName, namespaceName); } - public Streamable findVersions(UserData user) { - return extensionVersionRepo.findByPublishedWithUser(user); - } - public void deleteFileResources(ExtensionVersion extVersion, String type) { fileResourceRepo.deleteByExtensionAndType(extVersion, type); } @@ -560,10 +581,6 @@ public void updateExtensionPublicIds(Map publicIds) { extensionJooqRepo.updatePublicIds(publicIds); } - public void updateExtensionPublicId(long id, String publicId) { - extensionJooqRepo.updatePublicId(id, publicId); - } - public void updateNamespacePublicIds(Map publicIds) { namespaceJooqRepo.updatePublicIds(publicIds); } @@ -588,6 +605,10 @@ public List findVersionsForUrls(Extension extension, String ta return extensionVersionJooqRepo.findVersionsForUrls(extension, targetPlatform, version); } + public List findActiveExtensionsForUrls(Namespace namespace) { + return extensionJooqRepo.findActiveExtensionsForUrls(namespace); + } + public ExtensionVersion findExtensionVersion(String namespace, String extension, String targetPlatform, String version) { return extensionVersionJooqRepo.find(namespace, extension, targetPlatform, version); } @@ -602,10 +623,22 @@ public ExtensionVersion findLatestVersion(Extension extension, String targetPlat return extensionVersionJooqRepo.findLatest(extension, targetPlatform, onlyPreRelease, onlyActive); } + public ExtensionVersion findLatestVersion(String namespaceName, String extensionName, String targetPlatform, boolean onlyPreRelease, boolean onlyActive) { + return extensionVersionJooqRepo.findLatest(namespaceName, extensionName, targetPlatform, onlyPreRelease, onlyActive); + } + public List findLatestVersions(Namespace namespace) { return extensionVersionJooqRepo.findLatest(namespace); } + public List findLatestVersions(Collection extensionIds) { + return extensionVersionJooqRepo.findLatest(extensionIds); + } + + public Map findLatestVersionsIsPreview(Collection extensionIds) { + return extensionVersionJooqRepo.findLatestIsPreview(extensionIds); + } + public List findLatestVersions(UserData user) { return extensionVersionJooqRepo.findLatest(user); } @@ -617,4 +650,52 @@ public List findExtensionTargetPlatforms(Extension extension) { public void deactivateKeyPairs() { signatureKeyPairRepo.updateActiveSetFalse(); } + + public List findActiveExtensionNames(Namespace namespace) { + return extensionJooqRepo.findActiveExtensionNames(namespace); + } + + public List findMembershipsForOwner(UserData user, String namespaceName) { + return membershipJooqRepo.findMembershipsForOwner(user, namespaceName); + } + + public boolean isNamespaceOwner(UserData user, Namespace namespace) { + return membershipJooqRepo.isOwner(user, namespace); + } + + public boolean namespaceExists(String namespaceName) { + return namespaceJooqRepo.exists(namespaceName); + } + + public boolean hasSameVersion(ExtensionVersion extVersion) { + return extensionVersionJooqRepo.hasSameVersion(extVersion); + } + + public boolean hasActiveReview(Extension extension, UserData user) { + return extensionReviewJooqRepo.hasActiveReview(extension, user); + } + + public boolean hasAccessToken(String value) { + return tokenJooqRepo.hasToken(value); + } + + public boolean canPublishInNamespace(UserData user, Namespace namespace) { + return membershipJooqRepo.canPublish(user, namespace); + } + + public String findSignatureKeyPairPublicId(String namespace, String extension, String targetPlatform, String version) { + return signatureKeyPairJooqRepo.findPublicId(namespace, extension, targetPlatform, version); + } + + public String findFirstUnresolvedDependency(List dependencies) { + return extensionJooqRepo.findFirstUnresolvedDependency(dependencies); + } + + public PersonalAccessToken findAccessToken(UserData user, String description) { + return tokenRepo.findByUserAndDescriptionAndActiveTrue(user, description); + } + + public NamespaceMembership findFirstMembership(String namespaceName) { + return membershipRepo.findFirstByNamespaceNameIgnoreCase(namespaceName); + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/SignatureKeyPairJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/SignatureKeyPairJooqRepository.java new file mode 100644 index 000000000..4d05dfdfe --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/repositories/SignatureKeyPairJooqRepository.java @@ -0,0 +1,64 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU 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.repositories; + +import org.eclipse.openvsx.util.TargetPlatform; +import org.eclipse.openvsx.util.VersionAlias; +import org.jooq.DSLContext; +import org.springframework.stereotype.Component; + +import static org.eclipse.openvsx.jooq.Tables.*; +import static org.eclipse.openvsx.jooq.Tables.EXTENSION; + +@Component +public class SignatureKeyPairJooqRepository { + private final DSLContext dsl; + + public SignatureKeyPairJooqRepository(DSLContext dsl) { + this.dsl = dsl; + } + + public String findPublicId(String namespace, String extension, String targetPlatform, String version) { + var query = dsl.selectQuery(); + query.addSelect(SIGNATURE_KEY_PAIR.PUBLIC_ID); + query.addFrom(SIGNATURE_KEY_PAIR); + query.addJoin(EXTENSION_VERSION, EXTENSION_VERSION.SIGNATURE_KEY_PAIR_ID.eq(SIGNATURE_KEY_PAIR.ID)); + query.addJoin(EXTENSION, EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)); + query.addJoin(NAMESPACE, NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)); + query.addConditions( + NAMESPACE.NAME.equalIgnoreCase(namespace), + EXTENSION.NAME.equalIgnoreCase(extension), + EXTENSION_VERSION.ACTIVE.eq(true) + ); + var onlyPreRelease = VersionAlias.PRE_RELEASE.equals(version); + if(!VersionAlias.LATEST.equals(version) && !onlyPreRelease) { + query.addConditions(EXTENSION_VERSION.VERSION.eq(version)); + } + if(TargetPlatform.isValid(targetPlatform)) { + query.addConditions(EXTENSION_VERSION.TARGET_PLATFORM.eq(targetPlatform)); + } + if(onlyPreRelease) { + query.addConditions(EXTENSION_VERSION.PRE_RELEASE.eq(true)); + } + + query.addOrderBy( + EXTENSION_VERSION.SEMVER_MAJOR.desc(), + EXTENSION_VERSION.SEMVER_MINOR.desc(), + EXTENSION_VERSION.SEMVER_PATCH.desc(), + EXTENSION_VERSION.SEMVER_IS_PRE_RELEASE.asc(), + EXTENSION_VERSION.UNIVERSAL_TARGET_PLATFORM.desc(), + EXTENSION_VERSION.TARGET_PLATFORM.asc(), + EXTENSION_VERSION.TIMESTAMP.desc() + ); + query.addLimit(1); + return query.fetchOne(SIGNATURE_KEY_PAIR.PUBLIC_ID); + } +} + diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java index 019a33142..7a70f0abc 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/UserDataRepository.java @@ -9,19 +9,16 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; -import org.springframework.data.repository.Repository; -import org.springframework.data.util.Streamable; import org.eclipse.openvsx.entities.UserData; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.Repository; public interface UserDataRepository extends Repository { - UserData findByProviderAndAuthId(String provider, String authId); - UserData findByProviderAndLoginName(String provider, String loginName); - Streamable findByLoginNameStartingWith(String loginNameStart); - - Streamable findAll(); + Page findByLoginNameStartingWith(String loginNameStart, Pageable page); long count(); diff --git a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java index 299f091d8..23ac2d40b 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java @@ -197,13 +197,6 @@ protected String getBlobName(Namespace namespace) { return UrlUtil.createApiUrl("", namespace.getName(), "logo", namespace.getLogoName()).substring(1); // remove first '/' } - @Override - public TempFile downloadNamespaceLogo(Namespace namespace) throws IOException { - var logoFile = new TempFile("namespace-logo", ".png"); - getContainerClient().getBlobClient(getBlobName(namespace)).downloadToFile(logoFile.getPath().toString(), true); - return logoFile; - } - @Override public void copyFiles(List> pairs) { var copyOperations = new ArrayList>(); diff --git a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java index 454e8bc76..a1f978fe8 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java @@ -187,19 +187,6 @@ protected String getObjectId(Namespace namespace) { return UrlUtil.createApiUrl("", namespace.getName(), "logo", namespace.getLogoName()).substring(1); // remove first '/' } - @Override - public TempFile downloadNamespaceLogo(Namespace namespace) throws IOException { - var logoFile = new TempFile("namespace-logo", ".png"); - try ( - var reader = getStorage().reader(BlobId.of(bucketId, getObjectId(namespace))); - var output = new FileOutputStream(logoFile.getPath().toFile()) - ) { - output.getChannel().transferFrom(reader, 0, Long.MAX_VALUE); - } - - return logoFile; - } - @Override public void copyFiles(List> pairs) { for(var pair : pairs) { diff --git a/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java index 01a09aff2..ab1f3b81b 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java @@ -60,7 +60,5 @@ public interface IStorageService { */ URI getNamespaceLogoLocation(Namespace namespace); - TempFile downloadNamespaceLogo(Namespace namespace) throws IOException; - void copyFiles(List> pairs); } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java new file mode 100644 index 000000000..9b9c0cc99 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java @@ -0,0 +1,80 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU 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.storage; + +import io.micrometer.observation.annotation.Observed; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import org.eclipse.openvsx.entities.FileResource; +import org.eclipse.openvsx.entities.Namespace; +import org.eclipse.openvsx.util.TempFile; +import org.eclipse.openvsx.util.UrlUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; + +@Component +public class LocalStorageService { + + private final EntityManager entityManager; + + public LocalStorageService(EntityManager entityManager) { + this.entityManager = entityManager; + } + + public URI getLocation(FileResource resource) { + return URI.create(UrlUtil.createApiFileUrl(UrlUtil.getBaseUrl(), resource.getExtension(), resource.getName())); + } + + @Observed + public URI getNamespaceLogoLocation(Namespace namespace) { + return URI.create(UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "api", namespace.getName(), "logo", namespace.getLogoName())); + } + + public TempFile downloadNamespaceLogo(Namespace namespace) throws IOException { + var logoFile = createNamespaceLogoFile(); + Files.write(logoFile.getPath(), namespace.getLogoBytes()); + return logoFile; + } + + public TempFile createNamespaceLogoFile() throws IOException { + return new TempFile("namespace-logo", ".png"); + } + + @Transactional + public ResponseEntity getFileResponse(FileResource resource) { + resource = entityManager.find(FileResource.class, resource.getId()); + var headers = getFileResponseHeaders(resource.getName()); + return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK); + } + + @Transactional + public ResponseEntity getNamespaceLogo(Namespace namespace) { + namespace = entityManager.merge(namespace); + var headers = getFileResponseHeaders(namespace.getLogoName()); + return new ResponseEntity<>(namespace.getLogoBytes(), headers, HttpStatus.OK); + } + + public HttpHeaders getFileResponseHeaders(String fileName) { + var headers = new HttpHeaders(); + headers.setContentType(StorageUtil.getFileType(fileName)); + if (fileName.endsWith(".vsix")) { + headers.add("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + } else { + headers.setCacheControl(StorageUtil.getCacheControl(fileName)); + } + return headers; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java index 0ecf2aaf3..4832da9ad 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import static org.eclipse.openvsx.entities.FileResource.*; +import static org.eclipse.openvsx.util.UrlUtil.createApiFileUrl; /** * Provides utility around storing file resources and acts as a composite storage @@ -50,6 +51,7 @@ public class StorageUtilService implements IStorageService { private final RepositoryService repositories; private final GoogleCloudStorageService googleStorage; private final AzureBlobStorageService azureStorage; + private final LocalStorageService localStorage; private final AzureDownloadCountService azureDownloadCountService; private final SearchUtilService search; private final CacheService cache; @@ -69,6 +71,7 @@ public StorageUtilService( GoogleCloudStorageService googleStorage, AzureBlobStorageService azureStorage, AzureDownloadCountService azureDownloadCountService, + LocalStorageService localStorage, SearchUtilService search, CacheService cache, EntityManager entityManager, @@ -77,6 +80,7 @@ public StorageUtilService( this.repositories = repositories; this.googleStorage = googleStorage; this.azureStorage = azureStorage; + this.localStorage = localStorage; this.azureDownloadCountService = azureDownloadCountService; this.search = search; this.cache = cache; @@ -211,7 +215,7 @@ public URI getLocation(FileResource resource) { case STORAGE_AZURE: return azureStorage.getLocation(resource); case STORAGE_DB: - return URI.create(getFileUrl(resource.getName(), resource.getExtension(), UrlUtil.getBaseUrl())); + return localStorage.getLocation(resource); default: return null; } @@ -225,39 +229,12 @@ public URI getNamespaceLogoLocation(Namespace namespace) { case STORAGE_AZURE: return azureStorage.getNamespaceLogoLocation(namespace); case STORAGE_DB: - return URI.create(UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "api", namespace.getName(), "logo", namespace.getLogoName())); + return localStorage.getNamespaceLogoLocation(namespace); default: return null; } } - public TempFile downloadNamespaceLogo(Namespace namespace) throws IOException { - if(namespace.getLogoStorageType() == null) { - return createNamespaceLogoFile(); - } - - switch (namespace.getLogoStorageType()) { - case STORAGE_GOOGLE: - return googleStorage.downloadNamespaceLogo(namespace); - case STORAGE_AZURE: - return azureStorage.downloadNamespaceLogo(namespace); - case STORAGE_DB: - var logoFile = createNamespaceLogoFile(); - Files.write(logoFile.getPath(), namespace.getLogoBytes()); - return logoFile; - default: - return createNamespaceLogoFile(); - } - } - - private TempFile createNamespaceLogoFile() throws IOException { - return new TempFile("namespace-logo", ".png"); - } - - private String getFileUrl(String name, ExtensionVersion extVersion, String serverUrl) { - return UrlUtil.createApiFileUrl(serverUrl, extVersion, name); - } - /** * Returns URLs for the given file types as a map of ExtensionVersion.id by a map of type by file URL, to be used in JSON response data. */ @@ -270,7 +247,7 @@ public Map> getFileUrls(Collection e var resources = repositories.findFilesByType(extVersions, Arrays.asList(types)); for (var resource : resources) { var extVersion = resource.getExtension(); - type2Url.get(extVersion.getId()).put(resource.getType(), getFileUrl(resource.getName(), extVersion, serverUrl)); + type2Url.get(extVersion.getId()).put(resource.getType(), createApiFileUrl(serverUrl, extVersion, resource.getName())); } return type2Url; @@ -284,8 +261,8 @@ public void increaseDownloadCount(FileResource resource) { return; } - resource = entityManager.merge(resource); - var extension = resource.getExtension().getExtension(); + var managedResource = entityManager.find(FileResource.class, resource.getId()); + var extension = managedResource.getExtension().getExtension(); extension.setDownloadCount(extension.getDownloadCount() + 1); cache.evictNamespaceDetails(extension); @@ -296,22 +273,12 @@ public void increaseDownloadCount(FileResource resource) { } public HttpHeaders getFileResponseHeaders(String fileName) { - var headers = new HttpHeaders(); - headers.setContentType(StorageUtil.getFileType(fileName)); - if (fileName.endsWith(".vsix") || fileName.endsWith(".sigzip")) { - headers.add("Content-Disposition", "attachment; filename=\"" + fileName + "\""); - } else { - headers.setCacheControl(StorageUtil.getCacheControl(fileName)); - } - return headers; + return localStorage.getFileResponseHeaders(fileName); } - @Transactional public ResponseEntity getFileResponse(FileResource resource) { - resource = entityManager.merge(resource); if (resource.getStorageType().equals(STORAGE_DB)) { - var headers = getFileResponseHeaders(resource.getName()); - return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK); + return localStorage.getFileResponse(resource); } else { return ResponseEntity.status(HttpStatus.FOUND) .location(getLocation(resource)) @@ -320,12 +287,9 @@ public ResponseEntity getFileResponse(FileResource resource) { } } - @Transactional public ResponseEntity getNamespaceLogo(Namespace namespace) { - namespace = entityManager.merge(namespace); if (namespace.getLogoStorageType().equals(STORAGE_DB)) { - var headers = getFileResponseHeaders(namespace.getLogoName()); - return new ResponseEntity<>(namespace.getLogoBytes(), headers, HttpStatus.OK); + return localStorage.getNamespaceLogo(namespace); } else { return ResponseEntity.status(HttpStatus.FOUND) .location(getNamespaceLogoLocation(namespace)) diff --git a/server/src/main/java/org/eclipse/openvsx/util/BuiltInExtensionUtil.java b/server/src/main/java/org/eclipse/openvsx/util/BuiltInExtensionUtil.java index 4d46665ea..2feb96b0f 100644 --- a/server/src/main/java/org/eclipse/openvsx/util/BuiltInExtensionUtil.java +++ b/server/src/main/java/org/eclipse/openvsx/util/BuiltInExtensionUtil.java @@ -25,6 +25,6 @@ public static boolean isBuiltIn(String namespace) { } public static boolean isBuiltIn(Extension extension) { - return BUILT_IN_EXTENSION_NAMESPACE.equals(extension.getNamespace().getName()); + return BUILT_IN_EXTENSION_NAMESPACE.equalsIgnoreCase(extension.getNamespace().getName()); } } diff --git a/server/src/main/java/org/eclipse/openvsx/util/UrlUtil.java b/server/src/main/java/org/eclipse/openvsx/util/UrlUtil.java index 16f4feafa..7eb9f9f4d 100644 --- a/server/src/main/java/org/eclipse/openvsx/util/UrlUtil.java +++ b/server/src/main/java/org/eclipse/openvsx/util/UrlUtil.java @@ -235,7 +235,10 @@ public static String extractWildcardPath(HttpServletRequest request, String patt } public static String getPublicKeyUrl(ExtensionVersion extVersion) { - var publicId = extVersion.getSignatureKeyPair().getPublicId(); + return getPublicKeyUrl(extVersion.getSignatureKeyPair().getPublicId()); + } + + public static String getPublicKeyUrl(String publicId) { return createApiUrl(getBaseUrl(), "api", "-", "public-key", publicId); } diff --git a/server/src/test/java/org/eclipse/openvsx/IntegrationTest.java b/server/src/test/java/org/eclipse/openvsx/IntegrationTest.java index 88ff59665..c487a66b0 100644 --- a/server/src/test/java/org/eclipse/openvsx/IntegrationTest.java +++ b/server/src/test/java/org/eclipse/openvsx/IntegrationTest.java @@ -12,6 +12,8 @@ import com.fasterxml.jackson.databind.JsonNode; import org.eclipse.openvsx.json.*; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -30,6 +32,8 @@ @ActiveProfiles("test") public class IntegrationTest { + protected final Logger logger = LoggerFactory.getLogger(IntegrationTest.class); + @LocalServerPort int port; @@ -178,6 +182,7 @@ private void getVersionReferencesMetadata(String path) { } private void getFile(String path) { + logger.info(path); var response = restTemplate.getForEntity(apiCall(path), byte[].class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotEmpty(); @@ -202,7 +207,7 @@ public void getVscodeDownloadLink() throws URISyntaxException { var path = "/vscode/gallery/publishers/editorconfig/vsextensions/editorconfig/0.16.6/vspackage"; var response = restTemplate.getForEntity(apiCall(path), String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND); - var expectedPath = "/vscode/asset/editorconfig/editorconfig/0.16.6/Microsoft.VisualStudio.Services.VSIXPackage"; + var expectedPath = "/vscode/asset/EditorConfig/EditorConfig/0.16.6/Microsoft.VisualStudio.Services.VSIXPackage"; assertThat(response.getHeaders().getLocation()).isEqualTo(new URI(apiCall(expectedPath))); } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index a17d49f76..77717fc71 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -31,10 +31,7 @@ import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; -import org.eclipse.openvsx.storage.AzureBlobStorageService; -import org.eclipse.openvsx.storage.AzureDownloadCountService; -import org.eclipse.openvsx.storage.GoogleCloudStorageService; -import org.eclipse.openvsx.storage.StorageUtilService; +import org.eclipse.openvsx.storage.*; import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.VersionAlias; import org.eclipse.openvsx.util.VersionService; @@ -115,8 +112,8 @@ public class RegistryAPITest { @Test public void testPublicNamespace() throws Exception { var namespace = mockNamespace(); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(0L); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(false); mockMvc.perform(get("/api/{namespace}", "foobar")) .andExpect(status().isOk()) @@ -131,8 +128,8 @@ public void testVerifiedNamespace() throws Exception { var namespace = mockNamespace(); var user = new UserData(); user.setLoginName("test_user"); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(1L); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(true); mockMvc.perform(get("/api/{namespace}", "foobar")) .andExpect(status().isOk()) @@ -459,8 +456,7 @@ public void testUnknownExtensionVersionTarget() throws Exception { @Test public void testReadmeUniversalTarget() throws Exception { var resource = mockReadme(); - var extVersion = resource.getExtension(); - Mockito.when(repositories.findExtensionVersion("foo", "bar", "universal", "1.0.0")).thenReturn(extVersion); + Mockito.when(repositories.findFileByType("foo", "bar", "universal", "1.0.0", README)).thenReturn(resource); mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "README")) .andExpect(status().isOk()) @@ -470,8 +466,7 @@ public void testReadmeUniversalTarget() throws Exception { @Test public void testReadmeWindowsTarget() throws Exception { var resource = mockReadme("win32-x64"); - var extVersion = resource.getExtension(); - Mockito.when(repositories.findExtensionVersion("foo", "bar", "win32-x64", "1.0.0")).thenReturn(extVersion); + Mockito.when(repositories.findFileByType("foo", "bar", "win32-x64", "1.0.0", README)).thenReturn(resource); mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}/file/{fileName}", "foo", "bar", "win32-x64", "1.0.0", "README")) .andExpect(status().isOk()) @@ -488,8 +483,7 @@ public void testReadmeUnknownTarget() throws Exception { @Test public void testChangelog() throws Exception { var resource = mockChangelog(); - var extVersion = resource.getExtension(); - Mockito.when(repositories.findExtensionVersion("foo", "bar", "universal", "1.0.0")).thenReturn(extVersion); + Mockito.when(repositories.findFileByType("foo", "bar", "universal", "1.0.0", CHANGELOG)).thenReturn(resource); mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "CHANGELOG")) .andExpect(status().isOk()) @@ -499,8 +493,7 @@ public void testChangelog() throws Exception { @Test public void testLicense() throws Exception { var resource = mockLicense(); - var extVersion = resource.getExtension(); - Mockito.when(repositories.findExtensionVersion("foo", "bar", "universal", "1.0.0")).thenReturn(extVersion); + Mockito.when(repositories.findFileByType("foo", "bar", "universal", "1.0.0", LICENSE)).thenReturn(resource); mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "LICENSE")) .andExpect(status().isOk()) @@ -526,8 +519,8 @@ public void testUnknownFile() throws Exception { @Test public void testLatestFile() throws Exception { var resource = mockLatest(); - var extVersion = resource.getExtension(); - Mockito.when(repositories.findExtensionVersion("foo", "bar", "universal", VersionAlias.LATEST)).thenReturn(extVersion); + Mockito.when(repositories.findFileByType("foo", "bar", "universal", "latest", FileResource.DOWNLOAD)) + .thenReturn(resource); mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "latest", "DOWNLOAD")) .andExpect(status().isOk()) @@ -563,6 +556,8 @@ public void testReviews() throws Exception { public void testSearch() throws Exception { var extVersions = mockSearch(); extVersions.forEach(extVersion -> Mockito.when(repositories.findLatestVersion(extVersion.getExtension(), null, false, true)).thenReturn(extVersion)); + Mockito.when(repositories.findLatestVersions(extVersions.stream().map(ExtensionVersion::getExtension).map(Extension::getId).toList())) + .thenReturn(extVersions); mockMvc.perform(get("/api/-/search?query={query}&size={size}&offset={offset}", "foo", "10", "0")) .andExpect(status().isOk()) @@ -587,6 +582,8 @@ public void testSearchInactive() throws Exception { extension.setActive(false); extension.getVersions().get(0).setActive(false); }); + Mockito.when(repositories.findLatestVersions(extVersionsList.stream().map(ExtensionVersion::getExtension).map(Extension::getId).toList())) + .thenReturn(Collections.emptyList()); mockMvc.perform(get("/api/-/search?query={query}&size={size}&offset={offset}", "foo", "10", "0")) .andExpect(status().isOk()) @@ -626,9 +623,6 @@ public void testGetQueryNamespace() throws Exception { @Test public void testGetQueryUnknownExtension() throws Exception { mockExtensionVersion(); - Mockito.when(repositories.findActiveExtensionVersionsByExtensionName(TargetPlatform.NAME_UNIVERSAL, "baz")) - .thenReturn(Collections.emptyList()); - mockMvc.perform(get("/api/-/query?extensionName={extensionName}", "baz")) .andExpect(status().isOk()) .andExpect(content().json("{ \"extensions\": [] }")); @@ -779,9 +773,6 @@ public void testGetQueryV2Namespace() throws Exception { @Test public void testGetQueryV2UnknownExtension() throws Exception { mockExtensionVersion(); - Mockito.when(repositories.findActiveExtensionVersionsByExtensionName(TargetPlatform.NAME_UNIVERSAL, "baz")) - .thenReturn(Collections.emptyList()); - mockMvc.perform(get("/api/v2/-/query?extensionName={extensionName}", "baz")) .andExpect(status().isOk()) .andExpect(content().json("{ \"extensions\": [] }")); @@ -1290,10 +1281,8 @@ public void testCreateNamespaceInactiveToken() throws Exception { @Test public void testCreateExistingNamespace() throws Exception { mockAccessToken(); - var namespace = new Namespace(); - namespace.setName("foobar"); - Mockito.when(repositories.findNamespace("foobar")) - .thenReturn(namespace); + Mockito.when(repositories.findNamespaceName("foobar")) + .thenReturn("foobar"); mockMvc.perform(post("/api/-/namespace/create?token={token}", "my_token") .contentType(MediaType.APPLICATION_JSON) @@ -1531,8 +1520,11 @@ public void testPublishSameVersionDifferentTargetPlatformPreRelease() throws Exc extVersion.setPreRelease(false); mockForPublish("contributor"); - Mockito.when(repositories.findVersions(eq("1.0.0"), any(Extension.class))) - .thenReturn(Streamable.of(extVersion)); + Mockito.when(repositories.hasSameVersion(any(ExtensionVersion.class))) + .thenAnswer((Answer) invocation -> { + var extensionVersion = invocation.getArgument(0); + return extensionVersion.getVersion().equals(extVersion.getVersion()); + }); var bytes = createExtensionPackage("bar", "1.0.0", null, true, TargetPlatform.NAME_LINUX_X64); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") @@ -1550,8 +1542,11 @@ public void testPublishSameVersionDifferentTargetPlatformStableRelease() throws extVersion.setPreRelease(true); mockForPublish("contributor"); - Mockito.when(repositories.findVersions(eq("1.5.0"), any(Extension.class))) - .thenReturn(Streamable.of(extVersion)); + Mockito.when(repositories.hasSameVersion(any(ExtensionVersion.class))) + .thenAnswer((Answer) invocation -> { + var extensionVersion = invocation.getArgument(0); + return extensionVersion.getVersion().equals(extVersion.getVersion()); + }); var bytes = createExtensionPackage("bar", "1.5.0", null, false, TargetPlatform.NAME_ALPINE_ARM64); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") @@ -1651,12 +1646,8 @@ public void testPostExistingReview() throws Exception { var extension = extVersion.getExtension(); Mockito.when(repositories.findExtension("bar", "foo")) .thenReturn(extension); - var review = new ExtensionReview(); - review.setExtension(extension); - review.setUser(user); - review.setActive(true); - Mockito.when(repositories.findActiveReviews(extension, user)) - .thenReturn(Streamable.of(review)); + Mockito.when(repositories.hasActiveReview(extension, user)) + .thenReturn(true); mockMvc.perform(post("/api/{namespace}/{extension}/review", "foo", "bar") .contentType(MediaType.APPLICATION_JSON) @@ -1843,6 +1834,8 @@ private List mockExtensionVersions(String targetPlatform, List Mockito.when(repositories.findActiveExtensionVersions(Set.of(extension.getId()), null)) .thenReturn(versions); + Mockito.when(repositories.findLatestVersionsIsPreview(Set.of(extension.getId()))) + .thenReturn(Map.of(extension.getId(), versions.get(0).isPreview())); Mockito.when(repositories.findActiveVersionStringsSorted(Set.of(extension.getId()), null)) .thenReturn(versions.stream().collect(Collectors.groupingBy(ev -> ev.getExtension().getId(), Collectors.mapping(ev -> ev.getVersion(), Collectors.toList())))); Mockito.when(repositories.findVersionStringsSorted(extension, targetPlatform, true)) @@ -1880,6 +1873,9 @@ private void mockExtensionVersion() { Mockito.when(repositories.findActiveExtensionVersions(Set.of(extension.getId()), null)) .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findLatestVersionsIsPreview(Set.of(extension.getId()))) + .thenReturn(Map.of(extension.getId(), extVersion.isPreview())); + Mockito.when(repositories.findActiveVersions(any(QueryRequest.class))) .then((Answer>) invocation -> { var request = invocation.getArgument(0, QueryRequest.class); @@ -1939,24 +1935,17 @@ private ExtensionVersion mockExtension(String targetPlatform, boolean withSignat .thenReturn(Streamable.of(extVersion)); Mockito.when(repositories.findActiveExtensions(namespace)) .thenReturn(Streamable.of(extension)); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(0L); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(false); Mockito.when(repositories.countActiveReviews(extension)) .thenReturn(0L); Mockito.when(repositories.findNamespace("foo")) .thenReturn(namespace); Mockito.when(repositories.findExtensions("bar")) .thenReturn(Streamable.of(extension)); - Mockito.when(repositories.findNamespaceByPublicId("1234")) - .thenReturn(namespace); Mockito.when(repositories.findExtensionByPublicId("5678")) .thenReturn(extension); -// Mockito.when(repositories.findTargetPlatformsGroupedByVersion(extension)).thenReturn(); -// Mockito.when(repositories.findVersionsForUrls(extension, targetPlatform, extVersion.getVersion())).thenReturn(List.of(extVersion)); -// Mockito.when(repositories.findLatestVersion(extension, targetPlatform, false, true)).thenReturn(extVersion); -// Mockito.when(repositories.findExtensionTargetPlatforms(extension)).thenReturn(List.of(targetPlatform)); - var download = new FileResource(); download.setExtension(extVersion); download.setType(DOWNLOAD); @@ -2018,11 +2007,7 @@ private FileResource mockReadme(String targetPlatform) { resource.setType(FileResource.README); resource.setContent("Please read me".getBytes()); resource.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(resource)).thenReturn(resource); - Mockito.when(repositories.findFileByName(extVersion, "README")) - .thenReturn(resource); - Mockito.when(repositories.findFileByType(extVersion, FileResource.README)) - .thenReturn(resource); + Mockito.when(entityManager.find(FileResource.class, resource.getId())).thenReturn(resource); return resource; } @@ -2034,11 +2019,7 @@ private FileResource mockChangelog() { resource.setType(FileResource.CHANGELOG); resource.setContent("All notable changes is documented here".getBytes()); resource.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(resource)).thenReturn(resource); - Mockito.when(repositories.findFileByName(extVersion, "CHANGELOG")) - .thenReturn(resource); - Mockito.when(repositories.findFileByType(extVersion, FileResource.CHANGELOG)) - .thenReturn(resource); + Mockito.when(entityManager.find(FileResource.class, resource.getId())).thenReturn(resource); return resource; } @@ -2050,11 +2031,7 @@ private FileResource mockLicense() { resource.setType(FileResource.LICENSE); resource.setContent("I never broke the Law! I am the law!".getBytes()); resource.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(resource)).thenReturn(resource); - Mockito.when(repositories.findFileByName(extVersion, "LICENSE")) - .thenReturn(resource); - Mockito.when(repositories.findFileByType(extVersion, FileResource.LICENSE)) - .thenReturn(resource); + Mockito.when(entityManager.find(FileResource.class, resource.getId())).thenReturn(resource); return resource; } @@ -2066,9 +2043,7 @@ private FileResource mockLatest() { resource.setType(FileResource.DOWNLOAD); resource.setContent("latest download".getBytes()); resource.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(resource)).thenReturn(resource); - Mockito.when(repositories.findFileByType(extVersion, FileResource.DOWNLOAD)) - .thenReturn(resource); + Mockito.when(entityManager.find(FileResource.class, resource.getId())).thenReturn(resource); return resource; } @@ -2117,8 +2092,6 @@ private List mockSearch() { var searchOptions = new ISearchService.Options("foo", null, null, 10, 0, "desc", "relevance", false); Mockito.when(search.search(searchOptions)) .thenReturn(searchHits); - Mockito.when(repositories.findExtensions(List.of(extension.getId()))) - .thenReturn(Streamable.of(extension)); return List.of(extVersion); } @@ -2178,19 +2151,15 @@ private void mockForPublish(String mode) { ownerMem.setRole(NamespaceMembership.ROLE_OWNER); Mockito.when(repositories.findMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(Streamable.of(ownerMem)); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(1L); - Mockito.when(repositories.findMembership(token.getUser(), namespace)) - .thenReturn(ownerMem); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(true); + Mockito.when(repositories.canPublishInNamespace(token.getUser(), namespace)) + .thenReturn(true); Mockito.when(repositories.isVerified(namespace, token.getUser())) .thenReturn(true); } else if (mode.equals("contributor") || mode.equals("sole-contributor") || mode.equals("existing")) { - var contribMem = new NamespaceMembership(); - contribMem.setUser(token.getUser()); - contribMem.setNamespace(namespace); - contribMem.setRole(NamespaceMembership.ROLE_CONTRIBUTOR); - Mockito.when(repositories.findMembership(token.getUser(), namespace)) - .thenReturn(contribMem); + Mockito.when(repositories.canPublishInNamespace(token.getUser(), namespace)) + .thenReturn(true); Mockito.when(repositories.isVerified(namespace, token.getUser())) .thenReturn(true); if (mode.equals("contributor")) { @@ -2219,16 +2188,16 @@ private void mockForPublish(String mode) { ownerMem.setRole(NamespaceMembership.ROLE_OWNER); Mockito.when(repositories.findMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(Streamable.of(ownerMem)); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(1L); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(true); if (mode.equals("privileged")) { token.getUser().setRole(UserData.ROLE_PRIVILEGED); } } else { Mockito.when(repositories.findMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(Streamable.empty()); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(0L); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(false); } Mockito.when(entityManager.merge(any(Extension.class))) @@ -2396,6 +2365,7 @@ StorageUtilService storageUtilService( RepositoryService repositories, GoogleCloudStorageService googleStorage, AzureBlobStorageService azureStorage, + LocalStorageService localStorage, AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, @@ -2407,6 +2377,7 @@ StorageUtilService storageUtilService( googleStorage, azureStorage, azureDownloadCountService, + localStorage, search, cache, entityManager, @@ -2414,6 +2385,11 @@ StorageUtilService storageUtilService( ); } + @Bean + LocalStorageService localStorageService(EntityManager entityManager) { + return new LocalStorageService(entityManager); + } + @Bean ExtensionJsonCacheKeyGenerator extensionJsonCacheKeyGenerator() { return new ExtensionJsonCacheKeyGenerator(); } diff --git a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java index 9736af14d..7988c64a9 100644 --- a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java @@ -18,6 +18,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -280,6 +281,8 @@ public void testAddNamespaceMember() throws Exception { namespace.setName("foobar"); Mockito.when(repositories.findNamespace("foobar")) .thenReturn(namespace); + Mockito.when(repositories.isNamespaceOwner(userData1, namespace)) + .thenReturn(true); var membership = new NamespaceMembership(); membership.setUser(userData1); membership.setNamespace(namespace); @@ -316,6 +319,8 @@ public void testChangeNamespaceMember() throws Exception { namespace.setName("foobar"); Mockito.when(repositories.findNamespace("foobar")) .thenReturn(namespace); + Mockito.when(repositories.isNamespaceOwner(userData1, namespace)) + .thenReturn(true); var membership1 = new NamespaceMembership(); membership1.setUser(userData1); membership1.setNamespace(namespace); @@ -348,6 +353,8 @@ public void testRemoveNamespaceMember() throws Exception { namespace.setName("foobar"); Mockito.when(repositories.findNamespace("foobar")) .thenReturn(namespace); + Mockito.when(repositories.isNamespaceOwner(userData1, namespace)) + .thenReturn(true); var membership1 = new NamespaceMembership(); membership1.setUser(userData1); membership1.setNamespace(namespace); @@ -402,6 +409,8 @@ public void testChangeNamespaceMemberSameRole() throws Exception { namespace.setName("foobar"); Mockito.when(repositories.findNamespace("foobar")) .thenReturn(namespace); + Mockito.when(repositories.isNamespaceOwner(userData1, namespace)) + .thenReturn(true); var membership1 = new NamespaceMembership(); membership1.setUser(userData1); membership1.setNamespace(namespace); @@ -465,8 +474,8 @@ private void mockAccessTokens() { token3.setDescription("This is token 3"); token3.setCreatedTimestamp(LocalDateTime.parse("2000-01-01T10:00")); token3.setActive(true); - Mockito.when(repositories.findAccessTokens(userData)) - .thenReturn(Streamable.of(token1, token2, token3)); + Mockito.when(repositories.findActiveAccessTokens(userData)) + .thenReturn(Streamable.of(token1, token3)); } private String accessTokenJson(Consumer content) throws JsonProcessingException { @@ -485,6 +494,7 @@ private void mockOwnMemberships() { var userData = mockUserData(); var namespace1 = new Namespace(); namespace1.setName("foo"); + namespace1.setExtensions(Collections.emptyList()); Mockito.when(repositories.findActiveExtensions(namespace1)).thenReturn(Streamable.empty()); var membership1 = new NamespaceMembership(); membership1.setUser(userData); @@ -492,15 +502,14 @@ private void mockOwnMemberships() { membership1.setRole(NamespaceMembership.ROLE_OWNER); var namespace2 = new Namespace(); namespace2.setName("bar"); + namespace2.setExtensions(Collections.emptyList()); Mockito.when(repositories.findActiveExtensions(namespace2)).thenReturn(Streamable.empty()); var membership2 = new NamespaceMembership(); membership2.setUser(userData); membership2.setNamespace(namespace2); membership2.setRole(NamespaceMembership.ROLE_OWNER); - Mockito.when(repositories.findMemberships(userData, NamespaceMembership.ROLE_OWNER)) + Mockito.when(repositories.findMemberships(userData)) .thenReturn(Streamable.of(membership1, membership2)); - Mockito.when(repositories.findMemberships(userData, NamespaceMembership.ROLE_CONTRIBUTOR)) - .thenReturn(Streamable.empty()); } private String namespacesJson(Consumer> content) throws JsonProcessingException { @@ -513,22 +522,21 @@ private void mockNamespaceMemberships(String userRole) { var userData = mockUserData(); var namespace = new Namespace(); namespace.setName("foobar"); - Mockito.when(repositories.findNamespace("foobar")) - .thenReturn(namespace); + var membership1 = new NamespaceMembership(); membership1.setUser(userData); membership1.setNamespace(namespace); membership1.setRole(userRole); - Mockito.when(repositories.findMembership(userData, namespace)) - .thenReturn(membership1); + var userData2 = new UserData(); userData2.setLoginName("other_user"); var membership2 = new NamespaceMembership(); membership2.setUser(userData2); membership2.setNamespace(namespace); membership2.setRole(NamespaceMembership.ROLE_CONTRIBUTOR); - Mockito.when(repositories.findMemberships(namespace)) - .thenReturn(Streamable.of(membership1, membership2)); + + Mockito.when(repositories.findMembershipsForOwner(userData, "foobar")) + .thenReturn(userRole.equals(NamespaceMembership.ROLE_OWNER) ? List.of(membership1, membership2) : Collections.emptyList()); } private String membershipsJson(Consumer content) throws JsonProcessingException { diff --git a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java index c6c03d017..047e362aa 100644 --- a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java @@ -9,28 +9,13 @@ ********************************************************************************/ package org.eclipse.openvsx.adapter; -import static org.eclipse.openvsx.entities.FileResource.*; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - -import io.micrometer.observation.ObservationRegistry; -import jakarta.persistence.EntityManager; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import com.google.common.io.CharStreams; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import jakarta.persistence.EntityManager; import org.eclipse.openvsx.ExtensionValidator; import org.eclipse.openvsx.MockTransactionTemplate; import org.eclipse.openvsx.UserService; @@ -46,10 +31,7 @@ import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; -import org.eclipse.openvsx.storage.AzureBlobStorageService; -import org.eclipse.openvsx.storage.AzureDownloadCountService; -import org.eclipse.openvsx.storage.GoogleCloudStorageService; -import org.eclipse.openvsx.storage.StorageUtilService; +import org.eclipse.openvsx.storage.*; import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.VersionService; import org.junit.jupiter.api.Test; @@ -70,6 +52,19 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.support.TransactionTemplate; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static org.eclipse.openvsx.entities.FileResource.*; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + @WebMvcTest(VSCodeAPI.class) @AutoConfigureWebClient @MockBean({ @@ -249,24 +244,14 @@ public void testAssetMacOSX() throws Exception { @Test public void testAssetNotFound() throws Exception { - var extVersion = mockExtensionVersion(); - Mockito.when(repositories.findFileByType(extVersion, FileResource.MANIFEST)) + mockExtensionVersion(); + Mockito.when(repositories.findFileByType("redhat", "vscode-yaml", "universal", "0.5.2", FileResource.MANIFEST)) .thenReturn(null); mockMvc.perform(get("/vscode/asset/{namespace}/{extensionName}/{version}/{assetType}", "redhat", "vscode-yaml", "0.5.2", "Microsoft.VisualStudio.Code.Manifest")) .andExpect(status().isNotFound()); } - @Test - public void testGetItem() throws Exception { - var extension = mockExtension(); - extension.setActive(true); - Mockito.when(repositories.findExtension("vscode-yaml", "redhat")).thenReturn(extension); - mockMvc.perform(get("/vscode/item?itemName={itemName}", "redhat.vscode-yaml")) - .andExpect(status().isFound()) - .andExpect(header().string("Location", "/extension/redhat/vscode-yaml")); - } - @Test public void testWebResourceAsset() throws Exception { mockExtensionVersion(); @@ -292,6 +277,16 @@ public void testAssetExcludeBuiltInExtensions() throws Exception { .andExpect(content().string("Built-in extension namespace 'vscode' not allowed")); } + @Test + public void testGetItem() throws Exception { + var extension = mockExtension(); + extension.setActive(true); + Mockito.when(repositories.findActiveExtension("vscode-yaml", "redhat")).thenReturn(extension); + mockMvc.perform(get("/vscode/item?itemName={itemName}", "redhat.vscode-yaml")) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "/extension/redhat/vscode-yaml")); + } + @Test public void testGetItemExcludeBuiltInExtensions() throws Exception { mockMvc.perform(get("/vscode/item?itemName={itemName}", "vscode.vscode-yaml")) @@ -323,8 +318,8 @@ public void testBrowseNotFound() throws Exception { extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); Mockito.when(repositories.findResourceFileResources(1L, "extension/img")) .thenReturn(Collections.emptyList()); @@ -357,8 +352,8 @@ public void testBrowseTopDir() throws Exception { extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, "".getBytes(StandardCharsets.UTF_8)); var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8)); @@ -393,8 +388,8 @@ public void testBrowseVsixManifest() throws Exception { extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var content = "".getBytes(StandardCharsets.UTF_8); var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, content); @@ -417,22 +412,17 @@ public void testBrowseVsixManifestUniversal() throws Exception { extension.setId(0L); extension.setName(extensionName); extension.setNamespace(namespace); - var targetPlatforms = List.of(TargetPlatform.NAME_UNIVERSAL, TargetPlatform.NAME_WIN32_X64, TargetPlatform.NAME_LINUX_X64); - var extVersions = new ArrayList(targetPlatforms.size()); - for(var i = 0; i < targetPlatforms.size(); i++) { - var extVersion = new ExtensionVersion(); - extVersion.setId(i + 1); - extVersion.setVersion(version); - extVersion.setTargetPlatform(targetPlatforms.get(i)); - extVersion.setExtension(extension); - extVersions.add(extVersion); - } + var extVersion = new ExtensionVersion(); + extVersion.setId(1); + extVersion.setVersion(version); + extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); + extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(extVersions); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var content = "".getBytes(StandardCharsets.UTF_8); - var vsixResource = mockFileResource(15, extVersions.get(0), "extension.vsixmanifest", RESOURCE, STORAGE_DB, content); + var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, content); Mockito.when(repositories.findResourceFileResources(1L, "extension.vsixmanifest")) .thenReturn(List.of(vsixResource)); @@ -452,22 +442,17 @@ public void testBrowseVsixManifestWindows() throws Exception { extension.setId(0L); extension.setName(extensionName); extension.setNamespace(namespace); - var targetPlatforms = List.of(TargetPlatform.NAME_DARWIN_X64, TargetPlatform.NAME_LINUX_X64, TargetPlatform.NAME_WIN32_X64); - var extVersions = new ArrayList(targetPlatforms.size()); - for(var i = 0; i < targetPlatforms.size(); i++) { - var extVersion = new ExtensionVersion(); - extVersion.setId(i + 2); - extVersion.setVersion(version); - extVersion.setTargetPlatform(targetPlatforms.get(i)); - extVersion.setExtension(extension); - extVersions.add(extVersion); - } + var extVersion = new ExtensionVersion(); + extVersion.setId(2); + extVersion.setVersion(version); + extVersion.setTargetPlatform(TargetPlatform.NAME_DARWIN_X64); + extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(extVersions); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var content = "".getBytes(StandardCharsets.UTF_8); - var vsixResource = mockFileResource(15, extVersions.get(0), "extension.vsixmanifest", RESOURCE, STORAGE_DB, content); + var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, content); Mockito.when(repositories.findResourceFileResources(2L, "extension.vsixmanifest")) .thenReturn(List.of(vsixResource)); @@ -493,8 +478,8 @@ public void testBrowseExtensionDir() throws Exception { extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8)); var readmeResource = mockFileResource(17, extVersion, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8)); @@ -532,8 +517,8 @@ public void testBrowsePackageJson() throws Exception { extVersion.setVersion(version); extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var content = "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8); var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, content); @@ -561,8 +546,8 @@ public void testBrowseImagesDir() throws Exception { extVersion.setVersion(version); extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var content = "ICON128".getBytes(StandardCharsets.UTF_8); var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content); @@ -591,8 +576,8 @@ public void testBrowseIcon() throws Exception { extVersion.setVersion(version); extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); - Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName)) - .thenReturn(List.of(extVersion)); + Mockito.when(repositories.findActiveExtensionVersion(version, extensionName, namespaceName)) + .thenReturn(extVersion); var content = "ICON128".getBytes(StandardCharsets.UTF_8); var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content); @@ -792,6 +777,7 @@ private ExtensionVersion mockExtensionVersion(String targetPlatform) throws Json extension.setNamespace(namespace); var extVersion = new ExtensionVersion(); extension.getVersions().add(extVersion); + extVersion.setExtension(extension); extVersion.setTargetPlatform(targetPlatform); extVersion.setVersion("0.5.2"); extVersion.setPreRelease(true); @@ -811,17 +797,18 @@ private ExtensionVersion mockExtensionVersion(String targetPlatform) throws Json .thenReturn(extVersion); Mockito.when(repositories.findVersions(extension)) .thenReturn(Streamable.of(extVersion)); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(0L); var extensionFile = new FileResource(); + extensionFile.setId(10L); extensionFile.setExtension(extVersion); extensionFile.setName("redhat.vscode-yaml-0.5.2.vsix"); extensionFile.setType(FileResource.DOWNLOAD); extensionFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(extensionFile)).thenReturn(extensionFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.DOWNLOAD)) + Mockito.when(entityManager.find(FileResource.class, extensionFile.getId())).thenReturn(extensionFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), DOWNLOAD)) .thenReturn(extensionFile); + var manifestFile = new FileResource(); + manifestFile.setId(11L); manifestFile.setExtension(extVersion); manifestFile.setName("package.json"); manifestFile.setType(FileResource.MANIFEST); @@ -831,56 +818,56 @@ private ExtensionVersion mockExtensionVersion(String targetPlatform) throws Json manifestContent.put("target", targetPlatform); manifestFile.setContent(new ObjectMapper().writeValueAsBytes(manifestContent)); manifestFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(manifestFile)).thenReturn(manifestFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.MANIFEST)) + Mockito.when(entityManager.find(FileResource.class, manifestFile.getId())).thenReturn(manifestFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), FileResource.MANIFEST)) .thenReturn(manifestFile); var readmeFile = new FileResource(); readmeFile.setExtension(extVersion); readmeFile.setName("README.md"); readmeFile.setType(FileResource.README); readmeFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(readmeFile)).thenReturn(readmeFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.README)) + Mockito.when(entityManager.find(FileResource.class, readmeFile.getId())).thenReturn(readmeFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), README)) .thenReturn(readmeFile); var changelogFile = new FileResource(); changelogFile.setExtension(extVersion); changelogFile.setName("CHANGELOG.md"); changelogFile.setType(FileResource.CHANGELOG); changelogFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(changelogFile)).thenReturn(changelogFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.CHANGELOG)) + Mockito.when(entityManager.find(FileResource.class, changelogFile.getId())).thenReturn(changelogFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), CHANGELOG)) .thenReturn(changelogFile); var licenseFile = new FileResource(); licenseFile.setExtension(extVersion); licenseFile.setName("LICENSE.txt"); licenseFile.setType(FileResource.LICENSE); licenseFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(licenseFile)).thenReturn(licenseFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.LICENSE)) + Mockito.when(entityManager.find(FileResource.class, licenseFile.getId())).thenReturn(licenseFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), LICENSE)) .thenReturn(licenseFile); var iconFile = new FileResource(); iconFile.setExtension(extVersion); iconFile.setName("icon128.png"); iconFile.setType(FileResource.ICON); iconFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(iconFile)).thenReturn(iconFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.ICON)) + Mockito.when(entityManager.find(FileResource.class, iconFile.getId())).thenReturn(iconFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), ICON)) .thenReturn(iconFile); var vsixManifestFile = new FileResource(); vsixManifestFile.setExtension(extVersion); vsixManifestFile.setName("extension.vsixmanifest"); vsixManifestFile.setType(VSIXMANIFEST); vsixManifestFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(vsixManifestFile)).thenReturn(vsixManifestFile); - Mockito.when(repositories.findFileByType(extVersion, VSIXMANIFEST)) + Mockito.when(entityManager.find(FileResource.class, vsixManifestFile.getId())).thenReturn(vsixManifestFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), VSIXMANIFEST)) .thenReturn(vsixManifestFile); var signatureFile = new FileResource(); signatureFile.setExtension(extVersion); signatureFile.setName("redhat.vscode-yaml-0.5.2.sigzip"); signatureFile.setType(FileResource.DOWNLOAD_SIG); signatureFile.setStorageType(FileResource.STORAGE_DB); - Mockito.when(entityManager.merge(signatureFile)).thenReturn(signatureFile); - Mockito.when(repositories.findFileByType(extVersion, FileResource.DOWNLOAD_SIG)) + Mockito.when(entityManager.find(FileResource.class, signatureFile.getId())).thenReturn(signatureFile); + Mockito.when(repositories.findFileByType(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), DOWNLOAD_SIG)) .thenReturn(signatureFile); var webResourceFile = new FileResource(); webResourceFile.setExtension(extVersion); @@ -888,8 +875,8 @@ private ExtensionVersion mockExtensionVersion(String targetPlatform) throws Json webResourceFile.setType(FileResource.RESOURCE); webResourceFile.setStorageType(STORAGE_DB); webResourceFile.setContent("logo.png".getBytes()); - Mockito.when(entityManager.merge(webResourceFile)).thenReturn(webResourceFile); - Mockito.when(repositories.findFileByTypeAndName(extVersion, FileResource.RESOURCE, "extension/img/logo.png")) + Mockito.when(entityManager.find(FileResource.class, webResourceFile.getId())).thenReturn(webResourceFile); + Mockito.when(repositories.findFileByTypeAndName(namespace.getName(), extension.getName(), targetPlatform, extVersion.getVersion(), FileResource.RESOURCE, "extension/img/logo.png")) .thenReturn(webResourceFile); Mockito.when(repositories.findFilesByType(anyCollection(), anyCollection())).thenAnswer(invocation -> { Collection extVersions = invocation.getArgument(0); @@ -970,6 +957,7 @@ StorageUtilService storageUtilService( RepositoryService repositories, GoogleCloudStorageService googleStorage, AzureBlobStorageService azureStorage, + LocalStorageService localStorage, AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, @@ -981,6 +969,7 @@ StorageUtilService storageUtilService( googleStorage, azureStorage, azureDownloadCountService, + localStorage, search, cache, entityManager, @@ -988,6 +977,11 @@ StorageUtilService storageUtilService( ); } + @Bean + LocalStorageService localStorage(EntityManager entityManager) { + return new LocalStorageService(entityManager); + } + @Bean VersionService getVersionService() { return new VersionService(); diff --git a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java index 120f41036..5f6f1b5db 100644 --- a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java @@ -27,10 +27,7 @@ import org.eclipse.openvsx.security.OAuth2UserServices; import org.eclipse.openvsx.security.SecurityConfig; import org.eclipse.openvsx.security.TokenService; -import org.eclipse.openvsx.storage.AzureBlobStorageService; -import org.eclipse.openvsx.storage.AzureDownloadCountService; -import org.eclipse.openvsx.storage.GoogleCloudStorageService; -import org.eclipse.openvsx.storage.StorageUtilService; +import org.eclipse.openvsx.storage.*; import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.VersionService; import org.jobrunr.scheduling.JobRequestScheduler; @@ -385,8 +382,8 @@ public void testGetNamespaceMembers() throws Exception { membership1.setNamespace(namespace); membership1.setUser(user); membership1.setRole(NamespaceMembership.ROLE_OWNER); - Mockito.when(repositories.findMemberships(namespace)) - .thenReturn(Streamable.of(membership1)); + Mockito.when(repositories.findMemberships(namespace.getName())) + .thenReturn(List.of(membership1)); mockMvc.perform(get("/admin/namespace/{namespace}/members", "foobar") .with(user("admin_user").authorities(new SimpleGrantedAuthority(("ROLE_ADMIN")))) @@ -438,10 +435,8 @@ public void testCreateNamespace() throws Exception { @Test public void testCreateExistingNamespace() throws Exception { mockAdminUser(); - var namespace = new Namespace(); - namespace.setName("foobar"); - Mockito.when(repositories.findNamespace("foobar")) - .thenReturn(namespace); + Mockito.when(repositories.findNamespaceName("foobar")) + .thenReturn("foobar"); mockMvc.perform(post("/admin/create-namespace") .contentType(MediaType.APPLICATION_JSON) @@ -534,7 +529,7 @@ public void testRevokePublisherAgreement() throws Exception { Mockito.when(repositories.findAccessTokens(user)) .thenReturn(Streamable.of(token)); versions.forEach(v -> v.setPublishedWith(token)); - Mockito.when(repositories.findVersionsByAccessToken(token, true)) + Mockito.when(repositories.findVersionsByUser(user, true)) .thenReturn(Streamable.of(versions)); mockMvc.perform(post("/admin/publisher/{provider}/{loginName}/revoke", "github", "test") @@ -1030,7 +1025,7 @@ private PersonalAccessToken mockAdminToken() { token.setActive(true); token.setValue(tokenValue); token.setUser(user); - Mockito.when(repositories.findAccessToken(tokenValue)).thenReturn(token); + Mockito.when(repositories.isAdminToken(tokenValue)).thenReturn(true); return token; } @@ -1044,7 +1039,7 @@ private PersonalAccessToken mockNonAdminToken() { token.setActive(true); token.setValue(tokenValue); token.setUser(user); - Mockito.when(repositories.findAccessToken(tokenValue)).thenReturn(token); + Mockito.when(repositories.isAdminToken(tokenValue)).thenReturn(false); return token; } @@ -1073,8 +1068,8 @@ private Namespace mockNamespace() { .thenReturn(namespace); Mockito.when(repositories.findActiveExtensions(namespace)) .thenReturn(Streamable.empty()); - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(0l); + Mockito.when(repositories.hasMemberships(namespace, NamespaceMembership.ROLE_OWNER)) + .thenReturn(false); return namespace; } @@ -1120,7 +1115,7 @@ private List mockExtension(int numberOfVersions, int numberOfB extension.getVersions().addAll(versions); Mockito.when(repositories.countVersions(extension)).thenReturn(numberOfVersions); - Mockito.when(repositories.findLatestVersion(extension, null, false, false)) + Mockito.when(repositories.findLatestVersion(namespace.getName(), extension.getName(), null, false, false)) .thenReturn(versions.get(numberOfVersions - 1)); Mockito.when(repositories.findVersions(extension)) .thenReturn(Streamable.of(versions)); @@ -1306,6 +1301,7 @@ StorageUtilService storageUtilService( RepositoryService repositories, GoogleCloudStorageService googleStorage, AzureBlobStorageService azureStorage, + LocalStorageService localStorage, AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, @@ -1317,6 +1313,7 @@ StorageUtilService storageUtilService( googleStorage, azureStorage, azureDownloadCountService, + localStorage, search, cache, entityManager, @@ -1324,6 +1321,11 @@ StorageUtilService storageUtilService( ); } + @Bean + LocalStorageService localStorage(EntityManager entityManager) { + return new LocalStorageService(entityManager); + } + @Bean VersionService versionService() { return new VersionService(); diff --git a/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java b/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java index 8018061cc..0ad846c22 100644 --- a/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java @@ -34,10 +34,7 @@ import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; import org.eclipse.openvsx.security.TokenService; -import org.eclipse.openvsx.storage.AzureBlobStorageService; -import org.eclipse.openvsx.storage.AzureDownloadCountService; -import org.eclipse.openvsx.storage.GoogleCloudStorageService; -import org.eclipse.openvsx.storage.StorageUtilService; +import org.eclipse.openvsx.storage.*; import org.eclipse.openvsx.util.ErrorResultException; import org.eclipse.openvsx.util.TargetPlatform; import org.junit.jupiter.api.BeforeEach; @@ -165,7 +162,7 @@ public void testSignPublisherAgreement() throws Exception { var user = mockUser(); Mockito.when(restTemplate.postForEntity(any(String.class), any(), eq(String.class))) .thenReturn(mockAgreementResponse()); - Mockito.when(repositories.findAccessTokens(user)) + Mockito.when(repositories.findVersionsByUser(user, false)) .thenReturn(Streamable.empty()); var agreement = eclipse.signPublisherAgreement(user); @@ -181,11 +178,6 @@ public void testSignPublisherAgreementReactivateExtension() throws Exception { var user = mockUser(); Mockito.when(restTemplate.postForEntity(any(String.class), any(), eq(String.class))) .thenReturn(mockAgreementResponse()); - var accessToken = new PersonalAccessToken(); - accessToken.setUser(user); - accessToken.setActive(true); - Mockito.when(repositories.findAccessTokens(user)) - .thenReturn(Streamable.of(accessToken)); var namespace = new Namespace(); namespace.setName("foo"); var extension = new Extension(); @@ -196,7 +188,7 @@ public void testSignPublisherAgreementReactivateExtension() throws Exception { extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVersion.setExtension(extension); extension.getVersions().add(extVersion); - Mockito.when(repositories.findVersionsByAccessToken(accessToken, false)) + Mockito.when(repositories.findVersionsByUser(user, false)) .thenReturn(Streamable.of(extVersion)); var agreement = eclipse.signPublisherAgreement(user); @@ -315,6 +307,7 @@ StorageUtilService storageUtilService( RepositoryService repositories, GoogleCloudStorageService googleStorage, AzureBlobStorageService azureStorage, + LocalStorageService localStorage, AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, @@ -326,6 +319,7 @@ StorageUtilService storageUtilService( googleStorage, azureStorage, azureDownloadCountService, + localStorage, search, cache, entityManager, @@ -333,6 +327,11 @@ StorageUtilService storageUtilService( ); } + @Bean + LocalStorageService localStorageService(EntityManager entityManager) { + return new LocalStorageService(entityManager); + } + @Bean LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); 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 8707aa1dd..16144ae73 100644 --- a/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java +++ b/server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java @@ -9,6 +9,8 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.QueryRequest; import org.junit.jupiter.api.Test; @@ -19,13 +21,10 @@ import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; import java.lang.reflect.Modifier; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; @@ -63,6 +62,7 @@ void testExecuteQueries() { var extVersion = new ExtensionVersion(); extVersion.setVersion("3.1.2-rc1+armhf"); extVersion.setTargetPlatform("targetPlatform"); + extVersion.setExtension(extension); var personalAccessToken = new PersonalAccessToken(); var keyPair = new SignatureKeyPair(); keyPair.setPrivateKey(new byte[0]); @@ -87,8 +87,7 @@ void testExecuteQueries() { () -> repositories.countActiveExtensionsGroupedByExtensionReviewRating(), () -> repositories.countActiveReviews(null), () -> repositories.countExtensions(), - () -> repositories.countExtensions("name", "namespace"), - () -> repositories.countMemberships(namespace, "role"), + () -> repositories.hasMemberships(namespace, "role"), () -> repositories.isVerified(namespace, userData), () -> repositories.countNamespaces(), () -> repositories.countPublishersThatClaimedNamespaceOwnership(), @@ -106,7 +105,6 @@ void testExecuteQueries() { () -> repositories.findAllPersistedLogs(), () -> repositories.findAllReviews(extension), () -> repositories.findAllSucceededAzureDownloadCountProcessedItemsByNameIn(STRING_LIST), - () -> repositories.findAllUsers(), () -> repositories.findBundledExtensionsReference(extension), () -> repositories.findDependenciesReference(extension), () -> repositories.findDownloadsByStorageTypeAndName("storageType", STRING_LIST), @@ -115,27 +113,22 @@ void testExecuteQueries() { () -> repositories.findExtensionByPublicId("publicId"), () -> repositories.findExtensions(namespace), () -> repositories.findExtensions("name"), - () -> repositories.findFileByName(extVersion, "name"), () -> repositories.findFileByType(extVersion, "type"), - () -> repositories.findFileByTypeAndName(extVersion, "type", "name"), () -> repositories.findFiles(extVersion), () -> repositories.findFilesByStorageType("storageType"), () -> repositories.findMembership(userData, namespace), () -> repositories.findMemberships(namespace), () -> repositories.findMemberships(namespace, "role"), - () -> repositories.findMemberships(userData, "role"), () -> repositories.findNamespace("name"), - () -> repositories.findNamespaceByPublicId("publicId"), () -> repositories.findOrphanNamespaces(), () -> repositories.findPersistedLogsAfter(NOW), () -> repositories.findTargetPlatformVersions("version", "extensionName", "namespaceName"), () -> repositories.findUserByLoginName("provider", "loginName"), - () -> repositories.findUsersByLoginNameStartingWith("loginNameStart"), + () -> repositories.findUsersByLoginNameStartingWith("loginNameStart", 1), () -> repositories.findVersion("version", "targetPlatform", extension), () -> repositories.findVersion("version", "targetPlatform", "extensionName", "namespace"), () -> repositories.findVersions(extension), () -> repositories.findVersions("version", extension), - () -> repositories.findVersions(userData), () -> repositories.findVersionsByAccessToken(personalAccessToken, true), () -> repositories.getMaxExtensionDownloadCount(), () -> repositories.getOldestExtensionTimestamp(), @@ -157,16 +150,12 @@ void testExecuteQueries() { () -> repositories.topNamespaceExtensions(1), () -> repositories.topNamespaceExtensionVersions(1), () -> repositories.findFileResourcesByExtensionVersionIdAndType(LONG_LIST, STRING_LIST), - () -> repositories.findActiveExtensionVersionsByVersion("version", "extensionName", "namespaceName"), () -> repositories.findResourceFileResources(1L, "prefix"), () -> repositories.findActiveExtensionVersions(LONG_LIST, "targetPlatform"), () -> repositories.findActiveExtension("name", "namespaceName"), () -> repositories.findActiveExtensionsById(LONG_LIST), () -> repositories.findActiveExtensionsByPublicId(STRING_LIST, "namespaceName"), () -> repositories.findNamespaceMemberships(LONG_LIST), - () -> repositories.findActiveExtensionVersionsByNamespaceName("targetPlatform", "namespaceName"), - () -> repositories.findActiveExtensionVersionsByExtensionName("targetPlatform", "extensionName", "namespaceName"), - () -> repositories.findActiveExtensionVersionsByExtensionName("targetPlatform", "extensionName"), () -> repositories.findAllNotMatchingByExtensionId(STRING_LIST), () -> repositories.getAverageReviewRating(null), () -> repositories.getAverageReviewRating(), @@ -185,13 +174,12 @@ void testExecuteQueries() { () -> repositories.findVersionStringsSorted(extension, "targetPlatform", true), () -> repositories.findActiveVersions(queryRequest), () -> repositories.findActiveVersionStringsSorted(LONG_LIST,"targetPlatform"), - () -> repositories.findActiveVersionReferencesSorted(List.of(extension)), + () -> repositories.findActiveVersionReferencesSorted(List.of(1L)), () -> repositories.findAllPublicIds(), () -> repositories.findPublicId("namespaceName", "extensionName"), () -> repositories.findPublicId("namespaceName.extensionName"), () -> repositories.findNamespacePublicId("namespaceName.extensionName"), () -> repositories.updateExtensionPublicIds(Collections.emptyMap()), - () -> repositories.updateExtensionPublicId(1L, "namespaceName.extensionName"), () -> repositories.updateNamespacePublicIds(Collections.emptyMap()), () -> repositories.extensionPublicIdExists("namespaceName.extensionName"), () -> repositories.namespacePublicIdExists("namespaceName.extensionName"), @@ -204,7 +192,37 @@ void testExecuteQueries() { () -> repositories.findLatestVersions(namespace), () -> repositories.findLatestVersions(userData), () -> repositories.findExtensionTargetPlatforms(extension), - () -> repositories.deactivateKeyPairs() + () -> repositories.isNamespaceOwner(userData, namespace), + () -> repositories.findMembershipsForOwner(userData,"namespaceName"), + () -> repositories.findNamespaceName("namespaceName"), + () -> repositories.findMemberships("namespaceName"), + () -> repositories.findActiveExtensionNames(namespace), + () -> repositories.namespaceExists("namespaceName"), + () -> repositories.findFileByType("namespaceName", "extensionName", "targetPlatform", "version", "type"), + () -> repositories.findFileByName("namespaceName", "extensionName", "targetPlatform", "version", "name"), + () -> repositories.findVersionsByUser(userData, false), + () -> repositories.deleteFiles(extVersion), + () -> repositories.findExtensionTargetPlatforms(extension), + () -> repositories.deactivateKeyPairs(), + () -> repositories.findActiveExtensionVersion("version", "extensionName", "namespaceName"), + () -> repositories.findActiveAccessTokens(userData), + () -> repositories.isAdminToken("tokenValue"), + () -> repositories.findFileByTypeAndName("namespaceName", "extensionName", "targetPlatform", "version", "type", "name"), + () -> repositories.findLatestVersions(List.of(1L)), + () -> repositories.hasSameVersion(extVersion), + () -> repositories.hasActiveReview(extension, userData), + () -> repositories.findLatestVersionsIsPreview(List.of(1L)), + () -> repositories.findAccessToken(userData, "description"), + () -> repositories.findMemberships(userData), + () -> repositories.canPublishInNamespace(userData, namespace), + () -> repositories.findLatestVersion("namespaceName", "extensionName", "targetPlatform", false, false), + () -> repositories.hasMembership(userData, namespace), + () -> repositories.findFirstUnresolvedDependency(List.of(new String[]{"namespaceName", "extensionName"})), + () -> repositories.findAllAccessTokens(), + () -> repositories.hasAccessToken("tokenValue"), + () -> repositories.findSignatureKeyPairPublicId("namespaceName", "extensionName", "targetPlatform", "version"), + () -> repositories.findFirstMembership("namespaceName"), + () -> repositories.findActiveExtensionsForUrls(namespace) ); // check that we did not miss anything diff --git a/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java b/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java index 4d66f65af..c48c662ca 100644 --- a/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java @@ -314,9 +314,6 @@ private Extension mockExtension(String name, double averageRating, long ratingCo var namespace = new Namespace(); namespace.setName(namespaceName); extension.setNamespace(namespace); - var isUnverified = false; - Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) - .thenReturn(isUnverified ? 0l : 1l); var extVer = new ExtensionVersion(); extVer.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extVer.setCategories(categories);