From 7b6714b840d2ac40b4d8be1c0a2f97ebc9045804 Mon Sep 17 00:00:00 2001 From: amvanbaren Date: Fri, 7 Jan 2022 18:09:05 +0100 Subject: [PATCH] Implement target platform --- cli/package.json | 16 +- cli/src/get.ts | 6 +- cli/src/main.ts | 14 +- cli/src/publish.ts | 34 +- cli/src/registry.ts | 7 +- cli/yarn.lock | 468 ++++++++++-------- .../java/org/eclipse/openvsx/AdminAPI.java | 19 +- .../org/eclipse/openvsx/AdminService.java | 42 +- .../eclipse/openvsx/ExtensionProcessor.java | 5 + .../org/eclipse/openvsx/ExtensionService.java | 89 ++-- .../eclipse/openvsx/ExtensionValidator.java | 16 +- .../eclipse/openvsx/IExtensionRegistry.java | 7 +- .../eclipse/openvsx/LocalRegistryService.java | 100 ++-- .../java/org/eclipse/openvsx/RegistryAPI.java | 149 +++++- .../openvsx/UpstreamRegistryService.java | 15 +- .../openvsx/adapter/ExtensionQueryResult.java | 5 +- .../openvsx/adapter/VSCodeAdapter.java | 36 +- .../org/eclipse/openvsx/dto/ExtensionDTO.java | 6 +- .../openvsx/dto/ExtensionVersionDTO.java | 13 +- .../eclipse/PublisherComplianceChecker.java | 7 +- .../eclipse/openvsx/entities/Extension.java | 49 +- .../entities/ExtensionTargetPlatform.java | 107 ++++ .../openvsx/entities/ExtensionVersion.java | 21 +- .../eclipse/openvsx/json/ExtensionJson.java | 8 + .../eclipse/openvsx/json/QueryParamJson.java | 4 +- .../AdminStatisticCalculationsRepository.java | 9 +- .../repositories/ExtensionDTORepository.java | 21 +- .../ExtensionTargetPlatformRepository.java | 9 + .../ExtensionVersionDTORepository.java | 57 ++- .../ExtensionVersionRepository.java | 21 +- .../repositories/RepositoryService.java | 74 +-- .../openvsx/search/DatabaseSearchService.java | 49 +- .../openvsx/search/ElasticSearchService.java | 6 + .../openvsx/search/ExtensionSearch.java | 3 +- .../openvsx/search/ISearchService.java | 12 +- .../openvsx/search/RelevanceService.java | 15 +- .../storage/AzureBlobStorageService.java | 15 +- .../storage/GoogleCloudStorageService.java | 20 +- .../openvsx/storage/StorageUtilService.java | 21 +- .../eclipse/openvsx/util/SemanticVersion.java | 4 + .../openvsx/util/TargetPlatformValidator.java | 22 + .../openvsx/web/SitemapController.java | 20 +- .../org/eclipse/openvsx/jooq/Indexes.java | 4 - .../org/eclipse/openvsx/jooq/Keys.java | 13 +- .../org/eclipse/openvsx/jooq/Public.java | 7 + .../org/eclipse/openvsx/jooq/Tables.java | 6 + .../eclipse/openvsx/jooq/tables/Download.java | 13 +- .../openvsx/jooq/tables/Extension.java | 30 +- .../jooq/tables/ExtensionTargetPlatform.java | 173 +++++++ .../openvsx/jooq/tables/ExtensionVersion.java | 24 +- .../jooq/tables/records/DownloadRecord.java | 20 +- .../jooq/tables/records/ExtensionRecord.java | 122 +---- .../ExtensionTargetPlatformRecord.java | 254 ++++++++++ .../records/ExtensionVersionRecord.java | 64 +-- .../V1_21__ExtensionTargetPlatform.sql | 39 ++ .../org/eclipse/openvsx/AdminAPITest.java | 43 +- .../org/eclipse/openvsx/RegistryAPITest.java | 333 +++++++++---- .../openvsx/adapter/VSCodeAdapterTest.java | 77 ++- .../openvsx/eclipse/EclipseServiceTest.java | 20 +- .../search/DatabaseSearchServiceTest.java | 86 ++-- .../search/ElasticSearchServiceTest.java | 35 +- .../adapter/findid-yaml-query-alpine.json | 26 + .../adapter/findid-yaml-response-alpine.json | 102 ++++ .../adapter/findname-yaml-query-linux.json | 26 + .../adapter/findname-yaml-response-linux.json | 102 ++++ .../adapter/search-yaml-query-darwin.json | 26 + .../adapter/search-yaml-response-darwin.json | 102 ++++ webui/package.json | 2 +- webui/src/extension-registry-service.ts | 4 +- webui/src/extension-registry-types.ts | 6 + .../extension-remove-dialog.tsx | 46 +- .../extension-version-container.tsx | 122 +++-- .../extension-detail-downloads-menu.tsx | 81 +++ .../extension-detail-overview.tsx | 31 +- webui/src/utils.ts | 19 + 75 files changed, 2695 insertions(+), 984 deletions(-) create mode 100644 server/src/main/java/org/eclipse/openvsx/entities/ExtensionTargetPlatform.java create mode 100644 server/src/main/java/org/eclipse/openvsx/repositories/ExtensionTargetPlatformRepository.java create mode 100644 server/src/main/java/org/eclipse/openvsx/util/TargetPlatformValidator.java create mode 100644 server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionTargetPlatform.java create mode 100644 server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionTargetPlatformRecord.java create mode 100644 server/src/main/resources/db/migration/V1_21__ExtensionTargetPlatform.sql create mode 100644 server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-query-alpine.json create mode 100644 server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json create mode 100644 server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-query-linux.json create mode 100644 server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json create mode 100644 server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-query-darwin.json create mode 100644 server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json create mode 100644 webui/src/pages/extension-detail/extension-detail-downloads-menu.tsx diff --git a/cli/package.json b/cli/package.json index 8cdf226e2..a6fb47c3e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "ovsx", - "version": "0.3.0", + "version": "0.4.0", "description": "Command line interface for Eclipse Open VSX", "keywords": [ "cli", @@ -37,21 +37,21 @@ }, "dependencies": { "commander": "^6.1.0", - "follow-redirects": "^1.13.2", + "follow-redirects": "^1.14.6", "is-ci": "^2.0.0", "leven": "^3.1.0", "tmp": "^0.2.1", "vsce": "^2.6.3" }, "devDependencies": { - "@types/follow-redirects": "^1.13.0", + "@types/follow-redirects": "^1.13.1", "@types/is-ci": "^2.0.0", - "@types/node": "^10.14.18", - "@types/semver": "^7.1.0", + "@types/node": "^10.17.60", + "@types/semver": "^7.3.9", "@types/tmp": "^0.1.0", - "@typescript-eslint/eslint-plugin": "^3.6.1", - "@typescript-eslint/parser": "^3.6.1", - "eslint": "^7.4.0", + "@typescript-eslint/eslint-plugin": "^3.10.1", + "@typescript-eslint/parser": "^3.10.1", + "eslint": "^7.32.0", "rimraf": "^3.0.2", "typescript": "3.8.3" }, diff --git a/cli/src/get.ts b/cli/src/get.ts index b99790924..9584d7d24 100644 --- a/cli/src/get.ts +++ b/cli/src/get.ts @@ -26,7 +26,7 @@ export async function getExtension(options: GetOptions): Promise { throw new Error('The extension identifier must have the form `namespace.extension`.'); } - const extension = await registry.getMetadata(match[1], match[2]); + const extension = await registry.getMetadata(match[1], match[2], options.target); if (extension.error) { throw new Error(extension.error); } @@ -105,6 +105,10 @@ export interface GetOptions extends RegistryOptions { * Identifier in the form `namespace.extension` or `namespace/extension`. */ extensionId: string; + /** + * Target architecture. + */ + target?: string; /** * An exact version or version range. */ diff --git a/cli/src/main.ts b/cli/src/main.ts index b92e279e4..ba9b19613 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -35,15 +35,20 @@ module.exports = function (argv: string[]): void { const publishCmd = program.command('publish [extension.vsix]'); publishCmd.description('Publish an extension, packaging it first if necessary.') + .option('-t, --target ', 'Target architectures') .option('-i, --packagePath ', 'Publish the provided VSIX packages.') .option('--baseContentUrl ', 'Prepend all relative links in README.md with this URL.') .option('--baseImagesUrl ', 'Prepend all relative image links in README.md with this URL.') .option('--yarn', 'Use yarn instead of npm while packing extension files.') - .action((extensionFile: string, { packagePath, baseContentUrl, baseImagesUrl, yarn }) => { + .action((extensionFile: string, { target, packagePath, baseContentUrl, baseImagesUrl, yarn }) => { if (extensionFile !== undefined && packagePath !== undefined) { console.error('\u274c Please specify either a package file or a package path, but not both.\n'); publishCmd.help(); } + if (extensionFile !== undefined && target !== undefined) { + console.warn("Ignoring option '--target' for prepackaged extension."); + target = undefined; + } if (extensionFile !== undefined && baseContentUrl !== undefined) console.warn("Ignoring option '--baseContentUrl' for prepackaged extension."); if (extensionFile !== undefined && baseImagesUrl !== undefined) @@ -51,7 +56,7 @@ module.exports = function (argv: string[]): void { if (extensionFile !== undefined && yarn !== undefined) console.warn("Ignoring option '--yarn' for prepackaged extension."); const { registryUrl, pat } = program.opts(); - publish({ extensionFile, registryUrl, pat, packagePath: typeof packagePath === 'string' ? [packagePath] : packagePath, baseContentUrl, baseImagesUrl, yarn }) + publish({ extensionFile, registryUrl, pat, targets: typeof target === 'string' ? [target] : target, packagePath: typeof packagePath === 'string' ? [packagePath] : packagePath, baseContentUrl, baseImagesUrl, yarn }) .catch(handleError(program.debug, 'See the documentation for more information:\n' + 'https://github.com/eclipse/openvsx/wiki/Publishing-Extensions' @@ -60,12 +65,13 @@ module.exports = function (argv: string[]): void { const getCmd = program.command('get '); getCmd.description('Download an extension or its metadata.') + .option('-t, --target ', 'Target architecture') .option('-v, --versionRange ', 'Specify an exact version or a version range.') .option('-o, --output ', 'Save the output in the specified file or directory.') .option('--metadata', 'Print the extension\'s metadata instead of downloading it.') - .action((extensionId: string, { versionRange, output, metadata }) => { + .action((extensionId: string, { target, versionRange, output, metadata }) => { const { registryUrl } = program.opts(); - getExtension({ extensionId, version: versionRange, registryUrl, output, metadata }) + getExtension({ extensionId, target: target, version: versionRange, registryUrl, output, metadata }) .catch(handleError(program.debug)); }); diff --git a/cli/src/publish.ts b/cli/src/publish.ts index 89a701595..75605853b 100644 --- a/cli/src/publish.ts +++ b/cli/src/publish.ts @@ -18,12 +18,15 @@ import { checkLicense } from './check-license'; */ export async function publish(options: PublishOptions = {}): Promise { addEnvOptions(options); - if (options.packagePath) { - // call the publish command for every package path - await Promise.all(options.packagePath.map(path => doPublish({ ...options, packagePath: path }))); - } else { - return doPublish({ ... options, packagePath: undefined }); + const internalPublishOptions = []; + const packagePaths = options.packagePath || [undefined]; + const targets = options.targets || [undefined]; + for (const packagePath of packagePaths) { + for (const target of targets) { + internalPublishOptions.push({ ... options, packagePath: packagePath, target: target }); + } } + await Promise.all(internalPublishOptions.map(publishOptions => doPublish(publishOptions))); } async function doPublish(options: InternalPublishOptions = {}): Promise { @@ -35,6 +38,7 @@ async function doPublish(options: InternalPublishOptions = {}): Promise { if (options.packagePath && options.packagePath.endsWith('.vsix')) { options.extensionFile = options.packagePath; delete options.packagePath; + delete options.target; } const registry = new Registry(options); if (!options.extensionFile) { @@ -46,7 +50,13 @@ async function doPublish(options: InternalPublishOptions = {}): Promise { if (extension.error) { throw new Error(extension.error); } - console.log(`\ud83d\ude80 Published ${extension.namespace}.${extension.name} v${extension.version}`); + + const name = `${extension.namespace}.${extension.name}`; + const description = options.target + ? `${name} (${options.target}) v${extension.version}` + : `${name} v${extension.version}`; + + console.log(`\ud83d\ude80 Published ${description}`); } interface PublishCommonOptions extends RegistryOptions { @@ -72,6 +82,11 @@ interface PublishCommonOptions extends RegistryOptions { // Interface used by top level CLI export interface PublishOptions extends PublishCommonOptions { + /** + * Target architectures. + */ + targets?: string[]; + /** * Paths to the extension to be packaged and published. Cannot be used together * with `extensionFile`. @@ -82,6 +97,12 @@ export interface PublishOptions extends PublishCommonOptions { // Interface used internally by the doPublish method interface InternalPublishOptions extends PublishCommonOptions { + /** + * Only one target for our internal command. + * Target architecture. + */ + target?: string; + /** * Only one path for our internal command. * Path to the extension to be packaged and published. Cannot be used together @@ -97,6 +118,7 @@ async function packageExtension(options: InternalPublishOptions, registry: Regis options.extensionFile = await createTempFile({ postfix: '.vsix' }); const createVSIXOptions: ICreateVSIXOptions = { + target: options.target, cwd: options.packagePath, packagePath: options.extensionFile, baseContentUrl: options.baseContentUrl, diff --git a/cli/src/registry.ts b/cli/src/registry.ts index 1547a3928..82039fe50 100644 --- a/cli/src/registry.ts +++ b/cli/src/registry.ts @@ -70,9 +70,12 @@ export class Registry { } } - getMetadata(namespace: string, extension: string): Promise { + getMetadata(namespace: string, extension: string, target?: string): Promise { try { - const path = `api/${encodeURIComponent(namespace)}/${encodeURIComponent(extension)}`; + let path = `api/${encodeURIComponent(namespace)}/${encodeURIComponent(extension)}`; + if (target) { + path += `/${encodeURIComponent(target)}`; + } return this.getJson(this.getUrl(path)); } catch (err) { return Promise.reject(err); diff --git a/cli/yarn.lock b/cli/yarn.lock index db0fd828f..4bb2ce171 100644 --- a/cli/yarn.lock +++ b/cli/yarn.lock @@ -2,27 +2,56 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: - "@babel/highlight" "^7.8.3" + "@babel/highlight" "^7.10.4" -"@babel/helper-validator-identifier@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" - integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/highlight@^7.8.3": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" - integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== +"@babel/highlight@^7.10.4": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== dependencies: - "@babel/helper-validator-identifier" "^7.9.0" + "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@types/ci-info@*": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/ci-info/-/ci-info-2.0.0.tgz#51848cc0f5c30c064f4b25f7f688bf35825b3971" @@ -38,10 +67,10 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/follow-redirects@^1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@types/follow-redirects/-/follow-redirects-1.13.0.tgz#82d3767050314ddb44606576e6bf4fcfee5f4d05" - integrity sha512-VPGDD77WMB+da4l1vdfGYM1k9ZjtPyY8EHisvc33QUcwCf/stt/5M1P/ZOPhpnjPgBwJX/xuo3X3wFVp0rutTA== +"@types/follow-redirects@^1.13.1": + version "1.13.1" + resolved "https://registry.yarnpkg.com/@types/follow-redirects/-/follow-redirects-1.13.1.tgz#b3dde0c7fcff69c497c99daab21a8b366f09270a" + integrity sha512-WPzi4QUu0rXeRmcssiXmNVCSbV9elxQJoVfp1N4SThfbKAKEo/xrhzpZVVk/XSCzbwUg70W8wEUC/TCR05QMBQ== dependencies: "@types/node" "*" @@ -62,69 +91,67 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.8.tgz#09976420fc80a7a00bf40680c63815ed8c7616f4" integrity sha512-1WgO8hsyHynlx7nhP1kr0OFzsgKz5XDQL+Lfc3b1Q3qIln/n8cKD4m09NJ0+P1Rq7Zgnc7N0+SsMnoD1rEb0kA== -"@types/node@^10.14.18": - version "10.17.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.18.tgz#ae364d97382aacdebf583fa4e7132af2dfe56a0c" - integrity sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg== +"@types/node@^10.17.60": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/semver@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.1.0.tgz#c8c630d4c18cd326beff77404887596f96408408" - integrity sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA== - dependencies: - "@types/node" "*" +"@types/semver@^7.3.9": + version "7.3.9" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" + integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== "@types/tmp@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd" integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== -"@typescript-eslint/eslint-plugin@^3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.1.tgz#5ced8fd2087fbb83a76973dea4a0d39d9cb4a642" - integrity sha512-06lfjo76naNeOMDl+mWG9Fh/a0UHKLGhin+mGaIw72FUMbMGBkdi/FEJmgEDzh4eE73KIYzHWvOCYJ0ak7nrJQ== +"@typescript-eslint/eslint-plugin@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" + integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== dependencies: - "@typescript-eslint/experimental-utils" "3.6.1" + "@typescript-eslint/experimental-utils" "3.10.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.1.tgz#b5a2738ebbceb3fa90c5b07d50bb1225403c4a54" - integrity sha512-oS+hihzQE5M84ewXrTlVx7eTgc52eu+sVmG7ayLfOhyZmJ8Unvf3osyFQNADHP26yoThFfbxcibbO0d2FjnYhg== +"@typescript-eslint/experimental-utils@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/types" "3.6.1" - "@typescript-eslint/typescript-estree" "3.6.1" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.6.1.tgz#216e8adf4ee9c629f77c985476a2ea07fb80e1dc" - integrity sha512-SLihQU8RMe77YJ/jGTqOt0lMq7k3hlPVfp7v/cxMnXA9T0bQYoMDfTsNgHXpwSJM1Iq2aAJ8WqekxUwGv5F67Q== +"@typescript-eslint/parser@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" + integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.6.1" - "@typescript-eslint/types" "3.6.1" - "@typescript-eslint/typescript-estree" "3.6.1" + "@typescript-eslint/experimental-utils" "3.10.1" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/types@3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.1.tgz#87600fe79a1874235d3cc1cf5c7e1a12eea69eee" - integrity sha512-NPxd5yXG63gx57WDTW1rp0cF3XlNuuFFB5G+Kc48zZ+51ZnQn9yjDEsjTPQ+aWM+V+Z0I4kuTFKjKvgcT1F7xQ== +"@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== -"@typescript-eslint/typescript-estree@3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.1.tgz#a5c91fcc5497cce7922ff86bc37d5e5891dcdefa" - integrity sha512-G4XRe/ZbCZkL1fy09DPN3U0mR6SayIv1zSeBNquRFRk7CnVLgkC2ZPj8llEMJg5Y8dJ3T76SvTGtceytniaztQ== +"@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== dependencies: - "@typescript-eslint/types" "3.6.1" - "@typescript-eslint/visitor-keys" "3.6.1" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" debug "^4.1.1" glob "^7.1.6" is-glob "^4.0.1" @@ -132,24 +159,24 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.1.tgz#5c57a7772f4dd623cfeacc219303e7d46f963b37" - integrity sha512-qC8Olwz5ZyMTZrh4Wl3K4U6tfms0R/mzU4/5W3XeUZptVraGVmbptJbn6h2Ey6Rb3hOs3zWoAUebZk8t47KGiQ== +"@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== dependencies: eslint-visitor-keys "^1.1.0" -acorn-jsx@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" - integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^7.2.0: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -ajv@^6.10.0, ajv@^6.10.2: +ajv@^6.10.0: version "6.12.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== @@ -159,6 +186,26 @@ ajv@^6.10.0, ajv@^6.10.2: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.9.0.tgz#738019146638824dea25edcf299dcba1b0e7eb18" + integrity sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -169,11 +216,6 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -184,13 +226,20 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + ansi-styles@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" @@ -219,10 +268,10 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== azure-devops-node-api@^11.0.1: version "11.0.1" @@ -491,11 +540,6 @@ domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: domelementtype "^2.2.0" domhandler "^4.2.0" -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -525,7 +569,12 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -eslint-scope@^5.0.0, eslint-scope@^5.1.0: +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== @@ -533,40 +582,57 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^2.0.0: +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^7.4.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f" - integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g== +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" enquirer "^2.3.5" - eslint-scope "^5.1.0" - eslint-utils "^2.0.0" - eslint-visitor-keys "^1.2.0" - espree "^7.1.0" - esquery "^1.2.0" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -574,7 +640,7 @@ eslint@^7.4.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.14" + lodash.merge "^4.6.2" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -583,28 +649,28 @@ eslint@^7.4.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" - integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: - acorn "^7.2.0" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.2.0" + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -615,6 +681,13 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + estraverse@^4.1.0, estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" @@ -625,6 +698,11 @@ estraverse@^5.1.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -635,7 +713,7 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -657,28 +735,27 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" + flatted "^3.1.0" + rimraf "^3.0.2" -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== -follow-redirects@^1.13.2: +follow-redirects@^1.14.6: version "1.14.7" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== @@ -731,7 +808,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-parent@^5.0.0: +glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -750,12 +827,12 @@ glob@^7.0.6, glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.6.0, globals@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" has-flag@^3.0.0: version "3.0.0" @@ -819,6 +896,14 @@ import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -861,11 +946,6 @@ is-fullwidth-code-point@^1.0.0: dependencies: number-is-nan "^1.0.0" -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -906,6 +986,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -939,7 +1024,17 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" -lodash@^4.17.14, lodash@^4.17.15: +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lodash@^4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -984,7 +1079,7 @@ minimatch@^3.0.3, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -994,13 +1089,6 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@^0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1223,18 +1311,16 @@ regexpp@^3.0.0, regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -1312,14 +1398,14 @@ simple-get@^3.0.3: once "^1.3.1" simple-concat "^1.0.0" -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" sprintf-js@~1.0.2: version "1.0.3" @@ -1335,7 +1421,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4": +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1344,15 +1430,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1374,13 +1451,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -1395,7 +1465,7 @@ strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-json-comments@^3.1.0: +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -1419,15 +1489,16 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +table@^6.0.9: + version "6.8.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" + integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" tar-fs@^2.0.0: version "2.1.1" @@ -1498,10 +1569,10 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== typed-rest-client@^1.8.4: version "1.8.4" @@ -1599,13 +1670,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - xml2js@^0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" diff --git a/server/src/main/java/org/eclipse/openvsx/AdminAPI.java b/server/src/main/java/org/eclipse/openvsx/AdminAPI.java index 6f4b8fce2..de7dab787 100644 --- a/server/src/main/java/org/eclipse/openvsx/AdminAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/AdminAPI.java @@ -9,21 +9,17 @@ ********************************************************************************/ package org.eclipse.openvsx; -import java.time.DateTimeException; -import java.time.LocalDateTime; import java.time.Period; import java.time.format.DateTimeParseException; +import java.util.AbstractMap; import java.util.Collections; +import java.util.Map; import java.util.stream.Collectors; import java.net.URI; -import java.util.stream.LongStream; import com.google.common.base.Strings; -import org.eclipse.openvsx.entities.AdminStatistics; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.PersistedLog; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.ExtensionJson; import org.eclipse.openvsx.json.NamespaceJson; import org.eclipse.openvsx.json.NamespaceMembershipListJson; @@ -184,6 +180,10 @@ public ResponseEntity getExtension(@PathVariable String namespace json = local.toExtensionVersionJson(extVersion, false); } json.active = extension.isActive(); + json.allTargetPlatformVersions = repositories.findActiveVersions(extension).stream() + .map(v -> new AbstractMap.SimpleEntry<>(v.getVersion(), v.getTargetPlatform().getName())) + .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); + return ResponseEntity.ok(json); } catch (ErrorResultException exc) { return exc.toResponseEntity(ExtensionJson.class); @@ -209,10 +209,11 @@ private ExtensionVersion getLatestVersion(Extension extension) { ) public ResponseEntity deleteExtension(@PathVariable String namespaceName, @PathVariable String extensionName, - @RequestParam(required = false) String version) { + @RequestParam(required = false) String version, + @RequestParam(required = false) String targetPlatform) { try { var adminUser = admins.checkAdminUser(); - var result = admins.deleteExtension(namespaceName, extensionName, version, adminUser); + var result = admins.deleteExtension(namespaceName, extensionName, targetPlatform, version, adminUser); return ResponseEntity.ok(result); } catch (ErrorResultException exc) { return exc.toResponseEntity(); diff --git a/server/src/main/java/org/eclipse/openvsx/AdminService.java b/server/src/main/java/org/eclipse/openvsx/AdminService.java index 4cd569dcd..ea6ef408d 100644 --- a/server/src/main/java/org/eclipse/openvsx/AdminService.java +++ b/server/src/main/java/org/eclipse/openvsx/AdminService.java @@ -73,23 +73,39 @@ public class AdminService { .build(); @Transactional(rollbackOn = ErrorResultException.class) - public ResultJson deleteExtension(String namespaceName, String extensionName, String version, UserData admin) + public ResultJson deleteExtension(String namespaceName, String extensionName, String targetPlatform, String version, UserData admin) throws ErrorResultException { - if (Strings.isNullOrEmpty(version)) { + if (targetPlatform == null && Strings.isNullOrEmpty(version)) { var extension = repositories.findExtension(extensionName, namespaceName); if (extension == null) { throw new ErrorResultException("Extension not found: " + namespaceName + "." + extensionName, HttpStatus.NOT_FOUND); } return deleteExtension(extension, admin); + } else if(targetPlatform == null) { + ResultJson response = null; + var extensionId = namespaceName + "." + extensionName; + synchronized (deleteLocks.get(extensionId.hashCode(), hashCode -> new Object())) { + var extVersions = repositories.findTargetPlatformVersions(version, extensionName, namespaceName); + for (var extVersion : extVersions) { + if (extVersion == null) { + throw new ErrorResultException("Extension not found: " + namespaceName + "." + extensionName + " version " + version, + HttpStatus.NOT_FOUND); + } + + response = deleteExtension(extVersion, admin); + } + } + return response; } else { var extensionId = namespaceName + "." + extensionName; synchronized (deleteLocks.get(extensionId.hashCode(), hashCode -> new Object())) { - var extVersion = repositories.findVersion(version, extensionName, namespaceName); + var extVersion = repositories.findVersion(version, targetPlatform, extensionName, namespaceName); if (extVersion == null) { - throw new ErrorResultException("Extension not found: " + namespaceName + "." + extensionName + " version " + version, + throw new ErrorResultException("Extension not found: " + namespaceName + "." + extensionName + " (" + targetPlatform + ") version " + version, HttpStatus.NOT_FOUND); } + return deleteExtension(extVersion, admin); } } @@ -102,7 +118,7 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws throw new ErrorResultException("Extension " + namespace.getName() + "." + extension.getName() + " is bundled by the following extension packs: " + bundledRefs.stream() - .map(ev -> ev.getExtension().getNamespace().getName() + "." + ev.getExtension().getName() + "@" + ev.getVersion()) + .map(ev -> ev.getTargetPlatform().getExtension().getNamespace().getName() + "." + ev.getTargetPlatform().getExtension().getName() + "@" + ev.getVersion()) .collect(Collectors.joining(", "))); } var dependRefs = repositories.findDependenciesReference(extension); @@ -110,17 +126,23 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws throw new ErrorResultException("The following extensions have a dependency on " + namespace.getName() + "." + extension.getName() + ": " + dependRefs.stream() - .map(ev -> ev.getExtension().getNamespace().getName() + "." + ev.getExtension().getName() + "@" + ev.getVersion()) + .map(ev -> ev.getTargetPlatform().getExtension().getNamespace().getName() + "." + ev.getTargetPlatform().getExtension().getName() + "@" + ev.getVersion()) .collect(Collectors.joining(", "))); } - extension.setLatest(null); - extension.setPreview(null); + for(var targetPlatform : extension.getTargetPlatforms()) { + targetPlatform.setLatest(null); + targetPlatform.setPreview(null); + } for (var extVersion : repositories.findVersions(extension)) { removeExtensionVersion(extVersion); } for (var review : repositories.findAllReviews(extension)) { entityManager.remove(review); } + for(var targetPlatform : extension.getTargetPlatforms()) { + entityManager.remove(targetPlatform); + } + entityManager.remove(extension); search.removeSearchEntry(extension); @@ -130,7 +152,7 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws } protected ResultJson deleteExtension(ExtensionVersion extVersion, UserData admin) { - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); var versions = Lists.newArrayList(repositories.findVersions(extension)); if (versions.size() == 1) { return deleteExtension(extension, admin); @@ -261,7 +283,7 @@ public ResultJson revokePublisherContributions(String provider, String loginName var versions = repositories.findVersionsByAccessToken(accessToken, true); for (var version : versions) { version.setActive(false); - affectedExtensions.add(version.getExtension()); + affectedExtensions.add(version.getTargetPlatform().getExtension()); deactivatedExtensionCount++; } } diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java b/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java index af350a449..5ed2c832f 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java @@ -148,6 +148,11 @@ private JsonNode findByIdInArray(Iterable iter, String id) { return MissingNode.getInstance(); } + public String getTargetPlatform() { + loadVsixManifest(); + return vsixManifest.path("Metadata").path("Identity").path("TargetPlatform").asText(); + } + public String getExtensionName() { loadVsixManifest(); return vsixManifest.path("Metadata").path("Identity").path("Id").asText(); diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java index d87fcd777..dc5ae6d63 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java @@ -10,9 +10,7 @@ package org.eclipse.openvsx; import java.io.InputStream; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; +import java.util.*; import javax.persistence.EntityManager; import javax.transaction.Transactional; @@ -22,16 +20,13 @@ import com.google.common.base.Strings; import org.eclipse.openvsx.adapter.VSCodeIdService; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.FileResource; -import org.eclipse.openvsx.entities.PersonalAccessToken; -import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; import org.eclipse.openvsx.storage.StorageUtilService; import org.eclipse.openvsx.util.ErrorResultException; import org.eclipse.openvsx.util.SemanticVersion; +import org.eclipse.openvsx.util.TargetPlatformValidator; import org.eclipse.openvsx.util.TimeUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -80,7 +75,7 @@ public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken storeResources(extVersion, resources); // Update the 'latest' / 'preview' references and the search index - updateExtension(extVersion.getExtension()); + updateExtension(extVersion.getTargetPlatform().getExtension()); return extVersion; } @@ -110,6 +105,11 @@ private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Us extVersion.setPublishedWith(token); extVersion.setActive(true); + ExtensionTargetPlatform targetPlatform = null; + var targetPlatformName = TargetPlatformValidator.isValid(processor.getTargetPlatform()) + ? processor.getTargetPlatform() + : ExtensionTargetPlatform.NAME_ANY; + var extension = repositories.findExtension(extensionName, namespace); if (extension == null) { extension = new Extension(); @@ -119,16 +119,31 @@ private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Us vsCodeIdService.createPublicId(extension); entityManager.persist(extension); } else { - var existingVersion = repositories.findVersion(extVersion.getVersion(), extension); + targetPlatform = extension.getTargetPlatforms().stream() + .filter(p -> p.getName().equals(targetPlatformName)) + .findFirst() + .orElse(null); + } + + if(targetPlatform == null) { + targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(targetPlatformName); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); + + entityManager.persist(targetPlatform); + } else { + var existingVersion = repositories.findVersion(extVersion.getVersion(), targetPlatform.getName(), extension); if (existingVersion != null) { throw new ErrorResultException( "Extension " + namespace.getName() + "." + extension.getName() + + (targetPlatform.getName().isEmpty() ? "" : " for " + targetPlatform.getName()) + " version " + extVersion.getVersion() + " is already published" + (existingVersion.isActive() ? "." : ", but is currently inactive and therefore not visible.")); } } - extVersion.setExtension(extension); + extVersion.setTargetPlatform(targetPlatform); entityManager.persist(extVersion); var metadataIssues = validator.validateMetadata(extVersion); @@ -181,11 +196,18 @@ private void checkLicense(ExtensionVersion extVersion, List resour } private void storeResources(ExtensionVersion extVersion, List resources) { - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); resources.forEach(resource -> { if (resource.getType().equals(FileResource.DOWNLOAD)) { - resource.setName(namespace.getName() + "." + extension.getName() + "-" + extVersion.getVersion() + ".vsix"); + var resourceName = namespace.getName() + "." + extension.getName(); + if(!targetPlatform.getName().equals(ExtensionTargetPlatform.NAME_ANY)) { + resourceName += "-" + targetPlatform.getName(); + } + + resourceName += "-" + extVersion.getVersion() + ".vsix"; + resource.setName(resourceName); } if (storageUtil.shouldStoreExternally(resource)) { storageUtil.uploadFile(resource); @@ -204,14 +226,19 @@ private void storeResources(ExtensionVersion extVersion, List reso */ @Transactional(TxType.REQUIRED) public void updateExtension(Extension extension) { - extension.setLatest(getLatestVersion(extension, false)); - extension.setPreview(getLatestVersion(extension, true)); - if (extension.getLatest() == null) { - // Use a preview version as latest if it's the only available version - extension.setLatest(extension.getPreview()); + for(var targetPlatform : extension.getTargetPlatforms()) { + targetPlatform.setLatest(getLatestVersion(targetPlatform, false)); + targetPlatform.setPreview(getLatestVersion(targetPlatform, true)); + if (targetPlatform.getLatest() == null) { + // Use a preview version as latest if it's the only available version + targetPlatform.setLatest(targetPlatform.getPreview()); + } } - if (extension.getLatest() != null) { + var atLeastOneActiveVersion = extension.getTargetPlatforms().stream() + .anyMatch(p -> p.getLatest() != null); + + if (atLeastOneActiveVersion) { // There is at least one active version => activate the extension extension.setActive(true); search.updateSearchEntry(extension); @@ -222,10 +249,10 @@ public void updateExtension(Extension extension) { } } - private ExtensionVersion getLatestVersion(Extension extension, boolean preview) { + private ExtensionVersion getLatestVersion(ExtensionTargetPlatform targetPlatform, boolean preview) { ExtensionVersion latest = null; SemanticVersion latestSemver = null; - for (var extVer : repositories.findActiveVersions(extension, preview)) { + for (var extVer : repositories.findActiveVersions(targetPlatform, preview)) { var semver = extVer.getSemanticVersion(); if (latestSemver == null || latestSemver.compareTo(semver) < 0) { latest = extVer; @@ -241,14 +268,20 @@ private ExtensionVersion getLatestVersion(Extension extension, boolean preview) */ @Transactional(TxType.REQUIRED) public void updateExtension(Extension extension, Iterable versions) { - extension.setLatest(getLatestVersion(versions, false)); - extension.setPreview(getLatestVersion(versions, true)); - if (extension.getLatest() == null) { - // Use a preview version as latest if it's the only available version - extension.setLatest(extension.getPreview()); + for(var targetPlatform : extension.getTargetPlatforms()) { + targetPlatform.setLatest(getLatestVersion(versions, false)); + targetPlatform.setPreview(getLatestVersion(versions, true)); + if (targetPlatform.getLatest() == null) { + // Use a preview version as latest if it's the only available version + targetPlatform.setLatest(targetPlatform.getPreview()); + } } - if (extension.getLatest() != null) { + var atLeastOneActiveVersion = extension.getTargetPlatforms().stream() + .map(ExtensionTargetPlatform::getLatest) + .anyMatch(Objects::nonNull); + + if (atLeastOneActiveVersion) { // There is at least one active version => activate the extension extension.setActive(true); search.updateSearchEntry(extension); @@ -285,7 +318,7 @@ public void reactivateExtensions(UserData user) { var versions = repositories.findVersionsByAccessToken(accessToken, false); for (var version : versions) { version.setActive(true); - affectedExtensions.add(version.getExtension()); + affectedExtensions.add(version.getTargetPlatform().getExtension()); } } for (var extension : affectedExtensions) { diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java b/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java index 89b4e25e2..293e8517c 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java @@ -27,21 +27,13 @@ @Component public class ExtensionValidator { - private final static List MARKDOWN_VALUES = Arrays.asList(new String[] { - "github", "standard" - }); + private final static List MARKDOWN_VALUES = List.of("github", "standard"); - private final static List GALLERY_THEME_VALUES = Arrays.asList(new String[] { - "dark", "light" - }); + private final static List GALLERY_THEME_VALUES = List.of("dark", "light"); - private final static List QNA_VALUES = Arrays.asList(new String[] { - "marketplace", "false" - }); + private final static List QNA_VALUES = List.of("marketplace", "false"); - private final static List VERSION_CHARS = Arrays.asList(new Character[] { - '$', '+', '-', ',', '.', ':', ';', '_' - }); + private final static List VERSION_CHARS = List.of('$', '+', '-', ',', '.', ':', ';', '_'); private final static int DEFAULT_STRING_SIZE = 255; private final static int DESCRIPTION_SIZE = 2048; diff --git a/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java b/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java index 9ba89f32f..2c1c18cde 100644 --- a/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java +++ b/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java @@ -16,6 +16,7 @@ import org.eclipse.openvsx.json.ReviewListJson; import org.eclipse.openvsx.json.SearchResultJson; import org.eclipse.openvsx.search.ISearchService; +import org.eclipse.openvsx.util.NotFoundException; import org.springframework.http.ResponseEntity; /** @@ -25,11 +26,11 @@ public interface IExtensionRegistry { NamespaceJson getNamespace(String namespace); - ExtensionJson getExtension(String namespace, String extension); + ExtensionJson getExtension(String namespace, String extensionName, String targetPlatform); - ExtensionJson getExtension(String namespace, String extension, String version); + ExtensionJson getExtension(String namespace, String extension, String targetPlatform, String version); - ResponseEntity getFile(String namespace, String extension, String version, String fileName); + ResponseEntity getFile(String namespace, String extensionName, String targetPlatform, String version, String fileName); ReviewListJson getReviews(String namespace, String extension); diff --git a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java index 8dade874b..f07790c84 100644 --- a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java @@ -27,12 +27,7 @@ import org.apache.jena.ext.com.google.common.collect.Maps; import org.eclipse.openvsx.dto.*; import org.eclipse.openvsx.eclipse.EclipseService; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionReview; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.FileResource; -import org.eclipse.openvsx.entities.Namespace; -import org.eclipse.openvsx.entities.NamespaceMembership; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.ExtensionJson; import org.eclipse.openvsx.json.NamespaceJson; import org.eclipse.openvsx.json.QueryParamJson; @@ -109,40 +104,66 @@ public NamespaceJson getNamespace(String namespaceName) { } @Override - public ExtensionJson getExtension(String namespace, String extensionName) { + public ExtensionJson getExtension(String namespace, String extensionName, String targetPlatform) { var extension = repositories.findExtension(extensionName, namespace); if (extension == null || !extension.isActive()) throw new NotFoundException(); - return toExtensionVersionJson(extension.getLatest(), true); + + var latest = extension.getTargetPlatforms().stream() + .filter(p -> p.getName().equals(targetPlatform)) + .findFirst() + .map(ExtensionTargetPlatform::getLatest) + .orElseThrow(NotFoundException::new); + + var json = toExtensionVersionJson(latest, true); + json.downloads = extension.getTargetPlatforms().stream() + .flatMap(p -> p.getVersions().stream()) + .filter(v -> v.getVersion().equals(latest.getVersion())) + .map(v -> { + var fileUrls = new HashMap(); + storageUtil.addFileUrls(v, UrlUtil.getBaseUrl(), fileUrls, DOWNLOAD); + return new AbstractMap.SimpleEntry<>(v.getTargetPlatform().getName(), fileUrls.get(DOWNLOAD)); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + return json; } @Override - public ExtensionJson getExtension(String namespace, String extensionName, String version) { - var extVersion = findVersion(namespace, extensionName, version); + public ExtensionJson getExtension(String namespace, String extension, String targetPlatform, String version) { + var extVersion = findVersion(namespace, extension, targetPlatform, version); if (extVersion == null || !extVersion.isActive()) throw new NotFoundException(); return toExtensionVersionJson(extVersion, true); } - private ExtensionVersion findVersion(String namespace, String extensionName, String version) { + private ExtensionVersion findVersion(String namespace, String extensionName, String targetPlatform, String version) { if ("latest".equals(version)) { var extension = repositories.findExtension(extensionName, namespace); if (extension == null || !extension.isActive()) return null; - return extension.getLatest(); + return extension.getTargetPlatforms().stream() + .filter(p -> p.getName().equals(targetPlatform)) + .findFirst() + .map(ExtensionTargetPlatform::getLatest) + .orElse(null); } else if ("preview".equals(version)) { var extension = repositories.findExtension(extensionName, namespace); if (extension == null || !extension.isActive()) return null; - return extension.getPreview(); + return extension.getTargetPlatforms().stream() + .filter(p -> p.getName().equals(targetPlatform)) + .findFirst() + .map(ExtensionTargetPlatform::getPreview) + .orElse(null); } else { - return repositories.findVersion(version, extensionName, namespace); + return repositories.findVersion(version, targetPlatform, extensionName, namespace); } } @Override - public ResponseEntity getFile(String namespace, String extensionName, String version, String fileName) { - var extVersion = findVersion(namespace, extensionName, version); + public ResponseEntity getFile(String namespace, String extensionName, String targetPlatform, String version, String fileName) { + var extVersion = findVersion(namespace, extensionName, targetPlatform, version); if (extVersion == null || !extVersion.isActive()) throw new NotFoundException(); var resource = repositories.findFileByName(extVersion, fileName); @@ -224,32 +245,33 @@ public QueryResultJson query(QueryParamJson param) { param.extensionName = split[1]; } + var targetPlatform = param.targetPlatform == null ? ExtensionTargetPlatform.NAME_ANY : param.targetPlatform; List extensionVersions = new ArrayList<>(); // Add extension by UUID (public_id) if (!Strings.isNullOrEmpty(param.extensionUuid)) { - extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByExtensionPublicId(param.extensionUuid)); + extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByExtensionPublicId(targetPlatform, param.extensionUuid)); } // Add extensions by namespace UUID (public_id) if (!Strings.isNullOrEmpty(param.namespaceUuid)) { - extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByNamespacePublicId(param.namespaceUuid)); + extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByNamespacePublicId(targetPlatform, param.namespaceUuid)); } // Add a specific version of an extension if (!Strings.isNullOrEmpty(param.namespaceName) && !Strings.isNullOrEmpty(param.extensionName) && !Strings.isNullOrEmpty(param.extensionVersion) && !param.includeAllVersions) { - var extensionVersion = repositories.findActiveExtensionVersionDTOByVersion(param.extensionVersion, param.extensionName, param.namespaceName); + var extensionVersion = repositories.findActiveExtensionVersionDTOByVersion(param.extensionVersion, targetPlatform, param.extensionName, param.namespaceName); if(extensionVersion != null) { extensionVersions.add(extensionVersion); } // Add extension by namespace and name } else if (!Strings.isNullOrEmpty(param.namespaceName) && !Strings.isNullOrEmpty(param.extensionName)) { - extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByExtensionName(param.extensionName, param.namespaceName)); + extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByExtensionName(targetPlatform, param.extensionName, param.namespaceName)); // Add extensions by namespace } else if (!Strings.isNullOrEmpty(param.namespaceName)) { - extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByNamespaceName(param.namespaceName)); + extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByNamespaceName(targetPlatform, param.namespaceName)); // Add extensions by name } else if (!Strings.isNullOrEmpty(param.extensionName)) { - extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByExtensionName(param.extensionName)); + extensionVersions.addAll(repositories.findActiveExtensionVersionDTOsByExtensionName(targetPlatform, param.extensionName)); } extensionVersions = extensionVersions.stream() @@ -488,13 +510,16 @@ private Double computeAverageRating(Extension extension) { private SearchEntryJson toSearchEntry(SearchHit searchHit, String serverUrl, ISearchService.Options options) { var searchItem = searchHit.getContent(); var extension = entityManager.find(Extension.class, searchItem.id); - if (extension == null || !extension.isActive()){ + if (extension == null || !extension.isActive()) { extension = new Extension(); extension.setId(searchItem.id); search.removeSearchEntry(extension); return null; } - var extVer = extension.getLatest(); + + var extVer = findTargetPlatform(extension, options.targetPlatform) + .orElse(findTargetPlatform(extension, ExtensionTargetPlatform.NAME_ANY).orElse(extension.getTargetPlatforms().get(0))) + .getLatest(); var entry = extVer.toSearchEntryJson(); entry.url = createApiUrl(serverUrl, "api", entry.namespace, entry.name); entry.files = Maps.newLinkedHashMapWithExpectedSize(2); @@ -507,6 +532,10 @@ private SearchEntryJson toSearchEntry(SearchHit searchHit, Stri return entry; } + private Optional findTargetPlatform(Extension extension, String targetPlatformName) { + return extension.getTargetPlatforms().stream().filter(p -> p.getName().equals(targetPlatformName)).findFirst(); + } + private SearchEntryJson.VersionReference toVersionReference(ExtensionVersion extVersion, SearchEntryJson entry, String serverUrl) { var json = new SearchEntryJson.VersionReference(); json.version = extVersion.getVersion(); @@ -518,31 +547,32 @@ private SearchEntryJson.VersionReference toVersionReference(ExtensionVersion ext } public ExtensionJson toExtensionVersionJson(ExtensionVersion extVersion, boolean onlyActive) { - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); var json = extVersion.toExtensionJson(); + json.targetPlatform = targetPlatform.getName(); json.versionAlias = new ArrayList<>(2); - if (extVersion == extension.getLatest()) + if (extVersion == targetPlatform.getLatest()) json.versionAlias.add("latest"); - if (extVersion == extension.getPreview()) + if (extVersion == targetPlatform.getPreview()) json.versionAlias.add("preview"); json.verified = isVerified(extVersion); json.namespaceAccess = "restricted"; json.unrelatedPublisher = !json.verified; - json.reviewCount = repositories.countActiveReviews(extension); + json.reviewCount = repositories.countActiveReviews(targetPlatform.getExtension()); var serverUrl = UrlUtil.getBaseUrl(); json.namespaceUrl = createApiUrl(serverUrl, "api", json.namespace); json.reviewsUrl = createApiUrl(serverUrl, "api", json.namespace, json.name, "reviews"); - var versionStrings = onlyActive ? repositories.getActiveVersionStrings(extension) : repositories.getVersionStrings(extension); + var versionStrings = onlyActive ? repositories.getActiveVersionStrings(targetPlatform) : repositories.getVersionStrings(targetPlatform); var allVersions = CollectionUtil.map(versionStrings, v -> new SemanticVersion(v)); Collections.sort(allVersions, Collections.reverseOrder()); json.allVersions = Maps.newLinkedHashMapWithExpectedSize(allVersions.size() + 2); - if (extension.getLatest() != null) - json.allVersions.put("latest", createApiUrl(serverUrl, "api", json.namespace, json.name, "latest")); - if (extension.getPreview() != null) - json.allVersions.put("preview", createApiUrl(serverUrl, "api", json.namespace, json.name, "preview")); + if (targetPlatform.getLatest() != null) + json.allVersions.put("latest", createApiUrl(serverUrl, "api", json.namespace, json.name, json.targetPlatform, "latest")); + if (targetPlatform.getPreview() != null) + json.allVersions.put("preview", createApiUrl(serverUrl, "api", json.namespace, json.name, json.targetPlatform, "preview")); for (var version : allVersions) { - String url = createApiUrl(serverUrl, "api", json.namespace, json.name, version.toString()); + String url = createApiUrl(serverUrl, "api", json.namespace, json.name, json.targetPlatform, version.toString()); json.allVersions.put(version.toString(), url); } @@ -626,7 +656,7 @@ private boolean isVerified(ExtensionVersion extVersion) { if (extVersion.getPublishedWith() == null) return false; var user = extVersion.getPublishedWith().getUser(); - var namespace = extVersion.getExtension().getNamespace(); + var namespace = extVersion.getTargetPlatform().getExtension().getNamespace(); return repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER) > 0 && repositories.countMemberships(user, namespace) > 0; } diff --git a/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java b/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java index aa225344b..4b0099cb8 100644 --- a/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java @@ -18,6 +18,7 @@ import com.google.common.collect.Iterables; import io.swagger.annotations.*; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; import org.eclipse.openvsx.json.ExtensionJson; import org.eclipse.openvsx.json.NamespaceJson; import org.eclipse.openvsx.json.QueryParamJson; @@ -28,9 +29,7 @@ import org.eclipse.openvsx.json.SearchEntryJson; import org.eclipse.openvsx.json.SearchResultJson; import org.eclipse.openvsx.search.ISearchService; -import org.eclipse.openvsx.util.ErrorResultException; -import org.eclipse.openvsx.util.NotFoundException; -import org.eclipse.openvsx.util.UrlUtil; +import org.eclipse.openvsx.util.*; import org.elasticsearch.common.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.CacheControl; @@ -52,6 +51,7 @@ public class RegistryAPI { private final static int REVIEW_TITLE_SIZE = 255; private final static int REVIEW_COMMENT_SIZE = 2048; + private final static String VERSION_PATH_PARAM_REGEX = "(?:" + SemanticVersion.VERSION_PATH_PARAM_REGEX + ")|latest|preview"; @Autowired LocalRegistryService local; @@ -126,7 +126,7 @@ public ResponseEntity getExtension( try { return ResponseEntity.ok() .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) - .body(registry.getExtension(namespace, extension)); + .body(registry.getExtension(namespace, extension, ExtensionTargetPlatform.NAME_ANY)); } catch (NotFoundException exc) { // Try the next registry } @@ -136,7 +136,44 @@ public ResponseEntity getExtension( } @GetMapping( - path = "/api/{namespace}/{extension}/{version}", + path = "/api/{namespace}/{extension}/{target:" + ExtensionTargetPlatform.NAMES_PATH_PARAM_REGEX + "}", + produces = MediaType.APPLICATION_JSON_VALUE + ) + @CrossOrigin + @ApiOperation("Provides metadata of the latest version of an extension") + @ApiResponses({ + @ApiResponse( + code = 200, + message = "The extension metadata are returned in JSON format" + ), + @ApiResponse( + code = 404, + message = "The specified extension could not be found" + ) + }) + public ResponseEntity getExtension( + @PathVariable @ApiParam(value = "Extension namespace", example = "redhat") + String namespace, + @PathVariable @ApiParam(value = "Extension name", example = "java") + String extension, + @PathVariable @ApiParam(value = "Target architecture", example = ExtensionTargetPlatform.NAME_LINUX_ARM64, allowableValues = ExtensionTargetPlatform.NAMES_PARAM_METADATA) + CharSequence target + ) { + for (var registry : getRegistries()) { + try { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) + .body(registry.getExtension(namespace, extension, target.toString())); + } catch (NotFoundException exc) { + // Try the next registry + } + } + var json = ExtensionJson.error("Extension not found: " + namespace + "." + extension); + return new ResponseEntity<>(json, HttpStatus.NOT_FOUND); + } + + @GetMapping( + path = "/api/{namespace}/{extension}/{version:" + VERSION_PATH_PARAM_REGEX + "}", produces = MediaType.APPLICATION_JSON_VALUE ) @CrossOrigin @@ -163,7 +200,7 @@ public ResponseEntity getExtension( try { return ResponseEntity.ok() .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePublic()) - .body(registry.getExtension(namespace, extension, version)); + .body(registry.getExtension(namespace, extension, ExtensionTargetPlatform.NAME_ANY, version)); } catch (NotFoundException exc) { // Try the next registry } @@ -172,7 +209,88 @@ public ResponseEntity getExtension( return new ResponseEntity<>(json, HttpStatus.NOT_FOUND); } - @GetMapping("/api/{namespace}/{extension}/{version}/file/{fileName:.+}") + @GetMapping( + path = "/api/{namespace}/{extension}/{target:" + ExtensionTargetPlatform.NAMES_PATH_PARAM_REGEX + "}/{version:" + VERSION_PATH_PARAM_REGEX + "}", + produces = MediaType.APPLICATION_JSON_VALUE + ) + @CrossOrigin + @ApiOperation("Provides metadata of a specific version of an extension") + @ApiResponses({ + @ApiResponse( + code = 200, + message = "The extension metadata are returned in JSON format" + ), + @ApiResponse( + code = 404, + message = "The specified extension could not be found" + ) + }) + public ResponseEntity getExtension( + @PathVariable @ApiParam(value = "Extension namespace", example = "redhat") + String namespace, + @PathVariable @ApiParam(value = "Extension name", example = "java") + String extension, + @PathVariable @ApiParam(value = "Target architecture", example = ExtensionTargetPlatform.NAME_LINUX_ARM64, allowableValues = ExtensionTargetPlatform.NAMES_PARAM_METADATA) + String target, + @PathVariable @ApiParam(value = "Extension version", example = "0.65.0") + String version + ) { + for (var registry : getRegistries()) { + try { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePublic()) + .body(registry.getExtension(namespace, extension, target, version)); + } catch (NotFoundException exc) { + // Try the next registry + } + } + var json = ExtensionJson.error("Extension not found: " + namespace + "." + extension + " version " + version); + return new ResponseEntity<>(json, HttpStatus.NOT_FOUND); + } + + @GetMapping("/api/{namespace}/{extension}/{version:" + VERSION_PATH_PARAM_REGEX + "}/file/{fileName:.+}") + @CrossOrigin + @ApiOperation("Access a file packaged by an extension") + @ApiResponses({ + @ApiResponse( + code = 200, + message = "The file content is returned" + ), + @ApiResponse( + code = 302, + message = "The file is found at the specified location", + responseHeaders = @ResponseHeader( + name = "Location", + description = "The actual URL where the file can be accessed", + response = String.class + ) + ), + @ApiResponse( + code = 404, + message = "The specified file could not be found" + ) + }) + public ResponseEntity getFile( + @PathVariable @ApiParam(value = "Extension namespace", example = "redhat") + String namespace, + @PathVariable @ApiParam(value = "Extension name", example = "java") + String extension, + @PathVariable @ApiParam(value = "Extension version", example = "0.65.0") + String version, + @PathVariable @ApiParam(value = "Name of the file to access", example = "LICENSE.txt") + String fileName + ) { + for (var registry : getRegistries()) { + try { + return registry.getFile(namespace, extension, ExtensionTargetPlatform.NAME_ANY, version, fileName); + } catch (NotFoundException exc) { + // Try the next registry + } + } + throw new NotFoundException(); + } + + @GetMapping("/api/{namespace}/{extension}/{target:" + ExtensionTargetPlatform.NAMES_PATH_PARAM_REGEX + "}/{version:" + VERSION_PATH_PARAM_REGEX + "}/file/{fileName:.+}") @CrossOrigin @ApiOperation("Access a file packaged by an extension") @ApiResponses({ @@ -199,6 +317,8 @@ public ResponseEntity getFile( String namespace, @PathVariable @ApiParam(value = "Extension name", example = "java") String extension, + @PathVariable @ApiParam(value = "Target architecture", example = ExtensionTargetPlatform.NAME_LINUX_ARM64, allowableValues = ExtensionTargetPlatform.NAMES_PARAM_METADATA) + String target, @PathVariable @ApiParam(value = "Extension version", example = "0.65.0") String version, @PathVariable @ApiParam(value = "Name of the file to access", example = "LICENSE.txt") @@ -206,7 +326,7 @@ public ResponseEntity getFile( ) { for (var registry : getRegistries()) { try { - return registry.getFile(namespace, extension, version, fileName); + return registry.getFile(namespace, extension, target, version, fileName); } catch (NotFoundException exc) { // Try the next registry } @@ -272,6 +392,9 @@ public ResponseEntity search( @RequestParam(required = false) @ApiParam(value = "Extension category as shown in the UI", example = "Programming Languages") String category, + @RequestParam(defaultValue = "") + @ApiParam(value = "Target architecture", example = ExtensionTargetPlatform.NAME_LINUX_ARM64, allowableValues = ExtensionTargetPlatform.NAMES_PARAM_METADATA) + String targetPlatform, @RequestParam(defaultValue = "18") @ApiParam(value = "Maximal number of entries to return", allowableValues = "range[0,infinity]") int size, @@ -297,7 +420,7 @@ public ResponseEntity search( return new ResponseEntity<>(json, HttpStatus.BAD_REQUEST); } - var options = new ISearchService.Options(query, category, size, offset, sortOrder, sortBy, includeAllVersions); + var options = new ISearchService.Options(query, category, targetPlatform, size, offset, sortOrder, sortBy, includeAllVersions); var result = new SearchResultJson(); result.extensions = new ArrayList<>(size); for (var registry : getRegistries()) { @@ -376,7 +499,10 @@ public ResponseEntity getQuery( String namespaceUuid, @RequestParam(required = false) @ApiParam(value = "Whether to include all versions of an extension") - boolean includeAllVersions + boolean includeAllVersions, + @RequestParam(required = false) + @ApiParam(value = "Target architecture", example = ExtensionTargetPlatform.NAME_LINUX_X64, allowableValues = ExtensionTargetPlatform.NAMES_PARAM_METADATA) + String targetPlatform ) { var param = new QueryParamJson(); param.namespaceName = namespaceName; @@ -386,6 +512,7 @@ public ResponseEntity getQuery( param.extensionUuid = extensionUuid; param.namespaceUuid = namespaceUuid; param.includeAllVersions = includeAllVersions; + param.targetPlatform = targetPlatform; var result = new QueryResultJson(); for (var registry : getRegistries()) { @@ -534,7 +661,7 @@ public ResponseEntity publish( try { var json = local.publish(content, token); var serverUrl = UrlUtil.getBaseUrl(); - var url = UrlUtil.createApiUrl(serverUrl, "api", json.namespace, json.name, json.version); + var url = UrlUtil.createApiUrl(serverUrl, "api", json.namespace, json.name, json.targetPlatform, json.version); return ResponseEntity.status(HttpStatus.CREATED) .location(URI.create(url)) .body(json); diff --git a/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java b/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java index f9d5dcac4..23efd0169 100644 --- a/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java @@ -66,9 +66,9 @@ public NamespaceJson getNamespace(String namespace) { } @Override - public ExtensionJson getExtension(String namespace, String extension) { + public ExtensionJson getExtension(String namespace, String extension, String targetPlatform) { try { - String requestUrl = createApiUrl(upstreamUrl, "api", namespace, extension); + String requestUrl = createApiUrl(upstreamUrl, "api", namespace, extension, targetPlatform); return restTemplate.getForObject(requestUrl, ExtensionJson.class); } catch (RestClientException exc) { handleError(exc); @@ -77,9 +77,9 @@ public ExtensionJson getExtension(String namespace, String extension) { } @Override - public ExtensionJson getExtension(String namespace, String extension, String version) { + public ExtensionJson getExtension(String namespace, String extension, String targetPlatform, String version) { try { - String requestUrl = createApiUrl(upstreamUrl, "api", namespace, extension, version); + String requestUrl = createApiUrl(upstreamUrl, "api", namespace, extension, targetPlatform, version); return restTemplate.getForObject(requestUrl, ExtensionJson.class); } catch (RestClientException exc) { handleError(exc); @@ -88,8 +88,8 @@ public ExtensionJson getExtension(String namespace, String extension, String ver } @Override - public ResponseEntity getFile(String namespace, String extension, String version, String fileName) { - return getFile(createApiUrl(upstreamUrl, "api", namespace, extension, version, "file", fileName)); + public ResponseEntity getFile(String namespace, String extension, String targetPlatform, String version, String fileName) { + return getFile(createApiUrl(upstreamUrl, "api", namespace, extension, targetPlatform, version, "file", fileName)); } private ResponseEntity getFile(String url) { @@ -133,7 +133,8 @@ public SearchResultJson search(ISearchService.Options options) { "offset", Integer.toString(options.requestedOffset), "sortOrder", options.sortOrder, "sortBy", options.sortBy, - "includeAllVersions", Boolean.toString(options.includeAllVersions) + "includeAllVersions", Boolean.toString(options.includeAllVersions), + "targetPlatform", options.targetPlatform ); return restTemplate.getForObject(requestUrl, SearchResultJson.class); } catch (RestClientException exc) { diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java b/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java index 2fcf1cc2b..6b3a27063 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/ExtensionQueryResult.java @@ -12,8 +12,7 @@ import java.util.List; // Keep interfaces in sync with -// https://github.com/microsoft/vscode/blob/e7d7e9a9348e6a8cc8c03f877d39cb72e5dfb1ff/src/vs/platform/extensionManagement/common/extensionGalleryService.ts#L28-L67 - +// https://github.com/microsoft/vscode/blob/de0724b414e2f95f6cc484b03bccbc96686c2cfd/src/vs/platform/extensionManagement/common/extensionGalleryService.ts#L34-L81 public class ExtensionQueryResult { public List results; @@ -45,6 +44,8 @@ public static class Publisher { public String displayName; public String publisherId; public String publisherName; + public String domain; + public boolean isDomainVerified; } public static class ExtensionVersion { diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java b/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java index a9606a2e8..b5f404914 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java @@ -14,17 +14,16 @@ import org.eclipse.openvsx.dto.ExtensionReviewCountDTO; import org.eclipse.openvsx.dto.ExtensionVersionDTO; import org.eclipse.openvsx.dto.FileResourceDTO; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.FileResource; -import org.eclipse.openvsx.repositories.ExtensionDTORepository; -import org.eclipse.openvsx.repositories.ExtensionVersionDTORepository; -import org.eclipse.openvsx.repositories.FileResourceDTORepository; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; import org.eclipse.openvsx.storage.GoogleCloudStorageService; import org.eclipse.openvsx.storage.StorageUtilService; import org.eclipse.openvsx.util.ErrorResultException; import org.eclipse.openvsx.util.NotFoundException; +import org.eclipse.openvsx.util.TargetPlatformValidator; import org.eclipse.openvsx.util.UrlUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -81,6 +80,7 @@ public class VSCodeAdapter { ) @CrossOrigin public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam param) { + String targetPlatform; String queryString = null; String category = null; PageRequest pageRequest; @@ -92,6 +92,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para pageRequest = PageRequest.of(0, DEFAULT_PAGE_SIZE); sortBy = "relevance"; sortOrder = "desc"; + targetPlatform = ExtensionTargetPlatform.NAME_ANY; extensionIds = Collections.emptySet(); extensionNames = Collections.emptySet(); } else { @@ -104,6 +105,9 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para queryString = filter.findCriterion(FILTER_TAG); category = filter.findCriterion(FILTER_CATEGORY); + var targetCriterion = filter.findCriterion(FILTER_TARGET); + targetPlatform = TargetPlatformValidator.isValid(targetCriterion) ? targetCriterion : ExtensionTargetPlatform.NAME_ANY; + var pageSize = filter.pageSize > 0 ? filter.pageSize : DEFAULT_PAGE_SIZE; pageRequest = PageRequest.of(filter.pageNumber - 1, pageSize); sortOrder = getSortOrder(filter.sortOrder); @@ -113,7 +117,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para Long totalCount = null; List extensions; if (!extensionIds.isEmpty()) { - extensions = repositories.findAllActiveExtensionDTOsByPublicId(extensionIds); + extensions = repositories.findAllActiveExtensionDTOs(extensionIds, targetPlatform); } else if (!extensionNames.isEmpty()) { extensions = extensionNames.stream() .map(name -> name.split("\\.")) @@ -121,7 +125,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para .map(split -> { var name = split[1]; var namespaceName = split[0]; - return repositories.findActiveExtensionDTOByNameAndNamespaceName(name, namespaceName); + return repositories.findActiveExtensionDTO(name, namespaceName, targetPlatform); }) .filter(Objects::nonNull) .collect(Collectors.toList()); @@ -130,7 +134,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para } else { try { var offset = pageRequest.getPageNumber() * pageRequest.getPageSize(); - var searchOptions = new SearchUtilService.Options(queryString, category, pageRequest.getPageSize(), + var searchOptions = new SearchUtilService.Options(queryString, category, targetPlatform, pageRequest.getPageSize(), offset, sortOrder, sortBy, false); var searchResult = search.search(searchOptions, pageRequest); @@ -139,7 +143,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para .map(hit -> hit.getContent().id) .collect(Collectors.toList()); - var extensionsMap = repositories.findAllActiveExtensionDTOsById(ids).stream() + var extensionsMap = repositories.findAllActiveExtensionDTOs(ids).stream() .collect(Collectors.toMap(e -> e.getId(), e -> e)); // keep the same order as search results @@ -170,7 +174,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para .thenComparing(ExtensionVersionDTO::getTimestamp) .reversed(); - extensionVersions = repositories.findAllActiveExtensionVersionDTOsByExtensionId(idMap.keySet()).stream() + extensionVersions = repositories.findAllActiveExtensionVersionDTOs(idMap.keySet(), targetPlatform).stream() .map(ev -> { ev.setExtension(idMap.get(ev.getExtensionId())); return ev; @@ -277,10 +281,11 @@ public ResponseEntity getAsset(HttpServletRequest request, @PathVariable String namespace, @PathVariable String extensionName, @PathVariable String version, - @PathVariable String assetType) { + @PathVariable String assetType, + @RequestParam(defaultValue = ExtensionTargetPlatform.NAME_ANY) String targetPlatform) { var restOfTheUrl = UrlUtil.extractWildcardPath(request); var asset = (restOfTheUrl != null && restOfTheUrl.length() > 0) ? (assetType + "/" + restOfTheUrl) : assetType; - var extVersion = repositories.findVersion(version, extensionName, namespace); + var extVersion = repositories.findVersion(version, targetPlatform, extensionName, namespace); if (extVersion == null || !extVersion.isActive()) throw new NotFoundException(); var resource = getFileFromDB(extVersion, asset); @@ -339,9 +344,9 @@ public ModelAndView getItemUrl(@RequestParam String itemName, ModelMap model) { @GetMapping("/vscode/gallery/publishers/{namespace}/vsextensions/{extension}/{version}/vspackage") @CrossOrigin public ModelAndView download(@PathVariable String namespace, @PathVariable String extension, - @PathVariable String version, ModelMap model) { + @PathVariable String version, @RequestParam(required = false) String targetPlatform, ModelMap model) { if (googleStorage.isEnabled()) { - var extVersion = repositories.findVersion(version, extension, namespace); + var extVersion = repositories.findVersion(version, targetPlatform, extension, namespace); if (extVersion == null || !extVersion.isActive()) throw new NotFoundException(); var resource = repositories.findFileByType(extVersion, FileResource.DOWNLOAD); @@ -352,8 +357,10 @@ public ModelAndView download(@PathVariable String namespace, @PathVariable Strin return new ModelAndView("redirect:" + storageUtil.getLocation(resource), model); } } - var serverUrl = UrlUtil.getBaseUrl(); - return new ModelAndView("redirect:" + UrlUtil.createApiUrl(serverUrl, "vscode", "asset", namespace, extension, version, FILE_VSIX), model); + + var apiUrl = UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "vscode", "asset", namespace, extension, version, FILE_VSIX); + apiUrl = UrlUtil.addQuery(apiUrl, "targetPlatform", targetPlatform); + return new ModelAndView("redirect:" + apiUrl, model); } private ExtensionQueryResult.Extension toQueryExtension(ExtensionDTO extension, Map activeReviewCounts, int flags) { @@ -405,6 +412,7 @@ private ExtensionQueryResult.ExtensionVersion toQueryVersion( var queryVer = new ExtensionQueryResult.ExtensionVersion(); queryVer.version = extVer.getVersion(); queryVer.lastUpdated = extVer.getTimestamp().toString(); + queryVer.targetPlatform = extVer.getTargetPlatformName(); var serverUrl = UrlUtil.getBaseUrl(); var namespaceName = extVer.getExtension().getNamespace().getName(); var extensionName = extVer.getExtension().getName(); diff --git a/server/src/main/java/org/eclipse/openvsx/dto/ExtensionDTO.java b/server/src/main/java/org/eclipse/openvsx/dto/ExtensionDTO.java index b117653f0..9cceb3a2a 100644 --- a/server/src/main/java/org/eclipse/openvsx/dto/ExtensionDTO.java +++ b/server/src/main/java/org/eclipse/openvsx/dto/ExtensionDTO.java @@ -67,7 +67,8 @@ public ExtensionDTO( String latestGalleryColor, String latestGalleryTheme, String latestDependencies, - String latestBundledExtensions + String latestBundledExtensions, + String latestTargetPlatformName ) { this( id, publicId, name, averageRating, downloadCount, @@ -90,7 +91,8 @@ public ExtensionDTO( latestGalleryColor, latestGalleryTheme, latestDependencies, - latestBundledExtensions + latestBundledExtensions, + latestTargetPlatformName ); this.latest.setExtension(this); } diff --git a/server/src/main/java/org/eclipse/openvsx/dto/ExtensionVersionDTO.java b/server/src/main/java/org/eclipse/openvsx/dto/ExtensionVersionDTO.java index 3bac020d5..7950e7cf7 100644 --- a/server/src/main/java/org/eclipse/openvsx/dto/ExtensionVersionDTO.java +++ b/server/src/main/java/org/eclipse/openvsx/dto/ExtensionVersionDTO.java @@ -49,6 +49,7 @@ public class ExtensionVersionDTO { private String qna; private final List dependencies; private final List bundledExtensions; + private final String targetPlatformName; public ExtensionVersionDTO( long namespaceId, @@ -86,7 +87,8 @@ public ExtensionVersionDTO( String galleryTheme, String qna, String dependencies, - String bundledExtensions + String bundledExtensions, + String targetPlatformName ) { this( extensionId, @@ -104,7 +106,8 @@ public ExtensionVersionDTO( galleryColor, galleryTheme, dependencies, - bundledExtensions + bundledExtensions, + targetPlatformName ); this.extension = new ExtensionDTO( @@ -147,7 +150,8 @@ public ExtensionVersionDTO( String galleryColor, String galleryTheme, String dependencies, - String bundledExtensions + String bundledExtensions, + String targetPlatformName ) { var toList = new ListOfStringConverter(); @@ -167,6 +171,7 @@ public ExtensionVersionDTO( this.galleryTheme = galleryTheme; this.dependencies = toList.convertToEntityAttribute(dependencies); this.bundledExtensions = toList.convertToEntityAttribute(bundledExtensions); + this.targetPlatformName = targetPlatformName; } private List toExtensionReferenceJson(List extensionReferences) { @@ -346,4 +351,6 @@ public List getDependencies() { public List getBundledExtensions() { return bundledExtensions; } + + public String getTargetPlatformName() { return targetPlatformName; } } 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 79acea884..0c4a958a7 100644 --- a/server/src/main/java/org/eclipse/openvsx/eclipse/PublisherComplianceChecker.java +++ b/server/src/main/java/org/eclipse/openvsx/eclipse/PublisherComplianceChecker.java @@ -97,10 +97,11 @@ private void deactivateExtensions(Streamable accessTokens) for (var version : versions) { version.setActive(false); entityManager.merge(version); - affectedExtensions.add(version.getExtension()); + affectedExtensions.add(version.getTargetPlatform().getExtension()); logger.info("Deactivated: " + accessToken.getUser().getLoginName() + " - " - + version.getExtension().getNamespace().getName() - + "." + version.getExtension().getName() + + version.getTargetPlatform().getExtension().getNamespace().getName() + + "." + version.getTargetPlatform().getExtension().getName() + + (version.getTargetPlatform().getName().isEmpty() ? "" : " " + version.getTargetPlatform().getName()) + " v" + version.getVersion()); } } diff --git a/server/src/main/java/org/eclipse/openvsx/entities/Extension.java b/server/src/main/java/org/eclipse/openvsx/entities/Extension.java index 723996898..149d7bc58 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/Extension.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/Extension.java @@ -10,7 +10,11 @@ package org.eclipse.openvsx.entities; import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import javax.persistence.Column; import javax.persistence.Entity; @@ -44,13 +48,7 @@ public class Extension { Namespace namespace; @OneToMany(mappedBy = "extension") - List versions; - - @OneToOne - ExtensionVersion latest; - - @OneToOne - ExtensionVersion preview; + List targetPlatforms; boolean active; @@ -58,7 +56,6 @@ public class Extension { int downloadCount; - /** * Convert to a search entity for Elasticsearch. */ @@ -70,7 +67,14 @@ public ExtensionSearch toSearch() { search.extensionId = search.namespace + "." + search.name; search.averageRating = this.getAverageRating(); search.downloadCount = this.getDownloadCount(); - var extVer = this.getLatest(); + search.targetPlatforms = this.getTargetPlatforms().stream().map(ExtensionTargetPlatform::getName).collect(Collectors.toList()); + + var extVer = this.getTargetPlatforms().stream() + .filter(p -> p.getName().equals(ExtensionTargetPlatform.NAME_ANY)) + .map(ExtensionTargetPlatform::getLatest) + .findFirst() + .orElse(this.getTargetPlatforms().get(0).getLatest()); + search.displayName = extVer.getDisplayName(); search.description = extVer.getDescription(); search.timestamp = extVer.getTimestamp().toEpochSecond(ZoneOffset.UTC); @@ -106,31 +110,19 @@ public void setName(String name) { public Namespace getNamespace() { return namespace; } - - public List getVersions() { - return versions; + + public List getTargetPlatforms() { + if(targetPlatforms == null) { + targetPlatforms = new ArrayList<>(); + } + + return targetPlatforms; } public void setNamespace(Namespace namespace) { this.namespace = namespace; } - public ExtensionVersion getLatest() { - return latest; - } - - public void setLatest(ExtensionVersion latest) { - this.latest = latest; - } - - public ExtensionVersion getPreview() { - return preview; - } - - public void setPreview(ExtensionVersion preview) { - this.preview = preview; - } - public boolean isActive() { return active; } @@ -154,5 +146,4 @@ public int getDownloadCount() { public void setDownloadCount(int downloadCount) { this.downloadCount = downloadCount; } - } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionTargetPlatform.java b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionTargetPlatform.java new file mode 100644 index 000000000..e7a45ab33 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionTargetPlatform.java @@ -0,0 +1,107 @@ +package org.eclipse.openvsx.entities; + +import org.eclipse.openvsx.search.ExtensionSearch; + +import javax.persistence.*; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Entity +@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "extension_id", "name" })}) +public class ExtensionTargetPlatform { + + public static final String NAME_ANY = ""; + public static final String NAME_WIN32_X64 = "win32-x64"; + public static final String NAME_WIN32_IA32 = "win32-ia32"; + public static final String NAME_WIN32_ARM64 = "win32-arm64"; + public static final String NAME_LINUX_X64 = "linux-x64"; + public static final String NAME_LINUX_ARM64 = "linux-arm64"; + public static final String NAME_LINUX_ARMHF = "linux-armhf"; + public static final String NAME_ALPINE_X64 = "alpine-x64"; + public static final String NAME_ALPINE_ARM64 = "alpine-arm64"; + public static final String NAME_DARWIN_X64 = "darwin-x64"; + public static final String NAME_DARWIN_ARM64 = "darwin-arm64"; + public static final String NAME_WEB = "web"; + + public static final String NAMES_PATH_PARAM_REGEX = + NAME_WIN32_X64 + "|" + NAME_WIN32_IA32 + "|" + NAME_WIN32_ARM64 + "|" + + NAME_LINUX_X64 + "|" + NAME_LINUX_ARM64 + "|" + NAME_LINUX_ARMHF + "|" + + NAME_ALPINE_X64 + "|" + NAME_ALPINE_ARM64 + "|" + + NAME_DARWIN_X64 + "|" + NAME_DARWIN_ARM64 + "|" + + NAME_WEB; + + public static final String NAMES_PARAM_METADATA = + NAME_WIN32_X64 + "," + NAME_WIN32_IA32 + "," + NAME_WIN32_ARM64 + "," + + NAME_LINUX_X64 + "," + NAME_LINUX_ARM64 + "," + NAME_LINUX_ARMHF + "," + + NAME_ALPINE_X64 + "," + NAME_ALPINE_ARM64 + "," + + NAME_DARWIN_X64 + "," + NAME_DARWIN_ARM64 + "," + + NAME_WEB; + + @Id + @GeneratedValue + long id; + + String name; + + @ManyToOne + Extension extension; + + @OneToMany(mappedBy = "targetPlatform") + List versions; + + @OneToOne + ExtensionVersion latest; + + @OneToOne + ExtensionVersion preview; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Extension getExtension() { + return extension; + } + + public void setExtension(Extension extension) { + this.extension = extension; + } + + public List getVersions() { + if(versions == null) { + versions = new ArrayList<>(); + } + + return versions; + } + + public ExtensionVersion getLatest() { + return latest; + } + + public void setLatest(ExtensionVersion latest) { + this.latest = latest; + } + + public ExtensionVersion getPreview() { + return preview; + } + + public void setPreview(ExtensionVersion preview) { + this.preview = preview; + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java index 308a02700..fe62d8529 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/ExtensionVersion.java @@ -26,7 +26,7 @@ import org.eclipse.openvsx.util.TimeUtil; @Entity -@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "extension_id", "version" })}) +@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "target_platform_id", "version" })}) public class ExtensionVersion { public static final Comparator SORT_COMPARATOR = @@ -39,10 +39,10 @@ public class ExtensionVersion { long id; @ManyToOne - Extension extension; + ExtensionTargetPlatform targetPlatform; @OneToOne(mappedBy = "latest", fetch = FetchType.LAZY) - Extension latestInverse; + ExtensionTargetPlatform latestInverse; String version; @@ -112,7 +112,9 @@ public class ExtensionVersion { */ public ExtensionJson toExtensionJson() { var json = new ExtensionJson(); - var extension = this.getExtension(); + var targetPlatform = this.getTargetPlatform(); + json.targetPlatform = targetPlatform.getName(); + var extension = targetPlatform.getExtension(); json.namespace = extension.getNamespace().getName(); json.name = extension.getName(); json.averageRating = extension.getAverageRating(); @@ -167,7 +169,8 @@ private List toExtensionReferenceJson(List exten */ public SearchEntryJson toSearchEntryJson() { var entry = new SearchEntryJson(); - var extension = this.getExtension(); + var targetPlatform = this.getTargetPlatform(); + var extension = targetPlatform.getExtension(); entry.name = extension.getName(); entry.namespace = extension.getNamespace().getName(); entry.averageRating = extension.getAverageRating(); @@ -201,12 +204,12 @@ public void setId(long id) { this.id = id; } - public Extension getExtension() { - return extension; + public ExtensionTargetPlatform getTargetPlatform() { + return targetPlatform; } - public void setExtension(Extension extension) { - this.extension = extension; + public void setTargetPlatform(ExtensionTargetPlatform targetPlatform) { + this.targetPlatform = targetPlatform; } public String getVersion() { diff --git a/server/src/main/java/org/eclipse/openvsx/json/ExtensionJson.java b/server/src/main/java/org/eclipse/openvsx/json/ExtensionJson.java index a86bbf3bb..7c2214cc4 100644 --- a/server/src/main/java/org/eclipse/openvsx/json/ExtensionJson.java +++ b/server/src/main/java/org/eclipse/openvsx/json/ExtensionJson.java @@ -53,6 +53,9 @@ public static ExtensionJson error(String message) { @NotNull public String namespace; + @ApiModelProperty("Name of the target platform") + public String targetPlatform; + @ApiModelProperty("Selected version, or the latest version if none was specified") @NotNull public String version; @@ -150,4 +153,9 @@ public static ExtensionJson error(String message) { @ApiModelProperty("List of extensions bundled with this extension") public List bundledExtensions; + @ApiModelProperty("Map of download links by target platform") + public Map downloads; + + @ApiModelProperty("Map of target platforms by extension version") + public Map> allTargetPlatformVersions; } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/json/QueryParamJson.java b/server/src/main/java/org/eclipse/openvsx/json/QueryParamJson.java index eabcf93e3..443f9ef19 100644 --- a/server/src/main/java/org/eclipse/openvsx/json/QueryParamJson.java +++ b/server/src/main/java/org/eclipse/openvsx/json/QueryParamJson.java @@ -42,5 +42,7 @@ public class QueryParamJson { @ApiModelProperty("Whether to include all versions of an extension") public boolean includeAllVersions; - + + @ApiModelProperty("Target architecture") + public String targetPlatform; } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/AdminStatisticCalculationsRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/AdminStatisticCalculationsRepository.java index 43377f1ac..5819e0949 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/AdminStatisticCalculationsRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/AdminStatisticCalculationsRepository.java @@ -76,7 +76,8 @@ public int countActiveExtensionPublishers(LocalDateTime endExclusive) { .from(EXTENSION) .join(extensionState).on(extensionState.ENTITY_ID.eq(EXTENSION.ID).and(extensionState.ENTITY_TYPE.eq(ENTITY_TYPE_EXTENSION))) .join(lastChangedExtensionState).on(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID).eq(extensionState.ENTITY_ID).and(lastChangedExtensionState.field(ALIAS_LAST_CHANGE, LocalDateTime.class).eq(extensionState.TIMESTAMP))) - .join(EXTENSION_VERSION).on(EXTENSION_VERSION.EXTENSION_ID.eq(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID))) + .join(EXTENSION_TARGET_PLATFORM).on(EXTENSION_TARGET_PLATFORM.EXTENSION_ID.eq(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID))) + .join(EXTENSION_VERSION).on(EXTENSION_VERSION.TARGET_PLATFORM_ID.eq(EXTENSION_TARGET_PLATFORM.ID)) .join(extensionVersionState).on(extensionVersionState.ENTITY_ID.eq(EXTENSION_VERSION.ID).and(extensionVersionState.ENTITY_TYPE.eq(ENTITY_TYPE_EXTENSION_VERSION))) .join(lastChangedExtensionVersionState).on(lastChangedExtensionVersionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID).eq(extensionVersionState.ENTITY_ID).and(lastChangedExtensionVersionState.field(ALIAS_LAST_CHANGE, LocalDateTime.class).eq(extensionVersionState.TIMESTAMP))) .join(PERSONAL_ACCESS_TOKEN).on(PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)) @@ -99,7 +100,8 @@ public Map countActiveExtensionPublishersGroupedByExtensionsPu .from(EXTENSION) .join(extensionState).on(extensionState.ENTITY_ID.eq(EXTENSION.ID).and(extensionState.ENTITY_TYPE.eq(ENTITY_TYPE_EXTENSION))) .join(lastChangedExtensionState).on(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID).eq(extensionState.ENTITY_ID).and(lastChangedExtensionState.field(ALIAS_LAST_CHANGE, LocalDateTime.class).eq(extensionState.TIMESTAMP))) - .join(EXTENSION_VERSION).on(EXTENSION_VERSION.EXTENSION_ID.eq(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID))) + .join(EXTENSION_TARGET_PLATFORM).on(EXTENSION_TARGET_PLATFORM.EXTENSION_ID.eq(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID))) + .join(EXTENSION_VERSION).on(EXTENSION_VERSION.TARGET_PLATFORM_ID.eq(EXTENSION_TARGET_PLATFORM.ID)) .join(extensionVersionState).on(extensionVersionState.ENTITY_ID.eq(EXTENSION_VERSION.ID).and(extensionVersionState.ENTITY_TYPE.eq(ENTITY_TYPE_EXTENSION_VERSION))) .join(lastChangedExtensionVersionState).on(lastChangedExtensionVersionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID).eq(extensionVersionState.ENTITY_ID).and(lastChangedExtensionVersionState.field(ALIAS_LAST_CHANGE, LocalDateTime.class).eq(extensionVersionState.TIMESTAMP))) .join(PERSONAL_ACCESS_TOKEN).on(PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)) @@ -189,7 +191,8 @@ public int countPublishersThatClaimedNamespaceOwnership(LocalDateTime endExclusi .from(EXTENSION) .join(extensionState).on(extensionState.ENTITY_ID.eq(EXTENSION.ID).and(extensionState.ENTITY_TYPE.eq(ENTITY_TYPE_EXTENSION))) .join(lastChangedExtensionState).on(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID).eq(extensionState.ENTITY_ID).and(lastChangedExtensionState.field(ALIAS_LAST_CHANGE, LocalDateTime.class).eq(extensionState.TIMESTAMP))) - .join(EXTENSION_VERSION).on(EXTENSION_VERSION.EXTENSION_ID.eq(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID))) + .join(EXTENSION_TARGET_PLATFORM).on(EXTENSION_TARGET_PLATFORM.EXTENSION_ID.eq(lastChangedExtensionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID))) + .join(EXTENSION_VERSION).on(EXTENSION_VERSION.TARGET_PLATFORM_ID.eq(EXTENSION_TARGET_PLATFORM.ID)) .join(extensionVersionState).on(extensionVersionState.ENTITY_ID.eq(EXTENSION_VERSION.ID).and(extensionVersionState.ENTITY_TYPE.eq(ENTITY_TYPE_EXTENSION_VERSION))) .join(lastChangedExtensionVersionState).on(lastChangedExtensionVersionState.field(ENTITY_ACTIVE_STATE.ENTITY_ID).eq(extensionVersionState.ENTITY_ID).and(lastChangedExtensionVersionState.field(ALIAS_LAST_CHANGE, LocalDateTime.class).eq(extensionVersionState.TIMESTAMP))) .join(PERSONAL_ACCESS_TOKEN).on(PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)) diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionDTORepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionDTORepository.java index 8f1ece47f..ab2484703 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionDTORepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionDTORepository.java @@ -35,14 +35,19 @@ public List findAllActiveById(Collection ids) { return fetch(findAllActive().and(EXTENSION.ID.in(ids))); } - public List findAllActiveByPublicId(Collection publicIds) { - return fetch(findAllActive().and(EXTENSION.PUBLIC_ID.in(publicIds))); + public List findAllActiveByPublicIdAndTargetPlatform(Collection publicIds, String targetPlatform) { + var query = findAllActive() + .and(EXTENSION.PUBLIC_ID.in(publicIds)) + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)); + + return fetch(query); } - public ExtensionDTO findActiveByNameIgnoreCaseAndNamespaceNameIgnoreCase(String name, String namespaceName) { + public ExtensionDTO findActiveByNameIgnoreCaseAndNamespaceNameIgnoreCaseAndTargetPlatform(String name, String namespaceName, String targetPlatform) { return findAllActive() .and(DSL.upper(EXTENSION.NAME).eq(DSL.upper(name))) .and(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName))) + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) .fetchOneInto(ExtensionDTO.class); } @@ -80,11 +85,13 @@ private SelectConditionStep findAllActive() { latest.GALLERY_COLOR, latest.GALLERY_THEME, latest.DEPENDENCIES, - latest.BUNDLED_EXTENSIONS + latest.BUNDLED_EXTENSIONS, + EXTENSION_TARGET_PLATFORM.NAME ) - .from(EXTENSION) - .join(NAMESPACE).on(NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)) - .join(latest).on(latest.ID.eq(EXTENSION.LATEST_ID)) + .from(NAMESPACE) + .join(EXTENSION).on(EXTENSION.NAMESPACE_ID.eq(NAMESPACE.ID)) + .join(EXTENSION_TARGET_PLATFORM).on(EXTENSION_TARGET_PLATFORM.EXTENSION_ID.eq(EXTENSION.ID)) + .join(latest).on(latest.ID.eq(EXTENSION_TARGET_PLATFORM.LATEST_ID)) .where(EXTENSION.ACTIVE.eq(true)); } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionTargetPlatformRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionTargetPlatformRepository.java new file mode 100644 index 000000000..11696c447 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionTargetPlatformRepository.java @@ -0,0 +1,9 @@ +package org.eclipse.openvsx.repositories; + +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; +import org.springframework.data.repository.Repository; +import org.springframework.data.util.Streamable; + +public interface ExtensionTargetPlatformRepository extends Repository { + Streamable findByNameAndExtensionActiveTrue(String targetPlatform); +} diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionDTORepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionDTORepository.java index 674eac712..900d9d0a7 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionDTORepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionDTORepository.java @@ -28,9 +28,9 @@ public class ExtensionVersionDTORepository { @Autowired DSLContext dsl; - public List findAllActiveByExtensionId(Collection extensionIds) { + public List findAllActiveByExtensionIdAndTargetPlatform(Collection extensionIds, String targetPlatform) { return dsl.select( - EXTENSION_VERSION.EXTENSION_ID, + EXTENSION_TARGET_PLATFORM.EXTENSION_ID, EXTENSION_VERSION.ID, EXTENSION_VERSION.VERSION, EXTENSION_VERSION.PREVIEW, @@ -45,44 +45,55 @@ public List findAllActiveByExtensionId(Collection ext EXTENSION_VERSION.GALLERY_COLOR, EXTENSION_VERSION.GALLERY_THEME, EXTENSION_VERSION.DEPENDENCIES, - EXTENSION_VERSION.BUNDLED_EXTENSIONS + EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_TARGET_PLATFORM.NAME ) .from(EXTENSION_VERSION) + .join(EXTENSION_TARGET_PLATFORM).on(EXTENSION_TARGET_PLATFORM.ID.eq(EXTENSION_VERSION.TARGET_PLATFORM_ID)) .where(EXTENSION_VERSION.ACTIVE.eq(true)) - .and(EXTENSION_VERSION.EXTENSION_ID.in(extensionIds)) + .and(EXTENSION_TARGET_PLATFORM.EXTENSION_ID.in(extensionIds)) + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) .fetchInto(ExtensionVersionDTO.class); } - List findAllActiveByExtensionPublicId(String extensionPublicId) { - return fetch(findAllActive().and(EXTENSION.PUBLIC_ID.eq(extensionPublicId))); + List findAllActiveByExtensionPublicId(String targetPlatform, String extensionPublicId) { + return fetch(findAllActive() + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) + .and(EXTENSION.PUBLIC_ID.eq(extensionPublicId))); } - List findAllActiveByNamespacePublicId(String namespacePublicId) { - return fetch(findAllActive().and(NAMESPACE.PUBLIC_ID.eq(namespacePublicId))); + List findAllActiveByNamespacePublicId(String targetPlatform, String namespacePublicId) { + return fetch(findAllActive() + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) + .and(NAMESPACE.PUBLIC_ID.eq(namespacePublicId))); } - ExtensionVersionDTO findActiveByVersionAndExtensionNameAndNamespaceName(String extensionVersion, String extensionName, String namespaceName) { + ExtensionVersionDTO findActiveByVersionAndExtensionNameAndNamespaceName(String extensionVersion, String targetPlatform, String extensionName, String namespaceName) { return findAllActive() .and(EXTENSION_VERSION.VERSION.eq(extensionVersion)) + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) .and(DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName))) .and(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName))) .fetchOneInto(ExtensionVersionDTO.class); } - List findAllActiveByExtensionNameAndNamespaceName(String extensionName, String namespaceName) { - var query = findAllActive() + List findAllActiveByExtensionNameAndNamespaceName(String targetPlatform, String extensionName, String namespaceName) { + return fetch(findAllActive() + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) .and(DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName))) - .and(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName))); - - return fetch(query); + .and(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName)))); } - List findAllActiveByNamespaceName(String namespaceName) { - return fetch(findAllActive().and(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName)))); + List findAllActiveByNamespaceName(String targetPlatform, String namespaceName) { + return fetch(findAllActive() + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) + .and(DSL.upper(NAMESPACE.NAME).eq(DSL.upper(namespaceName)))); } - List findAllActiveByExtensionName(String extensionName) { - return fetch(findAllActive().and(DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName)))); + List findAllActiveByExtensionName(String targetPlatform, String extensionName) { + return fetch(findAllActive() + .and(EXTENSION_TARGET_PLATFORM.NAME.eq(targetPlatform)) + .and(DSL.upper(EXTENSION.NAME).eq(DSL.upper(extensionName)))); } private SelectConditionStep findAllActive() { @@ -93,8 +104,8 @@ private SelectConditionStep findAllActive() { EXTENSION.ID, EXTENSION.PUBLIC_ID, EXTENSION.NAME, - EXTENSION.LATEST_ID, - EXTENSION.PREVIEW_ID, + EXTENSION_TARGET_PLATFORM.LATEST_ID, + EXTENSION_TARGET_PLATFORM.PREVIEW_ID, EXTENSION.AVERAGE_RATING, EXTENSION.DOWNLOAD_COUNT, USER_DATA.ID, @@ -122,10 +133,12 @@ private SelectConditionStep findAllActive() { EXTENSION_VERSION.GALLERY_THEME, EXTENSION_VERSION.QNA, EXTENSION_VERSION.DEPENDENCIES, - EXTENSION_VERSION.BUNDLED_EXTENSIONS + EXTENSION_VERSION.BUNDLED_EXTENSIONS, + EXTENSION_TARGET_PLATFORM.NAME ) .from(EXTENSION_VERSION) - .join(EXTENSION).on(EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)) + .join(EXTENSION_TARGET_PLATFORM).on(EXTENSION_TARGET_PLATFORM.ID.eq(EXTENSION_VERSION.TARGET_PLATFORM_ID)) + .join(EXTENSION).on(EXTENSION.ID.eq(EXTENSION_TARGET_PLATFORM.EXTENSION_ID)) .join(NAMESPACE).on(NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)) .leftJoin(PERSONAL_ACCESS_TOKEN).on(PERSONAL_ACCESS_TOKEN.ID.eq(EXTENSION_VERSION.PUBLISHED_WITH_ID)) .join(USER_DATA).on(USER_DATA.ID.eq(PERSONAL_ACCESS_TOKEN.USER_DATA)) 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 7c82c2fc8..292f48fc2 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionRepository.java @@ -9,6 +9,7 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.util.Streamable; @@ -21,15 +22,17 @@ public interface ExtensionVersionRepository extends Repository { - Streamable findByExtension(Extension extension); + Streamable findByTargetPlatformExtension(Extension extension); - Streamable findByExtensionAndActiveTrue(Extension extension); + Streamable findByTargetPlatformExtensionAndActiveTrue(Extension extension); - Streamable findByExtensionAndPreviewAndActiveTrue(Extension extension, boolean preview); + Streamable findByTargetPlatformAndPreviewAndActiveTrue(ExtensionTargetPlatform targetPlatform, boolean preview); - ExtensionVersion findByVersionAndExtension(String version, Extension extension); + ExtensionVersion findByVersionAndTargetPlatformNameAndTargetPlatformExtension(String version, String targetPlatform, Extension extension); - ExtensionVersion findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String extensionName, String namespace); + ExtensionVersion findByVersionAndTargetPlatformNameAndTargetPlatformExtensionNameIgnoreCaseAndTargetPlatformExtensionNamespaceNameIgnoreCase(String version, String targetPlatform, String extensionName, String namespace); + + Streamable findByVersionAndTargetPlatformExtensionNameIgnoreCaseAndTargetPlatformExtensionNamespaceNameIgnoreCase(String version, String extensionName, String namespaceName); @Query("select ev from ExtensionVersion ev where concat(',', ev.bundledExtensions, ',') like concat('%,', ?1, ',%')") Streamable findByBundledExtensions(String extensionId); @@ -43,11 +46,11 @@ public interface ExtensionVersionRepository extends Repository findAll(); - @Query("select ev.version from ExtensionVersion ev where ev.extension = ?1 order by ev.timestamp desc") - Streamable getVersionStrings(Extension extension); + @Query("select ev.version from ExtensionVersion ev where ev.targetPlatform = ?1 order by ev.timestamp desc") + Streamable getVersionStrings(ExtensionTargetPlatform targetPlatform); - @Query("select ev.version from ExtensionVersion ev where ev.extension = ?1 and ev.active is true order by ev.timestamp desc") - Streamable getActiveVersionStrings(Extension extension); + @Query("select ev.version from ExtensionVersion ev where ev.targetPlatform = ?1 and ev.active is true order by ev.timestamp desc") + Streamable getActiveVersionStrings(ExtensionTargetPlatform targetPlatform); @Query("select min(ev.timestamp) from ExtensionVersion ev") LocalDateTime getOldestTimestamp(); 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 df8a46bb8..50fac46fe 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -11,17 +11,14 @@ import org.eclipse.openvsx.dto.*; import org.eclipse.openvsx.entities.*; -import org.jooq.Record2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Streamable; import org.springframework.stereotype.Component; import java.time.LocalDateTime; -import java.util.AbstractMap; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static org.eclipse.openvsx.entities.FileResource.DOWNLOAD; @@ -44,6 +41,7 @@ public class RepositoryService { @Autowired NamespaceMembershipDTORepository namespaceMembershipDTORepo; @Autowired AdminStatisticsRepository adminStatisticsRepo; @Autowired AdminStatisticCalculationsRepository adminStatisticCalculationsRepo; + @Autowired ExtensionTargetPlatformRepository extensionTargetPlatformRepo; public Namespace findNamespace(String name) { return namespaceRepo.findByNameIgnoreCase(name); @@ -89,6 +87,10 @@ public Streamable findAllActiveExtensions() { return extensionRepo.findByActiveTrue(); } + public Streamable findAllActiveExtensionTargetPlatforms(String targetPlatform) { + return extensionTargetPlatformRepo.findByNameAndExtensionActiveTrue(targetPlatform); + } + public long countExtensions() { return extensionRepo.count(); } @@ -101,32 +103,32 @@ public int getMaxExtensionDownloadCount() { return extensionRepo.getMaxDownloadCount(); } - public ExtensionVersion findVersion(String version, Extension extension) { - return extensionVersionRepo.findByVersionAndExtension(version, extension); + public ExtensionVersion findVersion(String version, String targetPlatform, Extension extension) { + return extensionVersionRepo.findByVersionAndTargetPlatformNameAndTargetPlatformExtension(version, targetPlatform, extension); } - public ExtensionVersion findVersion(String version, String extensionName, String namespace) { - return extensionVersionRepo.findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(version, extensionName, namespace); + public ExtensionVersion findVersion(String version, String targetPlatform, String extensionName, String namespace) { + return extensionVersionRepo.findByVersionAndTargetPlatformNameAndTargetPlatformExtensionNameIgnoreCaseAndTargetPlatformExtensionNamespaceNameIgnoreCase(version, targetPlatform, extensionName, namespace); } public Streamable findVersions(Extension extension) { - return extensionVersionRepo.findByExtension(extension); + return extensionVersionRepo.findByTargetPlatformExtension(extension); } public Streamable findActiveVersions(Extension extension) { - return extensionVersionRepo.findByExtensionAndActiveTrue(extension); + return extensionVersionRepo.findByTargetPlatformExtensionAndActiveTrue(extension); } - public Streamable getVersionStrings(Extension extension) { - return extensionVersionRepo.getVersionStrings(extension); + public Streamable getVersionStrings(ExtensionTargetPlatform targetPlatform) { + return extensionVersionRepo.getVersionStrings(targetPlatform); } - public Streamable getActiveVersionStrings(Extension extension) { - return extensionVersionRepo.getActiveVersionStrings(extension); + public Streamable getActiveVersionStrings(ExtensionTargetPlatform targetPlatform) { + return extensionVersionRepo.getActiveVersionStrings(targetPlatform); } - public Streamable findActiveVersions(Extension extension, boolean preview) { - return extensionVersionRepo.findByExtensionAndPreviewAndActiveTrue(extension, preview); + public Streamable findActiveVersions(ExtensionTargetPlatform targetPlatform, boolean preview) { + return extensionVersionRepo.findByTargetPlatformAndPreviewAndActiveTrue(targetPlatform, preview); } public Streamable findBundledExtensionsReference(Extension extension) { @@ -264,44 +266,44 @@ public Streamable findPersistedLogsAfter(LocalDateTime dateTime) { public List findAllSucceededAzureDownloadCountProcessedItemsByNameIn(List names) { return downloadCountRepo.findAllSucceededAzureDownloadCountProcessedItemsByNameIn(names); } - public List findAllActiveExtensionDTOsByPublicId(Collection publicIds) { - return extensionDTORepo.findAllActiveByPublicId(publicIds); + public List findAllActiveExtensionDTOs(Collection publicIds, String targetPlatform) { + return extensionDTORepo.findAllActiveByPublicIdAndTargetPlatform(publicIds, targetPlatform); } - public ExtensionDTO findActiveExtensionDTOByNameAndNamespaceName(String name, String namespaceName) { - return extensionDTORepo.findActiveByNameIgnoreCaseAndNamespaceNameIgnoreCase(name, namespaceName); + public ExtensionDTO findActiveExtensionDTO(String name, String namespaceName, String targetPlatform) { + return extensionDTORepo.findActiveByNameIgnoreCaseAndNamespaceNameIgnoreCaseAndTargetPlatform(name, namespaceName, targetPlatform); } - public List findAllActiveExtensionDTOsById(Collection ids) { + public List findAllActiveExtensionDTOs(Collection ids) { return extensionDTORepo.findAllActiveById(ids); } - public List findAllActiveExtensionVersionDTOsByExtensionId(Collection extensionIds) { - return extensionVersionDTORepo.findAllActiveByExtensionId(extensionIds); + public List findAllActiveExtensionVersionDTOs(Collection extensionIds, String targetPlatform) { + return extensionVersionDTORepo.findAllActiveByExtensionIdAndTargetPlatform(extensionIds, targetPlatform); } - public List findActiveExtensionVersionDTOsByExtensionPublicId(String extensionPublicId) { - return extensionVersionDTORepo.findAllActiveByExtensionPublicId(extensionPublicId); + public List findActiveExtensionVersionDTOsByExtensionPublicId(String targetPlatform, String extensionPublicId) { + return extensionVersionDTORepo.findAllActiveByExtensionPublicId(targetPlatform, extensionPublicId); } - public List findActiveExtensionVersionDTOsByNamespacePublicId(String namespacePublicId) { - return extensionVersionDTORepo.findAllActiveByNamespacePublicId(namespacePublicId); + public List findActiveExtensionVersionDTOsByNamespacePublicId(String targetPlatform, String namespacePublicId) { + return extensionVersionDTORepo.findAllActiveByNamespacePublicId(targetPlatform, namespacePublicId); } - public ExtensionVersionDTO findActiveExtensionVersionDTOByVersion(String extensionVersion, String extensionName, String namespaceName) { - return extensionVersionDTORepo.findActiveByVersionAndExtensionNameAndNamespaceName(extensionVersion, extensionName, namespaceName); + public ExtensionVersionDTO findActiveExtensionVersionDTOByVersion(String extensionVersion, String targetPlatform, String extensionName, String namespaceName) { + return extensionVersionDTORepo.findActiveByVersionAndExtensionNameAndNamespaceName(extensionVersion, targetPlatform, extensionName, namespaceName); } - public List findActiveExtensionVersionDTOsByExtensionName(String extensionName, String namespaceName) { - return extensionVersionDTORepo.findAllActiveByExtensionNameAndNamespaceName(extensionName, namespaceName); + public List findActiveExtensionVersionDTOsByExtensionName(String targetPlatform, String extensionName, String namespaceName) { + return extensionVersionDTORepo.findAllActiveByExtensionNameAndNamespaceName(targetPlatform, extensionName, namespaceName); } - public List findActiveExtensionVersionDTOsByNamespaceName(String namespaceName) { - return extensionVersionDTORepo.findAllActiveByNamespaceName(namespaceName); + public List findActiveExtensionVersionDTOsByNamespaceName(String targetPlatform, String namespaceName) { + return extensionVersionDTORepo.findAllActiveByNamespaceName(targetPlatform, namespaceName); } - public List findActiveExtensionVersionDTOsByExtensionName(String extensionName) { - return extensionVersionDTORepo.findAllActiveByExtensionName(extensionName); + public List findActiveExtensionVersionDTOsByExtensionName(String targetPlatform, String extensionName) { + return extensionVersionDTORepo.findAllActiveByExtensionName(targetPlatform, extensionName); } public List findAllFileResourceDTOsByExtensionVersionIdAndType(Collection extensionVersionIds, Collection types) { @@ -351,4 +353,8 @@ public long downloadsUntil(LocalDateTime endExclusive) { public long downloadsBetween(LocalDateTime startInclusive, LocalDateTime endExclusive) { return adminStatisticCalculationsRepo.downloadsSumByTimestampGreaterThanEqualAndTimestampLessThan(startInclusive, endExclusive); } + + public Streamable findTargetPlatformVersions(String version, String extensionName, String namespaceName) { + return extensionVersionRepo.findByVersionAndTargetPlatformExtensionNameIgnoreCaseAndTargetPlatformExtensionNamespaceNameIgnoreCase(version, extensionName, namespaceName); + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/search/DatabaseSearchService.java b/server/src/main/java/org/eclipse/openvsx/search/DatabaseSearchService.java index 1306cf1b9..abe6e33ff 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/DatabaseSearchService.java +++ b/server/src/main/java/org/eclipse/openvsx/search/DatabaseSearchService.java @@ -10,14 +10,15 @@ package org.eclipse.openvsx.search; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; +import org.eclipse.openvsx.entities.ExtensionVersion; +import org.eclipse.openvsx.util.TargetPlatformValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.util.Streamable; import org.springframework.stereotype.Component; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.SearchHit; @@ -52,32 +53,32 @@ public boolean isEnabled() { @Cacheable("database.search") public SearchHits search(ISearchService.Options options, Pageable pageRequest) { + var targetPlatformName = Optional.ofNullable(options.targetPlatform) + .filter(TargetPlatformValidator::isValid) + .orElse(ExtensionTargetPlatform.NAME_ANY); - // grab all extensions - var matchingExtensions = repositories.findAllActiveExtensions(); + // grab all extension target platforms + var targetPlatforms = repositories.findAllActiveExtensionTargetPlatforms(targetPlatformName); // no extensions in the database - if (matchingExtensions.isEmpty()) { + if (targetPlatforms.isEmpty()) { Aggregations aggregations = new Aggregations(Collections.emptyList()); return new SearchHitsImpl(0,TotalHitsRelation.OFF, 0f, "", Collections.emptyList(), aggregations); } // filter category if (options.category != null) { - matchingExtensions = matchingExtensions.filter(extension -> extension.getLatest().getCategories().stream() - .anyMatch(category -> category.toLowerCase().equals(options.category.toLowerCase()))); + targetPlatforms = targetPlatforms.filter(target -> target.getLatest().getCategories().stream() + .anyMatch(category -> category.equalsIgnoreCase(options.category))); } // filter text if (options.queryString != null) { - matchingExtensions = matchingExtensions.filter(extension -> - // var latest = extension.getLatest(); - extension.getName().toLowerCase().contains(options.queryString.toLowerCase()) - || extension.getNamespace().getName().contains(options.queryString.toLowerCase()) - || (extension.getLatest().getDescription() != null && extension.getLatest().getDescription() - .toLowerCase().contains(options.queryString.toLowerCase())) - || (extension.getLatest().getDisplayName() != null && extension.getLatest().getDisplayName() - .toLowerCase().contains(options.queryString.toLowerCase()))); + targetPlatforms = targetPlatforms.filter(target -> + target.getExtension().getName().toLowerCase().contains(options.queryString.toLowerCase()) + || target.getExtension().getNamespace().getName().contains(options.queryString.toLowerCase()) + || (target.getLatest().getDescription() != null && target.getLatest().getDescription().toLowerCase().contains(options.queryString.toLowerCase())) + || (target.getLatest().getDisplayName() != null && target.getLatest().getDisplayName().toLowerCase().contains(options.queryString.toLowerCase()))); } List sortedExtensions; @@ -90,13 +91,16 @@ public SearchHits search(ISearchService.Options options, Pageab var searchStats = new SearchStats(repositories); // needs to add relevance on extensions - sortedExtensions = new ArrayList<>(matchingExtensions + sortedExtensions = new ArrayList<>(targetPlatforms + .map(ExtensionTargetPlatform::getExtension) .map(extension -> relevanceService.toSearchEntry(extension, searchStats)) .toList()); // sort it sortedExtensions.sort(new RelevanceComparator()); } else { - sortedExtensions = matchingExtensions.stream().map(extension -> extension.toSearch()) + sortedExtensions = targetPlatforms.stream() + .map(ExtensionTargetPlatform::getExtension) + .map(Extension::toSearch) .collect(Collectors.toList()); if ("downloadCount".equals(options.sortBy)) { sortedExtensions.sort(new DownloadedCountComparator()); @@ -154,6 +158,13 @@ public SearchHits search(ISearchService.Options options, Pageab return searchHitsResult; } + private Optional findLatest(Extension extension, String targetPlatform) { + return extension.getTargetPlatforms().stream() + .filter(p -> targetPlatform.equals(p.getName())) + .findFirst() + .map(ExtensionTargetPlatform::getLatest); + } + /** * Clear the cache when asked to update the search index. It could be done also * through a cron job as well diff --git a/server/src/main/java/org/eclipse/openvsx/search/ElasticSearchService.java b/server/src/main/java/org/eclipse/openvsx/search/ElasticSearchService.java index b80cd7740..654179410 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/ElasticSearchService.java +++ b/server/src/main/java/org/eclipse/openvsx/search/ElasticSearchService.java @@ -9,8 +9,10 @@ ********************************************************************************/ package org.eclipse.openvsx.search; +import java.util.List; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; import com.google.common.base.Strings; @@ -224,6 +226,10 @@ public SearchHits search(Options options, Pageable pageRequest) // Filter by selected category queryBuilder.withFilter(QueryBuilders.matchPhraseQuery("categories", options.category)); } + if (!Strings.isNullOrEmpty(options.targetPlatform)) { + // Filter by selected target platform + queryBuilder.withFilter(QueryBuilders.matchPhraseQuery("targetPlatforms", options.targetPlatform)); + } // Sort search results according to 'sortOrder' and 'sortBy' options sortResults(queryBuilder, options.sortOrder, options.sortBy); diff --git a/server/src/main/java/org/eclipse/openvsx/search/ExtensionSearch.java b/server/src/main/java/org/eclipse/openvsx/search/ExtensionSearch.java index 9a041c835..e57edd014 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/ExtensionSearch.java +++ b/server/src/main/java/org/eclipse/openvsx/search/ExtensionSearch.java @@ -33,6 +33,8 @@ public class ExtensionSearch { @Field(index = false) public String extensionId; + public List targetPlatforms; + public String displayName; public String description; @@ -51,5 +53,4 @@ public class ExtensionSearch { public List categories; public List tags; - } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/search/ISearchService.java b/server/src/main/java/org/eclipse/openvsx/search/ISearchService.java index c0288e801..f3f8ed3df 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/ISearchService.java +++ b/server/src/main/java/org/eclipse/openvsx/search/ISearchService.java @@ -51,16 +51,18 @@ public interface ISearchService { public static class Options { public final String queryString; public final String category; + public final String targetPlatform; public final int requestedSize; public final int requestedOffset; public final String sortOrder; public final String sortBy; public final boolean includeAllVersions; - public Options(String queryString, String category, int size, int offset, String sortOrder, - String sortBy, boolean includeAllVersions) { + public Options(String queryString, String category, String targetPlatform, int size, int offset, + String sortOrder, String sortBy, boolean includeAllVersions) { this.queryString = queryString; this.category = category; + this.targetPlatform = targetPlatform; this.requestedSize = size; this.requestedOffset = offset; this.sortOrder = sortOrder; @@ -79,6 +81,8 @@ public boolean equals(Object obj) { return false; if (!Objects.equals(this.category, other.category)) return false; + if (!Objects.equals(this.targetPlatform, other.targetPlatform)) + return false; if (this.requestedSize != other.requestedSize) return false; if (this.requestedOffset != other.requestedOffset) @@ -94,8 +98,8 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(queryString, category, requestedSize, requestedOffset, sortOrder, sortBy, - includeAllVersions); + return Objects.hash(queryString, category, targetPlatform, requestedSize, requestedOffset, + sortOrder, sortBy, includeAllVersions); } } diff --git a/server/src/main/java/org/eclipse/openvsx/search/RelevanceService.java b/server/src/main/java/org/eclipse/openvsx/search/RelevanceService.java index e6ed0f67c..a34e84a06 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/RelevanceService.java +++ b/server/src/main/java/org/eclipse/openvsx/search/RelevanceService.java @@ -12,12 +12,14 @@ import java.time.Duration; import java.time.LocalDateTime; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.openvsx.entities.Extension; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.NamespaceMembership; import org.eclipse.openvsx.repositories.RepositoryService; @@ -87,13 +89,20 @@ protected ExtensionSearch toSearchEntry(Extension extension, SearchStats stats) ratingValue = (entry.averageRating / 5.0) * countRelevance; } var downloadsValue = entry.downloadCount / stats.downloadRef; - var timestamp = extension.getLatest().getTimestamp(); + + var latest = extension.getTargetPlatforms().stream() + .filter(p -> p.getName().equals(ExtensionTargetPlatform.NAME_ANY)) + .map(ExtensionTargetPlatform::getLatest) + .findFirst() + .orElse(extension.getTargetPlatforms().get(0).getLatest()); + + var timestamp = latest.getTimestamp(); var timestampValue = Duration.between(stats.oldest, timestamp).toSeconds() / stats.timestampRef; entry.relevance = ratingRelevance * limit(ratingValue) + downloadsRelevance * limit(downloadsValue) + timestampRelevance * limit(timestampValue); // Reduce the relevance value of unverified extensions - if (!isVerified(extension.getLatest())) { + if (!isVerified(latest)) { entry.relevance *= unverifiedRelevance; } @@ -127,7 +136,7 @@ private boolean isVerified(ExtensionVersion extVersion) { if (extVersion.getPublishedWith() == null) return false; var user = extVersion.getPublishedWith().getUser(); - var namespace = extVersion.getExtension().getNamespace(); + var namespace = extVersion.getTargetPlatform().getExtension().getNamespace(); return repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER) > 0 && repositories.countMemberships(user, namespace) > 0; } 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 d6b64b984..a36b48098 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java @@ -25,6 +25,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.FileResource; import org.springframework.beans.factory.annotation.Autowired; @@ -131,12 +132,16 @@ public URI getLocation(FileResource resource) { protected String getBlobName(String name, ExtensionVersion extVersion) { Preconditions.checkNotNull(name); try { - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); - return namespace.getName() - + "/" + extension.getName() - + "/" + URLEncoder.encode(extVersion.getVersion(), "UTF-8") - + "/" + name; + var blobName = namespace.getName() + "/" + extension.getName(); + if(!targetPlatform.getName().equals(ExtensionTargetPlatform.NAME_ANY)) { + blobName += "/" + targetPlatform.getName(); + } + + blobName += "/" + URLEncoder.encode(extVersion.getVersion(), "UTF-8") + "/" + name; + return blobName; } catch (UnsupportedEncodingException exc) { throw new RuntimeException(exc); } 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 a3bb9598d..cb1b9b7ef 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import org.eclipse.openvsx.entities.ExtensionTargetPlatform; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.util.UrlUtil; @@ -106,20 +107,25 @@ public URI getLocation(FileResource resource) { + resource.getName() + ": missing Google bucket id"); } var extVersion = resource.getExtension(); - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); return URI.create(UrlUtil.createApiUrl(BASE_URL, bucketId, namespace.getName(), - extension.getName(), extVersion.getVersion(), resource.getName())); + extension.getName(), targetPlatform.getName(), extVersion.getVersion(), resource.getName())); } protected String getObjectId(String name, ExtensionVersion extVersion) { Preconditions.checkNotNull(name); - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); - return namespace.getName() - + "/" + extension.getName() - + "/" + extVersion.getVersion() - + "/" + name; + var objectId = namespace.getName() + "/" + extension.getName(); + if(!targetPlatform.getName().equals(ExtensionTargetPlatform.NAME_ANY)) { + objectId += "/" + targetPlatform.getName(); + } + + objectId += "/" + extVersion.getVersion() + "/" + name; + return objectId; } } \ No newline at end of file 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 7702ebcbe..ad373834c 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java @@ -143,19 +143,22 @@ public URI getLocation(FileResource resource) { } private String getFileUrl(String name, ExtensionVersion extVersion, String serverUrl) { - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); - return UrlUtil.createApiUrl(serverUrl, "api", namespace.getName(), extension.getName(), extVersion.getVersion(), - "file", name); + return UrlUtil.createApiUrl(serverUrl, "api", namespace.getName(), extension.getName(), + targetPlatform.getName(), extVersion.getVersion(), "file", name); } /** * Adds URLs for the given file types to a map to be used in JSON response data. */ public void addFileUrls(ExtensionVersion extVersion, String serverUrl, Map type2Url, String... types) { - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); - var versionUrl = UrlUtil.createApiUrl(serverUrl, "api", namespace.getName(), extension.getName(), extVersion.getVersion()); + var versionUrl = UrlUtil.createApiUrl(serverUrl, "api", namespace.getName(), extension.getName(), + targetPlatform.getName(), extVersion.getVersion()); var resources = repositories.findFilesByType(extVersion, Arrays.asList(types)); for (var resource : resources) { var fileUrl = UrlUtil.createApiUrl(versionUrl, "file", resource.getName()); @@ -165,9 +168,11 @@ public void addFileUrls(ExtensionVersion extVersion, String serverUrl, Map getWebResourceUrls(ExtensionVersion extVersion, String serverUrl) { var name2Url = new HashMap(); - var extension = extVersion.getExtension(); + var targetPlatform = extVersion.getTargetPlatform(); + var extension = targetPlatform.getExtension(); var namespace = extension.getNamespace(); - var versionUrl = UrlUtil.createApiUrl(serverUrl, "api", namespace.getName(), extension.getName(), extVersion.getVersion()); + var versionUrl = UrlUtil.createApiUrl(serverUrl, "api", namespace.getName(), extension.getName(), + targetPlatform.getName(), extVersion.getVersion()); var resources = repositories.findFilesByType(extVersion, Arrays.asList(WEB_RESOURCE)); if (resources != null) { for (var resource : resources) { @@ -192,7 +197,7 @@ public void increaseDownloadCount(ExtensionVersion extVersion, FileResource reso * Register a package file download by increasing its download count. */ public void increaseDownloadCount(ExtensionVersion extVersion, FileResource resource, List downloadTimes) { - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); extension.setDownloadCount(extension.getDownloadCount() + downloadTimes.size()); for(var time : downloadTimes) { var download = new Download(); diff --git a/server/src/main/java/org/eclipse/openvsx/util/SemanticVersion.java b/server/src/main/java/org/eclipse/openvsx/util/SemanticVersion.java index 0cf85490e..a251ec260 100644 --- a/server/src/main/java/org/eclipse/openvsx/util/SemanticVersion.java +++ b/server/src/main/java/org/eclipse/openvsx/util/SemanticVersion.java @@ -14,6 +14,10 @@ public class SemanticVersion implements Comparable { + // source: https://semver.org/, search for: https://regex101.com/r/vkijKf/1/ + // has been modified to only use non-capturing groups (?:.*), so that it can be used as a URI template regex + public static final String VERSION_PATH_PARAM_REGEX = "(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-(?:(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?:[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"; + private final String original; private final List parts = new ArrayList<>(); diff --git a/server/src/main/java/org/eclipse/openvsx/util/TargetPlatformValidator.java b/server/src/main/java/org/eclipse/openvsx/util/TargetPlatformValidator.java new file mode 100644 index 000000000..bc8863dd3 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/util/TargetPlatformValidator.java @@ -0,0 +1,22 @@ +package org.eclipse.openvsx.util; + +import java.util.List; + +import static org.eclipse.openvsx.entities.ExtensionTargetPlatform.*; + +public class TargetPlatformValidator { + + private final static List TARGET_PLATFORM_NAMES = List.of( + NAME_WIN32_X64, NAME_WIN32_IA32, NAME_WIN32_ARM64, + NAME_LINUX_X64, NAME_LINUX_ARM64, NAME_LINUX_ARMHF, + NAME_ALPINE_X64, NAME_ALPINE_ARM64, + NAME_DARWIN_X64, NAME_DARWIN_ARM64, + NAME_WEB + ); + + private TargetPlatformValidator(){} + + public static boolean isValid(String targetPlatform) { + return TARGET_PLATFORM_NAMES.contains(targetPlatform); + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/web/SitemapController.java b/server/src/main/java/org/eclipse/openvsx/web/SitemapController.java index 8200db8db..91c83f7d4 100644 --- a/server/src/main/java/org/eclipse/openvsx/web/SitemapController.java +++ b/server/src/main/java/org/eclipse/openvsx/web/SitemapController.java @@ -11,6 +11,7 @@ import java.net.URI; import java.time.format.DateTimeFormatter; +import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.xml.parsers.DocumentBuilderFactory; @@ -56,14 +57,17 @@ public ResponseEntity getSitemap() throws ParserConfigura var baseUrl = getBaseUrl(); var timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); repositories.findAllActiveExtensions().forEach(extension -> { - var entry = document.createElement("url"); - var loc = document.createElement("loc"); - loc.setTextContent(UrlUtil.createApiUrl(baseUrl, "extension", extension.getNamespace().getName(), extension.getName())); - entry.appendChild(loc); - var lastmod = document.createElement("lastmod"); - lastmod.setTextContent(extension.getLatest().getTimestamp().format(timestampFormatter)); - entry.appendChild(lastmod); - urlset.appendChild(entry); + for(var targetPlatform : extension.getTargetPlatforms()) { + var entry = document.createElement("url"); + var loc = document.createElement("loc"); + var targetPlatformName = Optional.ofNullable(targetPlatform.getName()).orElse(""); + loc.setTextContent(UrlUtil.createApiUrl(baseUrl, "extension", extension.getNamespace().getName(), extension.getName(), targetPlatformName)); + entry.appendChild(loc); + var lastmod = document.createElement("lastmod"); + lastmod.setTextContent(targetPlatform.getLatest().getTimestamp().format(timestampFormatter)); + entry.appendChild(lastmod); + urlset.appendChild(entry); + } }); StreamingResponseBody stream = out -> { diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Indexes.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Indexes.java index 8da4be5df..21f6d90bf 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Indexes.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Indexes.java @@ -28,14 +28,10 @@ public class Indexes { // INDEX definitions // ------------------------------------------------------------------------- - public static final Index EXTENSION__LATEST_ID__IDX = Internal.createIndex(DSL.name("extension__latest_id__idx"), Extension.EXTENSION, new OrderField[] { Extension.EXTENSION.LATEST_ID }, false); public static final Index EXTENSION__NAMESPACE_ID__IDX = Internal.createIndex(DSL.name("extension__namespace_id__idx"), Extension.EXTENSION, new OrderField[] { Extension.EXTENSION.NAMESPACE_ID }, false); - public static final Index EXTENSION__PREVIEW_ID__IDX = Internal.createIndex(DSL.name("extension__preview_id__idx"), Extension.EXTENSION, new OrderField[] { Extension.EXTENSION.PREVIEW_ID }, false); public static final Index EXTENSION_REVIEW__EXTENSION_ID__IDX = Internal.createIndex(DSL.name("extension_review__extension_id__idx"), ExtensionReview.EXTENSION_REVIEW, new OrderField[] { ExtensionReview.EXTENSION_REVIEW.EXTENSION_ID }, false); public static final Index EXTENSION_REVIEW__USER_ID__IDX = Internal.createIndex(DSL.name("extension_review__user_id__idx"), ExtensionReview.EXTENSION_REVIEW, new OrderField[] { ExtensionReview.EXTENSION_REVIEW.USER_ID }, false); - public static final Index EXTENSION_VERSION__EXTENSION_ID__IDX = Internal.createIndex(DSL.name("extension_version__extension_id__idx"), ExtensionVersion.EXTENSION_VERSION, new OrderField[] { ExtensionVersion.EXTENSION_VERSION.EXTENSION_ID }, false); public static final Index EXTENSION_VERSION__PUBLISHED_WITH_ID__IDX = Internal.createIndex(DSL.name("extension_version__published_with_id__idx"), ExtensionVersion.EXTENSION_VERSION, new OrderField[] { ExtensionVersion.EXTENSION_VERSION.PUBLISHED_WITH_ID }, false); - public static final Index EXTENSION_VERSION_EXT_AND_VER_IDX = Internal.createIndex(DSL.name("extension_version_ext_and_ver_idx"), ExtensionVersion.EXTENSION_VERSION, new OrderField[] { ExtensionVersion.EXTENSION_VERSION.EXTENSION_ID, ExtensionVersion.EXTENSION_VERSION.VERSION }, false); public static final Index FILE_RESOURCE_EXTENSION_IDX = Internal.createIndex(DSL.name("file_resource_extension_idx"), FileResource.FILE_RESOURCE, new OrderField[] { FileResource.FILE_RESOURCE.EXTENSION_ID }, false); public static final Index FLYWAY_SCHEMA_HISTORY_S_IDX = Internal.createIndex(DSL.name("flyway_schema_history_s_idx"), FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY, new OrderField[] { FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY.SUCCESS }, false); public static final Index NAMESPACE_MEMBERSHIP__NAMESPACE__IDX = Internal.createIndex(DSL.name("namespace_membership__namespace__idx"), NamespaceMembership.NAMESPACE_MEMBERSHIP, new OrderField[] { NamespaceMembership.NAMESPACE_MEMBERSHIP.NAMESPACE }, false); diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Keys.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Keys.java index bfad8c7e8..cc6445368 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Keys.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Keys.java @@ -12,6 +12,7 @@ import org.eclipse.openvsx.jooq.tables.EntityActiveState; import org.eclipse.openvsx.jooq.tables.Extension; import org.eclipse.openvsx.jooq.tables.ExtensionReview; +import org.eclipse.openvsx.jooq.tables.ExtensionTargetPlatform; import org.eclipse.openvsx.jooq.tables.ExtensionVersion; import org.eclipse.openvsx.jooq.tables.FileResource; import org.eclipse.openvsx.jooq.tables.FlywaySchemaHistory; @@ -31,6 +32,7 @@ import org.eclipse.openvsx.jooq.tables.records.EntityActiveStateRecord; import org.eclipse.openvsx.jooq.tables.records.ExtensionRecord; import org.eclipse.openvsx.jooq.tables.records.ExtensionReviewRecord; +import org.eclipse.openvsx.jooq.tables.records.ExtensionTargetPlatformRecord; import org.eclipse.openvsx.jooq.tables.records.ExtensionVersionRecord; import org.eclipse.openvsx.jooq.tables.records.FileResourceRecord; import org.eclipse.openvsx.jooq.tables.records.FlywaySchemaHistoryRecord; @@ -67,7 +69,10 @@ public class Keys { public static final UniqueKey EXTENSION_PKEY = Internal.createUniqueKey(Extension.EXTENSION, DSL.name("extension_pkey"), new TableField[] { Extension.EXTENSION.ID }, true); public static final UniqueKey UNIQUE_EXTENSION_PUBLIC_ID = Internal.createUniqueKey(Extension.EXTENSION, DSL.name("unique_extension_public_id"), new TableField[] { Extension.EXTENSION.PUBLIC_ID }, true); public static final UniqueKey EXTENSION_REVIEW_PKEY = Internal.createUniqueKey(ExtensionReview.EXTENSION_REVIEW, DSL.name("extension_review_pkey"), new TableField[] { ExtensionReview.EXTENSION_REVIEW.ID }, true); + public static final UniqueKey EXTENSION_TARGET_PLATFORM_PKEY = Internal.createUniqueKey(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM, DSL.name("extension_target_platform_pkey"), new TableField[] { ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.ID }, true); + public static final UniqueKey UNIQUE_EXTENSION_TARGET_PLATFORM = Internal.createUniqueKey(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM, DSL.name("unique_extension_target_platform"), new TableField[] { ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.EXTENSION_ID, ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.NAME }, true); public static final UniqueKey EXTENSION_VERSION_PKEY = Internal.createUniqueKey(ExtensionVersion.EXTENSION_VERSION, DSL.name("extension_version_pkey"), new TableField[] { ExtensionVersion.EXTENSION_VERSION.ID }, true); + public static final UniqueKey UNIQUE_EXTENSION_VERSION = Internal.createUniqueKey(ExtensionVersion.EXTENSION_VERSION, DSL.name("unique_extension_version"), new TableField[] { ExtensionVersion.EXTENSION_VERSION.TARGET_PLATFORM_ID, ExtensionVersion.EXTENSION_VERSION.VERSION }, true); public static final UniqueKey FILE_RESOURCE_PKEY = Internal.createUniqueKey(FileResource.FILE_RESOURCE, DSL.name("file_resource_pkey"), new TableField[] { FileResource.FILE_RESOURCE.ID }, true); public static final UniqueKey FLYWAY_SCHEMA_HISTORY_PK = Internal.createUniqueKey(FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY, DSL.name("flyway_schema_history_pk"), new TableField[] { FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY.INSTALLED_RANK }, true); public static final UniqueKey NAMESPACE_PKEY = Internal.createUniqueKey(Namespace.NAMESPACE, DSL.name("namespace_pkey"), new TableField[] { Namespace.NAMESPACE.ID }, true); @@ -87,14 +92,14 @@ public class Keys { public static final ForeignKey ADMIN_STATISTICS_EXTENSIONS_BY_RATING__ADMIN_STATISTICS_EXTENSIONS_BY_RATING_FKEY = Internal.createForeignKey(AdminStatisticsExtensionsByRating.ADMIN_STATISTICS_EXTENSIONS_BY_RATING, DSL.name("admin_statistics_extensions_by_rating_fkey"), new TableField[] { AdminStatisticsExtensionsByRating.ADMIN_STATISTICS_EXTENSIONS_BY_RATING.ADMIN_STATISTICS_ID }, Keys.ADMIN_STATISTICS_PKEY, new TableField[] { AdminStatistics.ADMIN_STATISTICS.ID }, true); public static final ForeignKey ADMIN_STATISTICS_PUBLISHERS_BY_EXTENSIONS_PUBLISHED__ADMIN_STATISTICS_PUBLISHERS_BY_EXTENSIONS_PUBLISHED_FKEY = Internal.createForeignKey(AdminStatisticsPublishersByExtensionsPublished.ADMIN_STATISTICS_PUBLISHERS_BY_EXTENSIONS_PUBLISHED, DSL.name("admin_statistics_publishers_by_extensions_published_fkey"), new TableField[] { AdminStatisticsPublishersByExtensionsPublished.ADMIN_STATISTICS_PUBLISHERS_BY_EXTENSIONS_PUBLISHED.ADMIN_STATISTICS_ID }, Keys.ADMIN_STATISTICS_PKEY, new TableField[] { AdminStatistics.ADMIN_STATISTICS.ID }, true); - public static final ForeignKey DOWNLOAD__DOWNLOAD_FILE_RESOURCE_FKEY = Internal.createForeignKey(Download.DOWNLOAD, DSL.name("download_file_resource_fkey"), new TableField[] { Download.DOWNLOAD.FILE_RESOURCE_ID }, Keys.FILE_RESOURCE_PKEY, new TableField[] { FileResource.FILE_RESOURCE.ID }, true); - public static final ForeignKey EXTENSION__EXTENSION_PREVIEW_FKEY = Internal.createForeignKey(Extension.EXTENSION, DSL.name("extension_preview_fkey"), new TableField[] { Extension.EXTENSION.PREVIEW_ID }, Keys.EXTENSION_VERSION_PKEY, new TableField[] { ExtensionVersion.EXTENSION_VERSION.ID }, true); public static final ForeignKey EXTENSION__FK64IMD3NRJ67D50TPKJS94NGMN = Internal.createForeignKey(Extension.EXTENSION, DSL.name("fk64imd3nrj67d50tpkjs94ngmn"), new TableField[] { Extension.EXTENSION.NAMESPACE_ID }, Keys.NAMESPACE_PKEY, new TableField[] { Namespace.NAMESPACE.ID }, true); - public static final ForeignKey EXTENSION__FKEQJ0WVHFFQQVNH4VOKNOHJHTW = Internal.createForeignKey(Extension.EXTENSION, DSL.name("fkeqj0wvhffqqvnh4voknohjhtw"), new TableField[] { Extension.EXTENSION.LATEST_ID }, Keys.EXTENSION_VERSION_PKEY, new TableField[] { ExtensionVersion.EXTENSION_VERSION.ID }, true); public static final ForeignKey EXTENSION_REVIEW__FKGD2DQDC23OGBNOBX8AFJFPNKP = Internal.createForeignKey(ExtensionReview.EXTENSION_REVIEW, DSL.name("fkgd2dqdc23ogbnobx8afjfpnkp"), new TableField[] { ExtensionReview.EXTENSION_REVIEW.EXTENSION_ID }, Keys.EXTENSION_PKEY, new TableField[] { Extension.EXTENSION.ID }, true); public static final ForeignKey EXTENSION_REVIEW__FKINJBN9GRK135Y6IK0UT4UJP0W = Internal.createForeignKey(ExtensionReview.EXTENSION_REVIEW, DSL.name("fkinjbn9grk135y6ik0ut4ujp0w"), new TableField[] { ExtensionReview.EXTENSION_REVIEW.USER_ID }, Keys.USER_DATA_PKEY, new TableField[] { UserData.USER_DATA.ID }, true); + public static final ForeignKey EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_EXTENSION_FKEY = Internal.createForeignKey(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM, DSL.name("extension_target_platform_extension_fkey"), new TableField[] { ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.EXTENSION_ID }, Keys.EXTENSION_PKEY, new TableField[] { Extension.EXTENSION.ID }, true); + public static final ForeignKey EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_LATEST_FKEY = Internal.createForeignKey(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM, DSL.name("extension_target_platform_latest_fkey"), new TableField[] { ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.LATEST_ID }, Keys.EXTENSION_VERSION_PKEY, new TableField[] { ExtensionVersion.EXTENSION_VERSION.ID }, true); + public static final ForeignKey EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_PREVIEW_FKEY = Internal.createForeignKey(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM, DSL.name("extension_target_platform_preview_fkey"), new TableField[] { ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.PREVIEW_ID }, Keys.EXTENSION_VERSION_PKEY, new TableField[] { ExtensionVersion.EXTENSION_VERSION.ID }, true); + public static final ForeignKey EXTENSION_VERSION__EXTENSION_VERSION_TARGET_PLATFORM_FKEY = Internal.createForeignKey(ExtensionVersion.EXTENSION_VERSION, DSL.name("extension_version_target_platform_fkey"), new TableField[] { ExtensionVersion.EXTENSION_VERSION.TARGET_PLATFORM_ID }, Keys.EXTENSION_TARGET_PLATFORM_PKEY, new TableField[] { ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.ID }, true); public static final ForeignKey EXTENSION_VERSION__FK70KHJ8PM0VACASUIIAQ0W0R80 = Internal.createForeignKey(ExtensionVersion.EXTENSION_VERSION, DSL.name("fk70khj8pm0vacasuiiaq0w0r80"), new TableField[] { ExtensionVersion.EXTENSION_VERSION.PUBLISHED_WITH_ID }, Keys.PERSONAL_ACCESS_TOKEN_PKEY, new TableField[] { PersonalAccessToken.PERSONAL_ACCESS_TOKEN.ID }, true); - public static final ForeignKey EXTENSION_VERSION__FKKHS1EC9S9J08FGICQ9PMWU6BT = Internal.createForeignKey(ExtensionVersion.EXTENSION_VERSION, DSL.name("fkkhs1ec9s9j08fgicq9pmwu6bt"), new TableField[] { ExtensionVersion.EXTENSION_VERSION.EXTENSION_ID }, Keys.EXTENSION_PKEY, new TableField[] { Extension.EXTENSION.ID }, true); public static final ForeignKey FILE_RESOURCE__FILE_RESOURCE_EXTENSION_FKEY = Internal.createForeignKey(FileResource.FILE_RESOURCE, DSL.name("file_resource_extension_fkey"), new TableField[] { FileResource.FILE_RESOURCE.EXTENSION_ID }, Keys.EXTENSION_VERSION_PKEY, new TableField[] { ExtensionVersion.EXTENSION_VERSION.ID }, true); public static final ForeignKey NAMESPACE_MEMBERSHIP__FKGFHWHKNULA6DO2N6WYVQETM3N = Internal.createForeignKey(NamespaceMembership.NAMESPACE_MEMBERSHIP, DSL.name("fkgfhwhknula6do2n6wyvqetm3n"), new TableField[] { NamespaceMembership.NAMESPACE_MEMBERSHIP.NAMESPACE }, Keys.NAMESPACE_PKEY, new TableField[] { Namespace.NAMESPACE.ID }, true); public static final ForeignKey NAMESPACE_MEMBERSHIP__FKNSAMEKUTXYWVSB3S1MJDCJKYP = Internal.createForeignKey(NamespaceMembership.NAMESPACE_MEMBERSHIP, DSL.name("fknsamekutxywvsb3s1mjdcjkyp"), new TableField[] { NamespaceMembership.NAMESPACE_MEMBERSHIP.USER_DATA }, Keys.USER_DATA_PKEY, new TableField[] { UserData.USER_DATA.ID }, true); diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Public.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Public.java index ed50f6e32..19da2e086 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Public.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Public.java @@ -15,6 +15,7 @@ import org.eclipse.openvsx.jooq.tables.EntityActiveState; import org.eclipse.openvsx.jooq.tables.Extension; import org.eclipse.openvsx.jooq.tables.ExtensionReview; +import org.eclipse.openvsx.jooq.tables.ExtensionTargetPlatform; import org.eclipse.openvsx.jooq.tables.ExtensionVersion; import org.eclipse.openvsx.jooq.tables.FileResource; import org.eclipse.openvsx.jooq.tables.FlywaySchemaHistory; @@ -85,6 +86,11 @@ public class Public extends SchemaImpl { */ public final ExtensionReview EXTENSION_REVIEW = ExtensionReview.EXTENSION_REVIEW; + /** + * The table public.extension_target_platform. + */ + public final ExtensionTargetPlatform EXTENSION_TARGET_PLATFORM = ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM; + /** * The table public.extension_version. */ @@ -173,6 +179,7 @@ public final List> getTables() { EntityActiveState.ENTITY_ACTIVE_STATE, Extension.EXTENSION, ExtensionReview.EXTENSION_REVIEW, + ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM, ExtensionVersion.EXTENSION_VERSION, FileResource.FILE_RESOURCE, FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY, diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Tables.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Tables.java index 4334e9223..d5c2a6cf5 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Tables.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Tables.java @@ -12,6 +12,7 @@ import org.eclipse.openvsx.jooq.tables.EntityActiveState; import org.eclipse.openvsx.jooq.tables.Extension; import org.eclipse.openvsx.jooq.tables.ExtensionReview; +import org.eclipse.openvsx.jooq.tables.ExtensionTargetPlatform; import org.eclipse.openvsx.jooq.tables.ExtensionVersion; import org.eclipse.openvsx.jooq.tables.FileResource; import org.eclipse.openvsx.jooq.tables.FlywaySchemaHistory; @@ -71,6 +72,11 @@ public class Tables { */ public static final ExtensionReview EXTENSION_REVIEW = ExtensionReview.EXTENSION_REVIEW; + /** + * The table public.extension_target_platform. + */ + public static final ExtensionTargetPlatform EXTENSION_TARGET_PLATFORM = ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM; + /** * The table public.extension_version. */ diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Download.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Download.java index bc25b767b..bef8680f3 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Download.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Download.java @@ -53,9 +53,9 @@ public Class getRecordType() { public final TableField ID = createField(DSL.name("id"), SQLDataType.BIGINT.nullable(false), this, ""); /** - * The column public.download.file_resource_id. + * The column public.download.file_resource_id_not_fk. */ - public final TableField FILE_RESOURCE_ID = createField(DSL.name("file_resource_id"), SQLDataType.BIGINT.nullable(false), this, ""); + public final TableField FILE_RESOURCE_ID_NOT_FK = createField(DSL.name("file_resource_id_not_fk"), SQLDataType.BIGINT.nullable(false), this, ""); /** * The column public.download.timestamp. @@ -115,15 +115,6 @@ public List> getKeys() { return Arrays.>asList(Keys.DOWNLOAD_PKEY); } - @Override - public List> getReferences() { - return Arrays.>asList(Keys.DOWNLOAD__DOWNLOAD_FILE_RESOURCE_FKEY); - } - - public FileResource fileResource() { - return new FileResource(this, Keys.DOWNLOAD__DOWNLOAD_FILE_RESOURCE_FKEY); - } - @Override public Download as(String alias) { return new Download(DSL.name(alias), this); diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Extension.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Extension.java index b99203a88..df1bc3645 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Extension.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/Extension.java @@ -16,7 +16,7 @@ import org.jooq.Index; import org.jooq.Name; import org.jooq.Record; -import org.jooq.Row9; +import org.jooq.Row7; import org.jooq.Schema; import org.jooq.Table; import org.jooq.TableField; @@ -68,21 +68,11 @@ public Class getRecordType() { */ public final TableField NAME = createField(DSL.name("name"), SQLDataType.VARCHAR(255), this, ""); - /** - * The column public.extension.latest_id. - */ - public final TableField LATEST_ID = createField(DSL.name("latest_id"), SQLDataType.BIGINT, this, ""); - /** * The column public.extension.namespace_id. */ public final TableField NAMESPACE_ID = createField(DSL.name("namespace_id"), SQLDataType.BIGINT, this, ""); - /** - * The column public.extension.preview_id. - */ - public final TableField PREVIEW_ID = createField(DSL.name("preview_id"), SQLDataType.BIGINT, this, ""); - /** * The column public.extension.public_id. */ @@ -133,7 +123,7 @@ public Schema getSchema() { @Override public List getIndexes() { - return Arrays.asList(Indexes.EXTENSION__LATEST_ID__IDX, Indexes.EXTENSION__NAMESPACE_ID__IDX, Indexes.EXTENSION__PREVIEW_ID__IDX); + return Arrays.asList(Indexes.EXTENSION__NAMESPACE_ID__IDX); } @Override @@ -148,21 +138,13 @@ public List> getKeys() { @Override public List> getReferences() { - return Arrays.>asList(Keys.EXTENSION__FKEQJ0WVHFFQQVNH4VOKNOHJHTW, Keys.EXTENSION__FK64IMD3NRJ67D50TPKJS94NGMN, Keys.EXTENSION__EXTENSION_PREVIEW_FKEY); - } - - public ExtensionVersion fkeqj0wvhffqqvnh4voknohjhtw() { - return new ExtensionVersion(this, Keys.EXTENSION__FKEQJ0WVHFFQQVNH4VOKNOHJHTW); + return Arrays.>asList(Keys.EXTENSION__FK64IMD3NRJ67D50TPKJS94NGMN); } public Namespace namespace() { return new Namespace(this, Keys.EXTENSION__FK64IMD3NRJ67D50TPKJS94NGMN); } - public ExtensionVersion extensionPreviewFkey() { - return new ExtensionVersion(this, Keys.EXTENSION__EXTENSION_PREVIEW_FKEY); - } - @Override public Extension as(String alias) { return new Extension(DSL.name(alias), this); @@ -190,11 +172,11 @@ public Extension rename(Name name) { } // ------------------------------------------------------------------------- - // Row9 type methods + // Row7 type methods // ------------------------------------------------------------------------- @Override - public Row9 fieldsRow() { - return (Row9) super.fieldsRow(); + public Row7 fieldsRow() { + return (Row7) super.fieldsRow(); } } diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionTargetPlatform.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionTargetPlatform.java new file mode 100644 index 000000000..d57cbbbdd --- /dev/null +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionTargetPlatform.java @@ -0,0 +1,173 @@ +/* + * This file is generated by jOOQ. + */ +package org.eclipse.openvsx.jooq.tables; + + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.openvsx.jooq.Keys; +import org.eclipse.openvsx.jooq.Public; +import org.eclipse.openvsx.jooq.tables.records.ExtensionTargetPlatformRecord; +import org.jooq.Field; +import org.jooq.ForeignKey; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Row5; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.TableOptions; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.jooq.impl.TableImpl; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class ExtensionTargetPlatform extends TableImpl { + + private static final long serialVersionUID = 1L; + + /** + * The reference instance of public.extension_target_platform + */ + public static final ExtensionTargetPlatform EXTENSION_TARGET_PLATFORM = new ExtensionTargetPlatform(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return ExtensionTargetPlatformRecord.class; + } + + /** + * The column public.extension_target_platform.id. + */ + public final TableField ID = createField(DSL.name("id"), SQLDataType.BIGINT.nullable(false), this, ""); + + /** + * The column public.extension_target_platform.name. + */ + public final TableField NAME = createField(DSL.name("name"), SQLDataType.VARCHAR(255), this, ""); + + /** + * The column public.extension_target_platform.extension_id. + */ + public final TableField EXTENSION_ID = createField(DSL.name("extension_id"), SQLDataType.BIGINT, this, ""); + + /** + * The column public.extension_target_platform.latest_id. + */ + public final TableField LATEST_ID = createField(DSL.name("latest_id"), SQLDataType.BIGINT, this, ""); + + /** + * The column public.extension_target_platform.preview_id. + */ + public final TableField PREVIEW_ID = createField(DSL.name("preview_id"), SQLDataType.BIGINT, this, ""); + + private ExtensionTargetPlatform(Name alias, Table aliased) { + this(alias, aliased, null); + } + + private ExtensionTargetPlatform(Name alias, Table aliased, Field[] parameters) { + super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); + } + + /** + * Create an aliased public.extension_target_platform table reference + */ + public ExtensionTargetPlatform(String alias) { + this(DSL.name(alias), EXTENSION_TARGET_PLATFORM); + } + + /** + * Create an aliased public.extension_target_platform table reference + */ + public ExtensionTargetPlatform(Name alias) { + this(alias, EXTENSION_TARGET_PLATFORM); + } + + /** + * Create a public.extension_target_platform table reference + */ + public ExtensionTargetPlatform() { + this(DSL.name("extension_target_platform"), null); + } + + public ExtensionTargetPlatform(Table child, ForeignKey key) { + super(child, key, EXTENSION_TARGET_PLATFORM); + } + + @Override + public Schema getSchema() { + return Public.PUBLIC; + } + + @Override + public UniqueKey getPrimaryKey() { + return Keys.EXTENSION_TARGET_PLATFORM_PKEY; + } + + @Override + public List> getKeys() { + return Arrays.>asList(Keys.EXTENSION_TARGET_PLATFORM_PKEY, Keys.UNIQUE_EXTENSION_TARGET_PLATFORM); + } + + @Override + public List> getReferences() { + return Arrays.>asList(Keys.EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_EXTENSION_FKEY, Keys.EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_LATEST_FKEY, Keys.EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_PREVIEW_FKEY); + } + + public Extension extension() { + return new Extension(this, Keys.EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_EXTENSION_FKEY); + } + + public ExtensionVersion extensionTargetPlatformLatestFkey() { + return new ExtensionVersion(this, Keys.EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_LATEST_FKEY); + } + + public ExtensionVersion extensionTargetPlatformPreviewFkey() { + return new ExtensionVersion(this, Keys.EXTENSION_TARGET_PLATFORM__EXTENSION_TARGET_PLATFORM_PREVIEW_FKEY); + } + + @Override + public ExtensionTargetPlatform as(String alias) { + return new ExtensionTargetPlatform(DSL.name(alias), this); + } + + @Override + public ExtensionTargetPlatform as(Name alias) { + return new ExtensionTargetPlatform(alias, this); + } + + /** + * Rename this table + */ + @Override + public ExtensionTargetPlatform rename(String name) { + return new ExtensionTargetPlatform(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public ExtensionTargetPlatform rename(Name name) { + return new ExtensionTargetPlatform(name, null); + } + + // ------------------------------------------------------------------------- + // Row5 type methods + // ------------------------------------------------------------------------- + + @Override + public Row5 fieldsRow() { + return (Row5) super.fieldsRow(); + } +} diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java index 49d65e6c7..ae860d230 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionVersion.java @@ -118,11 +118,6 @@ public Class getRecordType() { */ public final TableField VERSION = createField(DSL.name("version"), SQLDataType.VARCHAR(255), this, ""); - /** - * The column public.extension_version.extension_id. - */ - public final TableField EXTENSION_ID = createField(DSL.name("extension_id"), SQLDataType.BIGINT, this, ""); - /** * The column public.extension_version.published_with_id. */ @@ -163,6 +158,11 @@ public Class getRecordType() { */ public final TableField EXTENSION_KIND = createField(DSL.name("extension_kind"), SQLDataType.VARCHAR(255), this, ""); + /** + * The column public.extension_version.target_platform_id. + */ + public final TableField TARGET_PLATFORM_ID = createField(DSL.name("target_platform_id"), SQLDataType.BIGINT, this, ""); + private ExtensionVersion(Name alias, Table aliased) { this(alias, aliased, null); } @@ -203,7 +203,7 @@ public Schema getSchema() { @Override public List getIndexes() { - return Arrays.asList(Indexes.EXTENSION_VERSION__EXTENSION_ID__IDX, Indexes.EXTENSION_VERSION__PUBLISHED_WITH_ID__IDX, Indexes.EXTENSION_VERSION_EXT_AND_VER_IDX); + return Arrays.asList(Indexes.EXTENSION_VERSION__PUBLISHED_WITH_ID__IDX); } @Override @@ -213,22 +213,22 @@ public UniqueKey getPrimaryKey() { @Override public List> getKeys() { - return Arrays.>asList(Keys.EXTENSION_VERSION_PKEY); + return Arrays.>asList(Keys.EXTENSION_VERSION_PKEY, Keys.UNIQUE_EXTENSION_VERSION); } @Override public List> getReferences() { - return Arrays.>asList(Keys.EXTENSION_VERSION__FKKHS1EC9S9J08FGICQ9PMWU6BT, Keys.EXTENSION_VERSION__FK70KHJ8PM0VACASUIIAQ0W0R80); - } - - public Extension extension() { - return new Extension(this, Keys.EXTENSION_VERSION__FKKHS1EC9S9J08FGICQ9PMWU6BT); + return Arrays.>asList(Keys.EXTENSION_VERSION__FK70KHJ8PM0VACASUIIAQ0W0R80, Keys.EXTENSION_VERSION__EXTENSION_VERSION_TARGET_PLATFORM_FKEY); } public PersonalAccessToken personalAccessToken() { return new PersonalAccessToken(this, Keys.EXTENSION_VERSION__FK70KHJ8PM0VACASUIIAQ0W0R80); } + public ExtensionTargetPlatform extensionTargetPlatform() { + return new ExtensionTargetPlatform(this, Keys.EXTENSION_VERSION__EXTENSION_VERSION_TARGET_PLATFORM_FKEY); + } + @Override public ExtensionVersion as(String alias) { return new ExtensionVersion(DSL.name(alias), this); diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/DownloadRecord.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/DownloadRecord.java index 70a945a61..9a88a455c 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/DownloadRecord.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/DownloadRecord.java @@ -37,16 +37,16 @@ public Long getId() { } /** - * Setter for public.download.file_resource_id. + * Setter for public.download.file_resource_id_not_fk. */ - public void setFileResourceId(Long value) { + public void setFileResourceIdNotFk(Long value) { set(1, value); } /** - * Getter for public.download.file_resource_id. + * Getter for public.download.file_resource_id_not_fk. */ - public Long getFileResourceId() { + public Long getFileResourceIdNotFk() { return (Long) get(1); } @@ -108,7 +108,7 @@ public Field field1() { @Override public Field field2() { - return Download.DOWNLOAD.FILE_RESOURCE_ID; + return Download.DOWNLOAD.FILE_RESOURCE_ID_NOT_FK; } @Override @@ -128,7 +128,7 @@ public Long component1() { @Override public Long component2() { - return getFileResourceId(); + return getFileResourceIdNotFk(); } @Override @@ -148,7 +148,7 @@ public Long value1() { @Override public Long value2() { - return getFileResourceId(); + return getFileResourceIdNotFk(); } @Override @@ -169,7 +169,7 @@ public DownloadRecord value1(Long value) { @Override public DownloadRecord value2(Long value) { - setFileResourceId(value); + setFileResourceIdNotFk(value); return this; } @@ -208,11 +208,11 @@ public DownloadRecord() { /** * Create a detached, initialised DownloadRecord */ - public DownloadRecord(Long id, Long fileResourceId, LocalDateTime timestamp, Integer amount) { + public DownloadRecord(Long id, Long fileResourceIdNotFk, LocalDateTime timestamp, Integer amount) { super(Download.DOWNLOAD); setId(id); - setFileResourceId(fileResourceId); + setFileResourceIdNotFk(fileResourceIdNotFk); setTimestamp(timestamp); setAmount(amount); } diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionRecord.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionRecord.java index 41f32517d..79f56dcc9 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionRecord.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionRecord.java @@ -7,8 +7,8 @@ import org.eclipse.openvsx.jooq.tables.Extension; import org.jooq.Field; import org.jooq.Record1; -import org.jooq.Record9; -import org.jooq.Row9; +import org.jooq.Record7; +import org.jooq.Row7; import org.jooq.impl.UpdatableRecordImpl; @@ -16,7 +16,7 @@ * This class is generated by jOOQ. */ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) -public class ExtensionRecord extends UpdatableRecordImpl implements Record9 { +public class ExtensionRecord extends UpdatableRecordImpl implements Record7 { private static final long serialVersionUID = 1L; @@ -76,74 +76,46 @@ public String getName() { return (String) get(3); } - /** - * Setter for public.extension.latest_id. - */ - public void setLatestId(Long value) { - set(4, value); - } - - /** - * Getter for public.extension.latest_id. - */ - public Long getLatestId() { - return (Long) get(4); - } - /** * Setter for public.extension.namespace_id. */ public void setNamespaceId(Long value) { - set(5, value); + set(4, value); } /** * Getter for public.extension.namespace_id. */ public Long getNamespaceId() { - return (Long) get(5); - } - - /** - * Setter for public.extension.preview_id. - */ - public void setPreviewId(Long value) { - set(6, value); - } - - /** - * Getter for public.extension.preview_id. - */ - public Long getPreviewId() { - return (Long) get(6); + return (Long) get(4); } /** * Setter for public.extension.public_id. */ public void setPublicId(String value) { - set(7, value); + set(5, value); } /** * Getter for public.extension.public_id. */ public String getPublicId() { - return (String) get(7); + return (String) get(5); } /** * Setter for public.extension.active. */ public void setActive(Boolean value) { - set(8, value); + set(6, value); } /** * Getter for public.extension.active. */ public Boolean getActive() { - return (Boolean) get(8); + return (Boolean) get(6); } // ------------------------------------------------------------------------- @@ -156,17 +128,17 @@ public Record1 key() { } // ------------------------------------------------------------------------- - // Record9 type implementation + // Record7 type implementation // ------------------------------------------------------------------------- @Override - public Row9 fieldsRow() { - return (Row9) super.fieldsRow(); + public Row7 fieldsRow() { + return (Row7) super.fieldsRow(); } @Override - public Row9 valuesRow() { - return (Row9) super.valuesRow(); + public Row7 valuesRow() { + return (Row7) super.valuesRow(); } @Override @@ -191,26 +163,16 @@ public Field field4() { @Override public Field field5() { - return Extension.EXTENSION.LATEST_ID; - } - - @Override - public Field field6() { return Extension.EXTENSION.NAMESPACE_ID; } @Override - public Field field7() { - return Extension.EXTENSION.PREVIEW_ID; - } - - @Override - public Field field8() { + public Field field6() { return Extension.EXTENSION.PUBLIC_ID; } @Override - public Field field9() { + public Field field7() { return Extension.EXTENSION.ACTIVE; } @@ -236,26 +198,16 @@ public String component4() { @Override public Long component5() { - return getLatestId(); - } - - @Override - public Long component6() { return getNamespaceId(); } @Override - public Long component7() { - return getPreviewId(); - } - - @Override - public String component8() { + public String component6() { return getPublicId(); } @Override - public Boolean component9() { + public Boolean component7() { return getActive(); } @@ -281,26 +233,16 @@ public String value4() { @Override public Long value5() { - return getLatestId(); - } - - @Override - public Long value6() { return getNamespaceId(); } @Override - public Long value7() { - return getPreviewId(); - } - - @Override - public String value8() { + public String value6() { return getPublicId(); } @Override - public Boolean value9() { + public Boolean value7() { return getActive(); } @@ -330,36 +272,24 @@ public ExtensionRecord value4(String value) { @Override public ExtensionRecord value5(Long value) { - setLatestId(value); - return this; - } - - @Override - public ExtensionRecord value6(Long value) { setNamespaceId(value); return this; } @Override - public ExtensionRecord value7(Long value) { - setPreviewId(value); - return this; - } - - @Override - public ExtensionRecord value8(String value) { + public ExtensionRecord value6(String value) { setPublicId(value); return this; } @Override - public ExtensionRecord value9(Boolean value) { + public ExtensionRecord value7(Boolean value) { setActive(value); return this; } @Override - public ExtensionRecord values(Long value1, Double value2, Integer value3, String value4, Long value5, Long value6, Long value7, String value8, Boolean value9) { + public ExtensionRecord values(Long value1, Double value2, Integer value3, String value4, Long value5, String value6, Boolean value7) { value1(value1); value2(value2); value3(value3); @@ -367,8 +297,6 @@ public ExtensionRecord values(Long value1, Double value2, Integer value3, String value5(value5); value6(value6); value7(value7); - value8(value8); - value9(value9); return this; } @@ -386,16 +314,14 @@ public ExtensionRecord() { /** * Create a detached, initialised ExtensionRecord */ - public ExtensionRecord(Long id, Double averageRating, Integer downloadCount, String name, Long latestId, Long namespaceId, Long previewId, String publicId, Boolean active) { + public ExtensionRecord(Long id, Double averageRating, Integer downloadCount, String name, Long namespaceId, String publicId, Boolean active) { super(Extension.EXTENSION); setId(id); setAverageRating(averageRating); setDownloadCount(downloadCount); setName(name); - setLatestId(latestId); setNamespaceId(namespaceId); - setPreviewId(previewId); setPublicId(publicId); setActive(active); } diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionTargetPlatformRecord.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionTargetPlatformRecord.java new file mode 100644 index 000000000..87057fc5a --- /dev/null +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionTargetPlatformRecord.java @@ -0,0 +1,254 @@ +/* + * This file is generated by jOOQ. + */ +package org.eclipse.openvsx.jooq.tables.records; + + +import org.eclipse.openvsx.jooq.tables.ExtensionTargetPlatform; +import org.jooq.Field; +import org.jooq.Record1; +import org.jooq.Record5; +import org.jooq.Row5; +import org.jooq.impl.UpdatableRecordImpl; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class ExtensionTargetPlatformRecord extends UpdatableRecordImpl implements Record5 { + + private static final long serialVersionUID = 1L; + + /** + * Setter for public.extension_target_platform.id. + */ + public void setId(Long value) { + set(0, value); + } + + /** + * Getter for public.extension_target_platform.id. + */ + public Long getId() { + return (Long) get(0); + } + + /** + * Setter for public.extension_target_platform.name. + */ + public void setName(String value) { + set(1, value); + } + + /** + * Getter for public.extension_target_platform.name. + */ + public String getName() { + return (String) get(1); + } + + /** + * Setter for public.extension_target_platform.extension_id. + */ + public void setExtensionId(Long value) { + set(2, value); + } + + /** + * Getter for public.extension_target_platform.extension_id. + */ + public Long getExtensionId() { + return (Long) get(2); + } + + /** + * Setter for public.extension_target_platform.latest_id. + */ + public void setLatestId(Long value) { + set(3, value); + } + + /** + * Getter for public.extension_target_platform.latest_id. + */ + public Long getLatestId() { + return (Long) get(3); + } + + /** + * Setter for public.extension_target_platform.preview_id. + */ + public void setPreviewId(Long value) { + set(4, value); + } + + /** + * Getter for public.extension_target_platform.preview_id. + */ + public Long getPreviewId() { + return (Long) get(4); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + @Override + public Record1 key() { + return (Record1) super.key(); + } + + // ------------------------------------------------------------------------- + // Record5 type implementation + // ------------------------------------------------------------------------- + + @Override + public Row5 fieldsRow() { + return (Row5) super.fieldsRow(); + } + + @Override + public Row5 valuesRow() { + return (Row5) super.valuesRow(); + } + + @Override + public Field field1() { + return ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.ID; + } + + @Override + public Field field2() { + return ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.NAME; + } + + @Override + public Field field3() { + return ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.EXTENSION_ID; + } + + @Override + public Field field4() { + return ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.LATEST_ID; + } + + @Override + public Field field5() { + return ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM.PREVIEW_ID; + } + + @Override + public Long component1() { + return getId(); + } + + @Override + public String component2() { + return getName(); + } + + @Override + public Long component3() { + return getExtensionId(); + } + + @Override + public Long component4() { + return getLatestId(); + } + + @Override + public Long component5() { + return getPreviewId(); + } + + @Override + public Long value1() { + return getId(); + } + + @Override + public String value2() { + return getName(); + } + + @Override + public Long value3() { + return getExtensionId(); + } + + @Override + public Long value4() { + return getLatestId(); + } + + @Override + public Long value5() { + return getPreviewId(); + } + + @Override + public ExtensionTargetPlatformRecord value1(Long value) { + setId(value); + return this; + } + + @Override + public ExtensionTargetPlatformRecord value2(String value) { + setName(value); + return this; + } + + @Override + public ExtensionTargetPlatformRecord value3(Long value) { + setExtensionId(value); + return this; + } + + @Override + public ExtensionTargetPlatformRecord value4(Long value) { + setLatestId(value); + return this; + } + + @Override + public ExtensionTargetPlatformRecord value5(Long value) { + setPreviewId(value); + return this; + } + + @Override + public ExtensionTargetPlatformRecord values(Long value1, String value2, Long value3, Long value4, Long value5) { + value1(value1); + value2(value2); + value3(value3); + value4(value4); + value5(value5); + return this; + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached ExtensionTargetPlatformRecord + */ + public ExtensionTargetPlatformRecord() { + super(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM); + } + + /** + * Create a detached, initialised ExtensionTargetPlatformRecord + */ + public ExtensionTargetPlatformRecord(Long id, String name, Long extensionId, Long latestId, Long previewId) { + super(ExtensionTargetPlatform.EXTENSION_TARGET_PLATFORM); + + setId(id); + setName(name); + setExtensionId(extensionId); + setLatestId(latestId); + setPreviewId(previewId); + } +} diff --git a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java index 12fb0de37..456c099d4 100644 --- a/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java +++ b/server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionVersionRecord.java @@ -215,130 +215,130 @@ public String getVersion() { return (String) get(13); } - /** - * Setter for public.extension_version.extension_id. - */ - public void setExtensionId(Long value) { - set(14, value); - } - - /** - * Getter for public.extension_version.extension_id. - */ - public Long getExtensionId() { - return (Long) get(14); - } - /** * Setter for public.extension_version.published_with_id. */ public void setPublishedWithId(Long value) { - set(15, value); + set(14, value); } /** * Getter for public.extension_version.published_with_id. */ public Long getPublishedWithId() { - return (Long) get(15); + return (Long) get(14); } /** * Setter for public.extension_version.active. */ public void setActive(Boolean value) { - set(16, value); + set(15, value); } /** * Getter for public.extension_version.active. */ public Boolean getActive() { - return (Boolean) get(16); + return (Boolean) get(15); } /** * Setter for public.extension_version.dependencies. */ public void setDependencies(String value) { - set(17, value); + set(16, value); } /** * Getter for public.extension_version.dependencies. */ public String getDependencies() { - return (String) get(17); + return (String) get(16); } /** * Setter for public.extension_version.bundled_extensions. */ public void setBundledExtensions(String value) { - set(18, value); + set(17, value); } /** * Getter for public.extension_version.bundled_extensions. */ public String getBundledExtensions() { - return (String) get(18); + return (String) get(17); } /** * Setter for public.extension_version.engines. */ public void setEngines(String value) { - set(19, value); + set(18, value); } /** * Getter for public.extension_version.engines. */ public String getEngines() { - return (String) get(19); + return (String) get(18); } /** * Setter for public.extension_version.categories. */ public void setCategories(String value) { - set(20, value); + set(19, value); } /** * Getter for public.extension_version.categories. */ public String getCategories() { - return (String) get(20); + return (String) get(19); } /** * Setter for public.extension_version.tags. */ public void setTags(String value) { - set(21, value); + set(20, value); } /** * Getter for public.extension_version.tags. */ public String getTags() { - return (String) get(21); + return (String) get(20); } /** * Setter for public.extension_version.extension_kind. */ public void setExtensionKind(String value) { - set(22, value); + set(21, value); } /** * Getter for public.extension_version.extension_kind. */ public String getExtensionKind() { - return (String) get(22); + return (String) get(21); + } + + /** + * Setter for public.extension_version.target_platform_id. + */ + public void setTargetPlatformId(Long value) { + set(22, value); + } + + /** + * Getter for public.extension_version.target_platform_id. + */ + public Long getTargetPlatformId() { + return (Long) get(22); } // ------------------------------------------------------------------------- @@ -364,7 +364,7 @@ public ExtensionVersionRecord() { /** * Create a detached, initialised ExtensionVersionRecord */ - public ExtensionVersionRecord(Long id, String bugs, String description, String displayName, String galleryColor, String galleryTheme, String homepage, String license, String markdown, Boolean preview, String qna, String repository, LocalDateTime timestamp, String version, Long extensionId, Long publishedWithId, Boolean active, String dependencies, String bundledExtensions, String engines, String categories, String tags, String extensionKind) { + public ExtensionVersionRecord(Long id, String bugs, String description, String displayName, String galleryColor, String galleryTheme, String homepage, String license, String markdown, Boolean preview, String qna, String repository, LocalDateTime timestamp, String version, Long publishedWithId, Boolean active, String dependencies, String bundledExtensions, String engines, String categories, String tags, String extensionKind, Long targetPlatformId) { super(ExtensionVersion.EXTENSION_VERSION); setId(id); @@ -381,7 +381,6 @@ public ExtensionVersionRecord(Long id, String bugs, String description, String d setRepository(repository); setTimestamp(timestamp); setVersion(version); - setExtensionId(extensionId); setPublishedWithId(publishedWithId); setActive(active); setDependencies(dependencies); @@ -390,5 +389,6 @@ public ExtensionVersionRecord(Long id, String bugs, String description, String d setCategories(categories); setTags(tags); setExtensionKind(extensionKind); + setTargetPlatformId(targetPlatformId); } } diff --git a/server/src/main/resources/db/migration/V1_21__ExtensionTargetPlatform.sql b/server/src/main/resources/db/migration/V1_21__ExtensionTargetPlatform.sql new file mode 100644 index 000000000..b94bf9b8e --- /dev/null +++ b/server/src/main/resources/db/migration/V1_21__ExtensionTargetPlatform.sql @@ -0,0 +1,39 @@ +CREATE TABLE extension_target_platform( + id BIGINT NOT NULL, + name CHARACTER VARYING(255), + extension_id BIGINT, + latest_id BIGINT, + preview_id BIGINT, + CONSTRAINT extension_target_platform_pkey PRIMARY KEY(id), + CONSTRAINT extension_target_platform_extension_fkey FOREIGN KEY(extension_id) REFERENCES extension(id), + CONSTRAINT extension_target_platform_latest_fkey FOREIGN KEY(latest_id) REFERENCES extension_version(id), + CONSTRAINT extension_target_platform_preview_fkey FOREIGN KEY(preview_id) REFERENCES extension_version(id) +); + +CREATE UNIQUE INDEX unique_extension_target_platform_idx ON extension_target_platform(extension_id, name); + +ALTER TABLE extension_target_platform ADD CONSTRAINT unique_extension_target_platform +UNIQUE USING INDEX unique_extension_target_platform_idx; + +INSERT INTO extension_target_platform(id, name, extension_id, latest_id, preview_id) +SELECT nextval('hibernate_sequence'), '', id, latest_id, preview_id +FROM extension; + +ALTER TABLE extension DROP COLUMN latest_id; +ALTER TABLE extension DROP COLUMN preview_id; + +ALTER TABLE extension_version ADD COLUMN target_platform_id BIGINT; + +ALTER TABLE extension_version ADD CONSTRAINT extension_version_target_platform_fkey +FOREIGN KEY(target_platform_id) REFERENCES extension_target_platform(id); + +UPDATE extension_version ev +SET target_platform_id = tp.id +FROM extension_target_platform tp +WHERE tp.extension_id = ev.extension_id; + +DROP INDEX unique_extension_version; +CREATE UNIQUE INDEX unique_extension_version_idx ON extension_version(target_platform_id, version); +ALTER TABLE extension_version ADD CONSTRAINT unique_extension_version UNIQUE USING INDEX unique_extension_version_idx; + +ALTER TABLE extension_version DROP COLUMN extension_id; \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/AdminAPITest.java b/server/src/test/java/org/eclipse/openvsx/AdminAPITest.java index 4ff582d24..17e08cc6f 100644 --- a/server/src/test/java/org/eclipse/openvsx/AdminAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/AdminAPITest.java @@ -133,7 +133,7 @@ public void testGetInactiveExtension() throws Exception { mockAdminUser(); mockExtension(2, 0, 0).forEach(ev -> { ev.setActive(false); - ev.getExtension().setActive(false); + ev.getTargetPlatform().getExtension().setActive(false); }); mockMvc.perform(get("/admin/extension/{namespace}/{extension}", "foobar", "baz") @@ -538,7 +538,7 @@ public void testRevokePublisherAgreement() throws Exception { .with(csrf().asHeader())) .andExpect(status().isOk()) .andExpect(content().json(successJson("Deactivated 1 tokens and deactivated 1 extensions of user github/test."))); - + assertThat(token.isActive()).isFalse(); assertThat(versions.get(0).isActive()).isFalse(); } @@ -743,39 +743,54 @@ private List mockExtension(int numberOfVersions, int numberOfB Mockito.when(repositories.findExtension("baz", "foobar")) .thenReturn(extension); + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); + var versions = new ArrayList(numberOfVersions); for (var i = 0; i < numberOfVersions; i++) { var extVersion = new ExtensionVersion(); - extVersion.setExtension(extension); + extVersion.setTargetPlatform(targetPlatform); extVersion.setVersion(Integer.toString(i + 1)); extVersion.setActive(true); Mockito.when(repositories.findFiles(extVersion)) .thenReturn(Streamable.empty()); Mockito.when(repositories.findFilesByType(eq(extVersion), any())) .thenReturn(Streamable.empty()); - Mockito.when(repositories.findVersion(extVersion.getVersion(), "baz", "foobar")) + Mockito.when(repositories.findVersion(extVersion.getVersion(), extVersion.getTargetPlatform().getName(), "baz", "foobar")) .thenReturn(extVersion); + Mockito.when(repositories.findTargetPlatformVersions(extVersion.getVersion(), "baz", "foobar")) + .thenReturn(Streamable.of(versions)); versions.add(extVersion); } - extension.setLatest(versions.get(versions.size() - 1)); + targetPlatform.setLatest(versions.get(versions.size() - 1)); + Mockito.when(repositories.findActiveVersions(extension)) + .thenReturn(Streamable.of(versions)); Mockito.when(repositories.findVersions(extension)) .thenReturn(Streamable.of(versions)); - Mockito.when(repositories.findActiveVersions(extension, false)) + Mockito.when(repositories.findActiveVersions(targetPlatform, false)) .thenReturn(Streamable.of(versions)); - Mockito.when(repositories.findActiveVersions(extension, true)) + Mockito.when(repositories.findActiveVersions(targetPlatform, true)) .thenReturn(Streamable.empty()); - Mockito.when(repositories.getVersionStrings(extension)) + Mockito.when(repositories.getVersionStrings(targetPlatform)) .thenReturn(Streamable.of(versions).map(ev -> ev.getVersion())); - Mockito.when(repositories.getActiveVersionStrings(extension)) + Mockito.when(repositories.getActiveVersionStrings(targetPlatform)) .thenReturn(Streamable.of(versions).map(ev -> ev.getVersion())); var bundleExt = new Extension(); bundleExt.setName("bundle"); bundleExt.setNamespace(namespace); + + var bundleTargetPlatform = new ExtensionTargetPlatform(); + bundleTargetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + bundleTargetPlatform.setExtension(bundleExt); + bundleExt.getTargetPlatforms().add(bundleTargetPlatform); + var bundles = new ArrayList(numberOfBundles); for (var i = 0; i < numberOfBundles; i++) { var bundle = new ExtensionVersion(); - bundle.setExtension(bundleExt); + bundle.setTargetPlatform(bundleTargetPlatform); bundle.setVersion(Integer.toString(i + 1)); bundles.add(bundle); } @@ -785,10 +800,16 @@ private List mockExtension(int numberOfVersions, int numberOfB var dependantExt = new Extension(); dependantExt.setName("dependant"); dependantExt.setNamespace(namespace); + + var dependantTargetPlatform = new ExtensionTargetPlatform(); + dependantTargetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + dependantTargetPlatform.setExtension(dependantExt); + dependantExt.getTargetPlatforms().add(dependantTargetPlatform); + var dependants = new ArrayList(numberOfDependants); for (var i = 0; i < numberOfDependants; i++) { var dependant = new ExtensionVersion(); - dependant.setExtension(dependantExt); + dependant.setTargetPlatform(dependantTargetPlatform); dependant.setVersion(Integer.toString(i + 1)); dependants.add(dependant); } diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index 9f0191cb0..9a7ea8ecd 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -36,14 +36,7 @@ import org.eclipse.openvsx.adapter.VSCodeIdService; import org.eclipse.openvsx.dto.ExtensionVersionDTO; import org.eclipse.openvsx.eclipse.EclipseService; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionReview; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.FileResource; -import org.eclipse.openvsx.entities.Namespace; -import org.eclipse.openvsx.entities.NamespaceMembership; -import org.eclipse.openvsx.entities.PersonalAccessToken; -import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.ExtensionJson; import org.eclipse.openvsx.json.NamespaceJson; import org.eclipse.openvsx.json.ResultJson; @@ -65,6 +58,8 @@ import org.elasticsearch.search.aggregations.Aggregations; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -160,18 +155,35 @@ public void testExtension() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; }))); } + @Test + public void testExtensionLinuxTarget() throws Exception { + var extVersion = mockExtension("linux-x64"); + extVersion.setDisplayName("Foo Bar (linux x64)"); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}", "foo", "bar", "linux-x64")) + .andExpect(status().isOk()) + .andExpect(content().json(extensionJson(e -> { + e.namespace = "foo"; + e.name = "bar"; + e.targetPlatform = "linux-x64"; + e.version = "1.0.0"; + e.verified = false; + e.timestamp = "2000-01-01T10:00Z"; + e.displayName = "Foo Bar (linux x64)"; + }))); + } + @Test public void testInactiveExtension() throws Exception { var extVersion = mockExtension(); extVersion.setActive(false); - extVersion.getExtension().setActive(false); + extVersion.getTargetPlatform().getExtension().setActive(false); mockMvc.perform(get("/api/{namespace}/{extension}", "foo", "bar")) .andExpect(status().isNotFound()) @@ -186,21 +198,45 @@ public void testUnknownExtension() throws Exception { .andExpect(content().json(errorJson("Extension not found: foo.baz"))); } + @Test + public void testUnknownExtensionTarget() throws Exception { + mockExtension(); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}", "foo", "bar", "win32-ia32")) + .andExpect(status().isNotFound()) + .andExpect(content().json(errorJson("Extension not found: foo.bar"))); + } + @Test public void testExtensionVersion() throws Exception { mockExtension(); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "1")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "1.0.0")) .andExpect(status().isOk()) .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; }))); } + @Test + public void testExtensionVersionMacOSXTarget() throws Exception { + var extVersion = mockExtension("darwin-arm64"); + extVersion.setDisplayName("Foo Bar (darwin arm64)"); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}", "foo", "bar", "darwin-arm64", "1.0.0")) + .andExpect(status().isOk()) + .andExpect(content().json(extensionJson(e -> { + e.namespace = "foo"; + e.name = "bar"; + e.version = "1.0.0"; + e.verified = false; + e.timestamp = "2000-01-01T10:00Z"; + e.displayName = "Foo Bar (darwin arm64)"; + }))); + } + @Test public void testLatestExtensionVersion() throws Exception { mockExtension(); @@ -209,43 +245,120 @@ public void testLatestExtensionVersion() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; }))); } + @Test + public void testLatestExtensionVersionAlpineLinuxTarget() throws Exception { + var extVersion = mockExtension("alpine-arm64"); + extVersion.setDisplayName("Foo Bar (alpine arm64)"); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}", "foo", "bar", "alpine-arm64", "latest")) + .andExpect(status().isOk()) + .andExpect(content().json(extensionJson(e -> { + e.namespace = "foo"; + e.name = "bar"; + e.version = "1.0.0"; + e.verified = false; + e.timestamp = "2000-01-01T10:00Z"; + e.displayName = "Foo Bar (alpine arm64)"; + e.targetPlatform = "alpine-arm64"; + }))); + } + + @Test + public void testPreviewExtensionVersion() throws Exception { + var extVersion = mockExtension(); + extVersion.setPreview(true); + extVersion.getTargetPlatform().setPreview(extVersion); + mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "preview")) + .andExpect(status().isOk()) + .andExpect(content().json(extensionJson(e -> { + e.namespace = "foo"; + e.name = "bar"; + e.version = "1.0.0"; + e.verified = false; + e.timestamp = "2000-01-01T10:00Z"; + e.displayName = "Foo Bar"; + e.preview = true; + }))); + } + + @Test + public void testPreviewExtensionVersionWebTarget() throws Exception { + var extVersion = mockExtension("web"); + extVersion.setPreview(true); + extVersion.getTargetPlatform().setPreview(extVersion); + extVersion.setDisplayName("Foo Bar (web)"); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}", "foo", "bar", "web", "preview")) + .andExpect(status().isOk()) + .andExpect(content().json(extensionJson(e -> { + e.namespace = "foo"; + e.name = "bar"; + e.version = "1.0.0"; + e.verified = false; + e.timestamp = "2000-01-01T10:00Z"; + e.displayName = "Foo Bar (web)"; + e.preview = true; + }))); + } + @Test public void testInactiveExtensionVersion() throws Exception { var extVersion = mockExtension(); extVersion.setActive(false); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "1")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "1.0.0")) .andExpect(status().isNotFound()) - .andExpect(content().json(errorJson("Extension not found: foo.bar version 1"))); + .andExpect(content().json(errorJson("Extension not found: foo.bar version 1.0.0"))); } @Test public void testUnknownExtensionVersion() throws Exception { mockExtension(); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "2")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}", "foo", "bar", "2.0.0")) + .andExpect(status().isNotFound()) + .andExpect(content().json(errorJson("Extension not found: foo.bar version 2.0.0"))); + } + + @Test + public void testUnknownExtensionVersionTarget() throws Exception { + mockExtension(); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}", "foo", "bar", "linux-armhf", "1.0.0")) .andExpect(status().isNotFound()) - .andExpect(content().json(errorJson("Extension not found: foo.bar version 2"))); + .andExpect(content().json(errorJson("Extension not found: foo.bar version 1.0.0"))); } @Test public void testReadme() throws Exception { mockReadme(); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1", "README")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "README")) .andExpect(status().isOk()) .andExpect(content().string("Please read me")); } + @Test + public void testReadmeWindowsTarget() throws Exception { + mockReadme("win32-x64"); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}/file/{fileName}", "foo", "bar", "win32-x64", "1.0.0", "README")) + .andExpect(status().isOk()) + .andExpect(content().string("Please read me")); + } + + @Test + public void testReadmeUnknownTarget() throws Exception { + mockReadme(); + mockMvc.perform(get("/api/{namespace}/{extension}/{target}/{version}/file/{fileName}", "foo", "bar", "darwin-x64", "1.0.0", "README")) + .andExpect(status().isNotFound()); + } + @Test public void testChangelog() throws Exception { mockChangelog(); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1", "CHANGELOG")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "CHANGELOG")) .andExpect(status().isOk()) .andExpect(content().string("All notable changes is documented here")); } @@ -253,7 +366,7 @@ public void testChangelog() throws Exception { @Test public void testLicense() throws Exception { mockLicense(); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1", "LICENSE")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "LICENSE")) .andExpect(status().isOk()) .andExpect(content().string("I never broke the Law! I am the law!")); } @@ -263,14 +376,14 @@ public void testInactiveFile() throws Exception { var extVersion = mockExtension(); extVersion.setActive(false); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1", "README")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "README")) .andExpect(status().isNotFound()); } @Test public void testUnknownFile() throws Exception { mockExtension(); - mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1", "unknown.txt")) + mockMvc.perform(get("/api/{namespace}/{extension}/{version}/file/{fileName}", "foo", "bar", "1.0.0", "unknown.txt")) .andExpect(status().isNotFound()); } @@ -310,7 +423,7 @@ public void testSearch() throws Exception { var e1 = new SearchEntryJson(); e1.namespace = "foo"; e1.name = "bar"; - e1.version = "1"; + e1.version = "1.0.0"; e1.timestamp = "2000-01-01T10:00Z"; e1.displayName = "Foo Bar"; s.extensions.add(e1); @@ -322,7 +435,7 @@ public void testSearchInactive() throws Exception { var extensions = mockSearch(); extensions.forEach(extension -> { extension.setActive(false); - extension.getLatest().setActive(false); + extension.getTargetPlatforms().get(0).getLatest().setActive(false); }); mockMvc.perform(get("/api/-/search?query={query}&size={size}&offset={offset}", "foo", "10", "0")) @@ -338,7 +451,7 @@ public void testGetQueryExtensionName() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -353,7 +466,7 @@ public void testGetQueryNamespace() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -363,7 +476,7 @@ public void testGetQueryNamespace() throws Exception { @Test public void testGetQueryUnknownExtension() throws Exception { mockExtensionVersionDTO(); - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName("baz")) + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(ExtensionTargetPlatform.NAME_ANY, "baz")) .thenReturn(Collections.emptyList()); mockMvc.perform(get("/api/-/query?extensionName={extensionName}", "baz")) @@ -390,7 +503,7 @@ public void testGetQueryExtensionId() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -405,7 +518,7 @@ public void testGetQueryExtensionUuid() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -420,7 +533,7 @@ public void testGetQueryNamespaceUuid() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -437,7 +550,7 @@ public void testPostQueryExtensionName() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -454,7 +567,7 @@ public void testPostQueryNamespace() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -464,7 +577,7 @@ public void testPostQueryNamespace() throws Exception { @Test public void testPostQueryUnknownExtension() throws Exception { mockExtensionVersionDTO(); - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName("baz")) + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(ExtensionTargetPlatform.NAME_ANY, "baz")) .thenReturn(Collections.emptyList()); mockMvc.perform(post("/api/-/query") @@ -494,7 +607,7 @@ public void testPostQueryExtensionId() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -511,7 +624,7 @@ public void testPostQueryExtensionUuid() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -528,7 +641,7 @@ public void testPostQueryNamespaceUuid() throws Exception { .andExpect(content().json(queryResultJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; e.verified = false; e.timestamp = "2000-01-01T10:00Z"; e.displayName = "Foo Bar"; @@ -595,7 +708,7 @@ public void testCreateExistingNamespace() throws Exception { @Test public void testPublishOrphan() throws Exception { mockForPublish("orphan"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -609,7 +722,7 @@ public void testPublishRequireLicenseNone() throws Exception { try { extensionService.requireLicense = true; mockForPublish("contributor"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -626,7 +739,7 @@ public void testPublishRequireLicenseOk() throws Exception { try { extensionService.requireLicense = true; mockForPublish("contributor"); - var bytes = createExtensionPackage("bar", "1", "MIT"); + var bytes = createExtensionPackage("bar", "1.0.0", "MIT"); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -634,7 +747,7 @@ public void testPublishRequireLicenseOk() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; var u = new UserJson(); u.loginName = "test_user"; e.publishedBy = u; @@ -648,7 +761,7 @@ public void testPublishRequireLicenseOk() throws Exception { @Test public void testPublishInactiveToken() throws Exception { mockForPublish("invalid"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -659,7 +772,7 @@ public void testPublishInactiveToken() throws Exception { @Test public void testPublishUnknownNamespace() throws Exception { mockAccessToken(); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -671,7 +784,7 @@ public void testPublishUnknownNamespace() throws Exception { @Test public void testPublishVerifiedOwner() throws Exception { mockForPublish("owner"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -679,18 +792,18 @@ public void testPublishVerifiedOwner() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; var u = new UserJson(); u.loginName = "test_user"; e.publishedBy = u; e.verified = true; }))); } - + @Test public void testPublishVerifiedContributor() throws Exception { mockForPublish("contributor"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -698,18 +811,18 @@ public void testPublishVerifiedContributor() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; var u = new UserJson(); u.loginName = "test_user"; e.publishedBy = u; e.verified = true; }))); } - + @Test public void testPublishSoleContributor() throws Exception { mockForPublish("sole-contributor"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -717,18 +830,18 @@ public void testPublishSoleContributor() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; var u = new UserJson(); u.loginName = "test_user"; e.publishedBy = u; e.verified = false; }))); } - + @Test public void testPublishRestrictedPrivileged() throws Exception { mockForPublish("privileged"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) @@ -736,47 +849,47 @@ public void testPublishRestrictedPrivileged() throws Exception { .andExpect(content().json(extensionJson(e -> { e.namespace = "foo"; e.name = "bar"; - e.version = "1"; + e.version = "1.0.0"; var u = new UserJson(); u.loginName = "test_user"; e.publishedBy = u; e.verified = false; }))); } - + @Test public void testPublishRestrictedUnrelated() throws Exception { mockForPublish("unrelated"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) .andExpect(status().isBadRequest()) .andExpect(content().json(errorJson("Insufficient access rights for publisher: foo"))); } - + @Test public void testPublishExistingExtension() throws Exception { mockForPublish("existing"); - var bytes = createExtensionPackage("bar", "1", null); + var bytes = createExtensionPackage("bar", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) .andExpect(status().isBadRequest()) - .andExpect(content().json(errorJson("Extension foo.bar version 1 is already published."))); + .andExpect(content().json(errorJson("Extension foo.bar version 1.0.0 is already published."))); } - + @Test public void testPublishInvalidName() throws Exception { mockForPublish("contributor"); - var bytes = createExtensionPackage("b.a.r", "1", null); + var bytes = createExtensionPackage("b.a.r", "1.0.0", null); mockMvc.perform(post("/api/-/publish?token={token}", "my_token") .contentType(MediaType.APPLICATION_OCTET_STREAM) .content(bytes)) .andExpect(status().isBadRequest()) .andExpect(content().json(errorJson("Invalid extension name: b.a.r"))); } - + @Test public void testPublishInvalidVersion() throws Exception { mockForPublish("contributor"); @@ -792,7 +905,7 @@ public void testPublishInvalidVersion() throws Exception { public void testPostReview() throws Exception { var user = mockUserData(); var extVersion = mockExtension(); - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); Mockito.when(repositories.findExtension("bar", "foo")) .thenReturn(extension); Mockito.when(repositories.findActiveReviews(extension, user)) @@ -852,7 +965,7 @@ public void testPostReviewUnknownExtension() throws Exception { public void testPostExistingReview() throws Exception { var user = mockUserData(); var extVersion = mockExtension(); - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); Mockito.when(repositories.findExtension("bar", "foo")) .thenReturn(extension); var review = new ExtensionReview(); @@ -877,7 +990,7 @@ public void testPostExistingReview() throws Exception { public void testDeleteReview() throws Exception { var user = mockUserData(); var extVersion = mockExtension(); - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); Mockito.when(repositories.findExtension("bar", "foo")) .thenReturn(extension); var review = new ExtensionReview(); @@ -916,7 +1029,7 @@ public void testDeleteReviewUnknownExtension() throws Exception { public void testDeleteNonExistingReview() throws Exception { var user = mockUserData(); var extVersion = mockExtension(); - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); Mockito.when(repositories.findExtension("bar", "foo")) .thenReturn(extension); Mockito.when(repositories.findActiveReviews(extension, user)) @@ -949,11 +1062,12 @@ private String namespaceJson(Consumer content) throws JsonProcess } private void mockInactiveExtensionVersionDTO(String namespaceName, String extensionName) { - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(extensionName, namespaceName)) + var targetPlatform = ExtensionTargetPlatform.NAME_ANY; + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(targetPlatform, extensionName, namespaceName)) .thenReturn(Collections.emptyList()); - Mockito.when(repositories.findActiveExtensionVersionDTOsByNamespaceName(namespaceName)) + Mockito.when(repositories.findActiveExtensionVersionDTOsByNamespaceName(targetPlatform, namespaceName)) .thenReturn(Collections.emptyList()); - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(extensionName)) + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(targetPlatform, extensionName)) .thenReturn(Collections.emptyList()); } @@ -965,7 +1079,7 @@ private ExtensionVersionDTO mockExtensionVersionDTO() { var extensionName = "bar"; var id = 3L; var extensionLatestId = id; - var version = "1"; + var version = "1.0.0"; var timestamp = LocalDateTime.parse("2000-01-01T10:00"); var displayName = "Foo Bar"; @@ -974,21 +1088,22 @@ private ExtensionVersionDTO mockExtensionVersionDTO() { null, null, 0, null, null, null, null, null, null, id, version, false, timestamp, displayName, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null + null, null, null, null, null, null, ExtensionTargetPlatform.NAME_ANY ); var extensionPublicId = "5678"; - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionPublicId(extensionPublicId)) + var targetPlatform = ExtensionTargetPlatform.NAME_ANY; + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionPublicId(targetPlatform, extensionPublicId)) .thenReturn(List.of(extVersion)); - Mockito.when(repositories.findActiveExtensionVersionDTOsByNamespacePublicId(namespacePublicId)) + Mockito.when(repositories.findActiveExtensionVersionDTOsByNamespacePublicId(targetPlatform, namespacePublicId)) .thenReturn(List.of(extVersion)); - Mockito.when(repositories.findActiveExtensionVersionDTOByVersion(version, extensionName, namespaceName)) + Mockito.when(repositories.findActiveExtensionVersionDTOByVersion(version, targetPlatform, extensionName, namespaceName)) .thenReturn(extVersion); - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(extensionName, namespaceName)) + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(targetPlatform, extensionName, namespaceName)) .thenReturn(List.of(extVersion)); - Mockito.when(repositories.findActiveExtensionVersionDTOsByNamespaceName(namespaceName)) + Mockito.when(repositories.findActiveExtensionVersionDTOsByNamespaceName(targetPlatform, namespaceName)) .thenReturn(List.of(extVersion)); - Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(extensionName)) + Mockito.when(repositories.findActiveExtensionVersionDTOsByExtensionName(targetPlatform, extensionName)) .thenReturn(List.of(extVersion)); Mockito.when(repositories.findAllActiveReviewCountsByExtensionId(Set.of(extensionId))) @@ -1003,6 +1118,10 @@ private ExtensionVersionDTO mockExtensionVersionDTO() { } private ExtensionVersion mockExtension() { + return mockExtension(ExtensionTargetPlatform.NAME_ANY); + } + + private ExtensionVersion mockExtension(String targetPlatformName) { var namespace = new Namespace(); namespace.setName("foo"); namespace.setPublicId("1234"); @@ -1011,31 +1130,36 @@ private ExtensionVersion mockExtension() { extension.setNamespace(namespace); extension.setPublicId("5678"); extension.setActive(true); + + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(targetPlatformName); + extension.getTargetPlatforms().add(targetPlatform); + targetPlatform.setExtension(extension); + var extVersion = new ExtensionVersion(); - extension.setLatest(extVersion); - extVersion.setExtension(extension); - extVersion.setVersion("1"); + extVersion.setTargetPlatform(targetPlatform); + extVersion.setVersion("1.0.0"); extVersion.setTimestamp(LocalDateTime.parse("2000-01-01T10:00")); extVersion.setActive(true); extVersion.setDisplayName("Foo Bar"); + targetPlatform.getVersions().add(extVersion); + targetPlatform.setLatest(extVersion); Mockito.when(repositories.findExtension("bar", "foo")) .thenReturn(extension); - Mockito.when(repositories.findVersion("1", "bar", "foo")) + Mockito.when(repositories.findVersion("1.0.0", targetPlatformName, "bar", "foo")) .thenReturn(extVersion); Mockito.when(repositories.findVersions(extension)) .thenReturn(Streamable.of(extVersion)); Mockito.when(repositories.findActiveExtensions(namespace)) .thenReturn(Streamable.of(extension)); - Mockito.when(repositories.getVersionStrings(extension)) + Mockito.when(repositories.getVersionStrings(targetPlatform)) .thenReturn(Streamable.of(extVersion.getVersion())); - Mockito.when(repositories.getActiveVersionStrings(extension)) + Mockito.when(repositories.getActiveVersionStrings(targetPlatform)) .thenReturn(Streamable.of(extVersion.getVersion())); Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(0l); Mockito.when(repositories.countActiveReviews(extension)) .thenReturn(0l); - Mockito.when(repositories.findFilesByType(eq(extVersion), anyCollection())) - .thenReturn(Streamable.empty()); Mockito.when(repositories.findNamespace("foo")) .thenReturn(namespace); Mockito.when(repositories.findExtensions("bar")) @@ -1044,6 +1168,17 @@ private ExtensionVersion mockExtension() { .thenReturn(namespace); Mockito.when(repositories.findExtensionByPublicId("5678")) .thenReturn(extension); + + var download = new FileResource(); + download.setExtension(extVersion); + download.setType(DOWNLOAD); + download.setStorageType(STORAGE_DB); + download.setName("extension-1.0.0.vsix"); + Mockito.when(repositories.findFilesByType(eq(extVersion), anyCollection())).thenAnswer(invocation -> { + Collection types = invocation.getArgument(1); + return types.contains(DOWNLOAD) ? Streamable.of(download) : Streamable.empty(); + }); + return extVersion; } @@ -1058,7 +1193,11 @@ private String queryResultJson(Consumer content) throws JsonProce } private FileResource mockReadme() { - var extVersion = mockExtension(); + return mockReadme(ExtensionTargetPlatform.NAME_ANY); + } + + private FileResource mockReadme(String targetPlatformName) { + var extVersion = mockExtension(targetPlatformName); var resource = new FileResource(); resource.setExtension(extVersion); resource.setName("README"); @@ -1098,7 +1237,7 @@ private FileResource mockLicense() { private void mockReviews() { var extVersion = mockExtension(); - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); var user1 = new UserData(); user1.setLoginName("user1"); var review1 = new ExtensionReview(); @@ -1130,7 +1269,7 @@ private String reviewsJson(Consumer content) throws JsonProcessi private List mockSearch() { var extVersion = mockExtension(); - var extension = extVersion.getExtension(); + var extension = extVersion.getTargetPlatform().getExtension(); extension.setId(1l); var entry1 = new ExtensionSearch(); entry1.id = 1; @@ -1139,7 +1278,7 @@ private List mockSearch() { Arrays.asList(searchHit), new Aggregations(Collections.emptyList())); Mockito.when(search.isEnabled()) .thenReturn(true); - var searchOptions = new ISearchService.Options("foo", null, 10, 0, "desc", "relevance", false); + var searchOptions = new ISearchService.Options("foo", null, ExtensionTargetPlatform.NAME_ANY, 10, 0, "desc", "relevance", false); Mockito.when(search.search(searchOptions, PageRequest.of(0, 10))) .thenReturn(searchHits); Mockito.when(entityManager.find(Extension.class, 1l)) @@ -1179,24 +1318,30 @@ private void mockForPublish(String mode) { if (mode.equals("existing")) { var extension = new Extension(); extension.setName("bar"); + + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); + var extVersion = new ExtensionVersion(); - extVersion.setExtension(extension); - extVersion.setVersion("1"); + extVersion.setTargetPlatform(targetPlatform); + extVersion.setVersion("1.0.0"); extVersion.setActive(true); Mockito.when(repositories.findExtension("bar", namespace)) .thenReturn(extension); - Mockito.when(repositories.findVersion("1", extension)) + Mockito.when(repositories.findVersion("1.0.0", targetPlatform.getName(), extension)) .thenReturn(extVersion); } Mockito.when(repositories.countActiveReviews(any(Extension.class))) .thenReturn(0l); Mockito.when(repositories.findVersions(any(Extension.class))) .thenReturn(Streamable.empty()); - Mockito.when(repositories.findActiveVersions(any(Extension.class), any(boolean.class))) + Mockito.when(repositories.findActiveVersions(any(ExtensionTargetPlatform.class), any(boolean.class))) .thenReturn(Streamable.empty()); - Mockito.when(repositories.getVersionStrings(any(Extension.class))) + Mockito.when(repositories.getVersionStrings(any(ExtensionTargetPlatform.class))) .thenReturn(Streamable.empty()); - Mockito.when(repositories.getActiveVersionStrings(any(Extension.class))) + Mockito.when(repositories.getActiveVersionStrings(any(ExtensionTargetPlatform.class))) .thenReturn(Streamable.empty()); Mockito.when(repositories.findFilesByType(any(ExtensionVersion.class), anyCollection())) .thenReturn(Streamable.empty()); diff --git a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java index 9a7a09486..8232c2bf3 100644 --- a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java +++ b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java @@ -34,11 +34,7 @@ import org.eclipse.openvsx.dto.ExtensionVersionDTO; import org.eclipse.openvsx.dto.FileResourceDTO; import org.eclipse.openvsx.eclipse.EclipseService; -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.entities.NamespaceMembership; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.ExtensionSearch; import org.eclipse.openvsx.search.ISearchService; @@ -104,6 +100,16 @@ public void testSearch() throws Exception { .andExpect(content().json(file("search-yaml-response.json"))); } + @Test + public void testSearchMacOSXTarget() throws Exception { + mockSearch("darwin-x64", true); + mockMvc.perform(post("/vscode/gallery/extensionquery") + .content(file("search-yaml-query-darwin.json")) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(file("search-yaml-response-darwin.json"))); + } + @Test public void testFindById() throws Exception { mockSearch(true); @@ -114,6 +120,16 @@ public void testFindById() throws Exception { .andExpect(content().json(file("findid-yaml-response.json"))); } + @Test + public void testFindByIdAlpineTarget() throws Exception { + mockSearch("alpine-arm64", true); + mockMvc.perform(post("/vscode/gallery/extensionquery") + .content(file("findid-yaml-query-alpine.json")) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(file("findid-yaml-response-alpine.json"))); + } + @Test public void testFindByIdDuplicate() throws Exception { mockSearch(true); @@ -144,6 +160,16 @@ public void testFindByName() throws Exception { .andExpect(content().json(file("findname-yaml-response.json"))); } + @Test + public void testFindByNameLinuxTarget() throws Exception { + mockSearch("linux-x64", true); + mockMvc.perform(post("/vscode/gallery/extensionquery") + .content(file("findname-yaml-query-linux.json")) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(file("findname-yaml-response-linux.json"))); + } + @Test public void testFindByNameDuplicate() throws Exception { mockSearch(true); @@ -175,8 +201,11 @@ public void testAssetNotFound() throws Exception { // ---------- UTILITY ----------// - private void mockSearch(boolean active) { + mockSearch(ExtensionTargetPlatform.NAME_ANY, active); + } + + private void mockSearch(String targetPlatform, boolean active) { var entry1 = new ExtensionSearch(); entry1.id = 1; var searchHit = new SearchHit("0", "1", 1.0f, null, null, entry1); @@ -184,17 +213,17 @@ private void mockSearch(boolean active) { Arrays.asList(searchHit), new Aggregations(Collections.emptyList())); Mockito.when(search.isEnabled()) .thenReturn(true); - var searchOptions = new ISearchService.Options("yaml", null, 50, 0, "desc", "relevance", false); + var searchOptions = new ISearchService.Options("yaml", null, targetPlatform, 50, 0, "desc", "relevance", false); Mockito.when(search.search(searchOptions, PageRequest.of(0, 50))) .thenReturn(searchHits); - var extension = mockExtensionDTO(); + var extension = mockExtensionDTO(targetPlatform); List results = active ? List.of(extension) : Collections.emptyList(); - Mockito.when(repositories.findAllActiveExtensionDTOsById(List.of(entry1.id))) + Mockito.when(repositories.findAllActiveExtensionDTOs(List.of(entry1.id))) .thenReturn(results); var publicIds = Set.of(extension.getPublicId()); - Mockito.when(repositories.findAllActiveExtensionDTOsByPublicId(publicIds)) + Mockito.when(repositories.findAllActiveExtensionDTOs(publicIds, targetPlatform)) .thenReturn(results); var ids = List.of(extension.getId()); @@ -203,13 +232,13 @@ private void mockSearch(boolean active) { var name = extension.getName(); var namespaceName = extension.getNamespace().getName(); - Mockito.when(repositories.findActiveExtensionDTOByNameAndNamespaceName(name, namespaceName)) + Mockito.when(repositories.findActiveExtensionDTO(name, namespaceName, targetPlatform)) .thenReturn(extension); mockFileResourceDTOs(extension.getLatest()); } - private ExtensionDTO mockExtensionDTO() { + private ExtensionDTO mockExtensionDTO(String targetPlatform) { var id = 1; var publicId = "test-1"; var name = "vscode-yaml"; @@ -230,7 +259,7 @@ private ExtensionDTO mockExtensionDTO() { return new ExtensionDTO(id,publicId,name,averageRating,downloadCount,namespaceId,namespacePublicId, namespaceName,latestId,latestVersion,latestPreview,latestTimestamp,latestDisplayName,latestDescription, latestEngines,null,null,null,latestRepository,null, - null,null,null); + null,null,null, targetPlatform); } private void mockFileResourceDTOs(ExtensionVersionDTO extensionVersion) { @@ -249,6 +278,10 @@ private void mockFileResourceDTOs(ExtensionVersionDTO extensionVersion) { } private ExtensionVersion mockExtension() { + var namespace = new Namespace(); + namespace.setId(2); + namespace.setPublicId("test-2"); + namespace.setName("redhat"); var extension = new Extension(); extension.setId(1); extension.setPublicId("test-1"); @@ -256,14 +289,14 @@ private ExtensionVersion mockExtension() { extension.setActive(true); extension.setDownloadCount(100); extension.setAverageRating(3.0); - var namespace = new Namespace(); - namespace.setId(2); - namespace.setPublicId("test-2"); - namespace.setName("redhat"); extension.setNamespace(namespace); + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); var extVersion = new ExtensionVersion(); - extension.setLatest(extVersion); - extVersion.setExtension(extension); + targetPlatform.setLatest(extVersion); + extVersion.setTargetPlatform(targetPlatform); extVersion.setVersion("0.5.2"); extVersion.setPreview(true); extVersion.setTimestamp(LocalDateTime.parse("2000-01-01T10:00")); @@ -278,13 +311,13 @@ private ExtensionVersion mockExtension() { .thenReturn(extension); Mockito.when(repositories.findExtension("vscode-yaml", "redhat")) .thenReturn(extension); - Mockito.when(repositories.findVersion("0.5.2", "vscode-yaml", "redhat")) + Mockito.when(repositories.findVersion("0.5.2", ExtensionTargetPlatform.NAME_ANY, "vscode-yaml", "redhat")) .thenReturn(extVersion); Mockito.when(repositories.findVersions(extension)) .thenReturn(Streamable.of(extVersion)); - Mockito.when(repositories.getVersionStrings(extension)) + Mockito.when(repositories.getVersionStrings(targetPlatform)) .thenReturn(Streamable.of(extVersion.getVersion())); - Mockito.when(repositories.getActiveVersionStrings(extension)) + Mockito.when(repositories.getActiveVersionStrings(targetPlatform)) .thenReturn(Streamable.of(extVersion.getVersion())); Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(0l); 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 0fafc3e39..b796d8367 100644 --- a/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java @@ -27,13 +27,7 @@ import org.eclipse.openvsx.MockTransactionTemplate; import org.eclipse.openvsx.UserService; import org.eclipse.openvsx.adapter.VSCodeIdService; -import org.eclipse.openvsx.entities.AuthToken; -import org.eclipse.openvsx.entities.EclipseData; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.Namespace; -import org.eclipse.openvsx.entities.PersonalAccessToken; -import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; import org.eclipse.openvsx.security.TokenService; @@ -197,14 +191,18 @@ public void testSignPublisherAgreementReactivateExtension() throws Exception { var extension = new Extension(); extension.setName("bar"); extension.setNamespace(namespace); + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); var extVersion = new ExtensionVersion(); - extVersion.setVersion("1"); - extVersion.setExtension(extension); + extVersion.setVersion("1.0.0"); + extVersion.setTargetPlatform(targetPlatform); Mockito.when(repositories.findVersionsByAccessToken(accessToken, false)) .thenReturn(Streamable.of(extVersion)); - Mockito.when(repositories.findActiveVersions(extension, false)) + Mockito.when(repositories.findActiveVersions(targetPlatform, false)) .thenReturn(Streamable.of(extVersion)); - Mockito.when(repositories.findActiveVersions(extension, true)) + Mockito.when(repositories.findActiveVersions(targetPlatform, true)) .thenReturn(Streamable.empty()); eclipse.signPublisherAgreement(user); 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 8e875ff87..693df85af 100644 --- a/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java @@ -14,14 +14,11 @@ import java.time.LocalDateTime; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.Namespace; -import org.eclipse.openvsx.entities.NamespaceMembership; -import org.eclipse.openvsx.entities.PersonalAccessToken; -import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.repositories.RepositoryService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -49,9 +46,9 @@ public void testCategory() throws Exception { var ext1 = mockExtension("yaml", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext3 = mockExtension("openshift", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, "Programming Languages", 50, 0, null, null, false); + var searchOptions = new ISearchService.Options(null, "Programming Languages", ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // should find two extensions assertThat(result.getTotalHits()).isEqualTo(2); @@ -62,9 +59,9 @@ public void testRelevance() throws Exception { var ext1 = mockExtension("yaml", 1.0, 100, 100, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext2 = mockExtension("java", 4.0, 100, 10000, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext3 = mockExtension("openshift", 1.0, 100, 10, "redhat", Arrays.asList("Snippets", "Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, null, 50, 0, null, "relevance", false); + var searchOptions = new ISearchService.Options(null, null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, "relevance", false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // should find all extensions but order should be different assertThat(result.getTotalHits()).isEqualTo(3); @@ -80,9 +77,9 @@ public void testRelevance() throws Exception { public void testReverse() throws Exception { var ext1 = mockExtension("yaml", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, "Programming Languages", 50, 0, "desc", null, false); + var searchOptions = new ISearchService.Options(null, "Programming Languages", ExtensionTargetPlatform.NAME_ANY, 50, 0, "desc", null, false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // should find two extensions assertThat(result.getTotalHits()).isEqualTo(2); @@ -101,10 +98,10 @@ public void testSimplePageSize() throws Exception { var ext5 = mockExtension("ext5", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext6 = mockExtension("ext6", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext7 = mockExtension("ext7", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - Mockito.when(repositories.findAllActiveExtensions()) - .thenReturn(Streamable.of(ext1, ext2, ext3, ext4, ext5, ext6, ext7)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)) + .thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4, ext5, ext6, ext7).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, null, 50, 0, null, null, false); + var searchOptions = new ISearchService.Options(null, null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var pageSizeItems = 5; var result = search.search(searchOptions, PageRequest.of(0, pageSizeItems)); @@ -130,10 +127,10 @@ public void testPages() throws Exception { var ext5 = mockExtension("ext5", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext6 = mockExtension("ext6", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext7 = mockExtension("ext7", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - Mockito.when(repositories.findAllActiveExtensions()) - .thenReturn(Streamable.of(ext1, ext2, ext3, ext4, ext5, ext6, ext7)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)) + .thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4, ext5, ext6, ext7).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, null, 50, 0, null, null, false); + var searchOptions = new ISearchService.Options(null, null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var pageNumber = 2; var pageSizeItems = 2; @@ -155,9 +152,9 @@ public void testQueryStringPublisherName() throws Exception { var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext3 = mockExtension("openshift", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Other")); var ext4 = mockExtension("foo", 4.0, 100, 0, "bar", Arrays.asList("Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options("redhat", null, 50, 0, null, null, false); + var searchOptions = new ISearchService.Options("redhat", null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // namespace finding assertThat(result.getTotalHits()).isEqualTo(3); @@ -175,9 +172,9 @@ public void testQueryStringExtensionName() throws Exception { var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext3 = mockExtension("openshift", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Other")); var ext4 = mockExtension("foo", 4.0, 100, 0, "bar", Arrays.asList("Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options("openshift", null, 50, 0, null, null, false); + var searchOptions = new ISearchService.Options("openshift", null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // extension name finding assertThat(result.getTotalHits()).isEqualTo(1); @@ -191,13 +188,13 @@ public void testQueryStringExtensionName() throws Exception { public void testQueryStringDescription() throws Exception { var ext1 = mockExtension("yaml", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - ext2.getLatest().setDescription("another desc"); + ext2.getTargetPlatforms().get(0).getLatest().setDescription("another desc"); var ext3 = mockExtension("openshift", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Other")); - ext3.getLatest().setDescription("my custom desc"); + ext3.getTargetPlatforms().get(0).getLatest().setDescription("my custom desc"); var ext4 = mockExtension("foo", 4.0, 100, 0, "bar", Arrays.asList("Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options("my custom desc", null, 50, 0, null, null, false); + var searchOptions = new ISearchService.Options("my custom desc", null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // custom description assertThat(result.getTotalHits()).isEqualTo(1); @@ -210,14 +207,14 @@ public void testQueryStringDescription() throws Exception { @Test public void testQueryStringDisplayName() throws Exception { var ext1 = mockExtension("yaml", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - ext1.getLatest().setDisplayName("This is a YAML extension"); + ext1.getTargetPlatforms().get(0).getLatest().setDisplayName("This is a YAML extension"); var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - ext2.getLatest().setDisplayName("Red Hat"); + ext2.getTargetPlatforms().get(0).getLatest().setDisplayName("Red Hat"); var ext3 = mockExtension("openshift", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Other")); var ext4 = mockExtension("foo", 4.0, 100, 0, "bar", Arrays.asList("Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options("Red Hat", null, 50, 0, null, null, false); + var searchOptions = new ISearchService.Options("Red Hat", null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, null, false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // custom displayname @@ -231,16 +228,16 @@ public void testQueryStringDisplayName() throws Exception { @Test public void testSortByTimeStamp() throws Exception { var ext1 = mockExtension("yaml", 3.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - ext1.getLatest().setTimestamp(LocalDateTime.parse("2021-10-10T00:00")); + ext1.getTargetPlatforms().get(0).getLatest().setTimestamp(LocalDateTime.parse("2021-10-10T00:00")); var ext2 = mockExtension("java", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); - ext2.getLatest().setTimestamp(LocalDateTime.parse("2021-10-07T00:00")); + ext2.getTargetPlatforms().get(0).getLatest().setTimestamp(LocalDateTime.parse("2021-10-07T00:00")); var ext3 = mockExtension("openshift", 4.0, 100, 0, "redhat", Arrays.asList("Snippets", "Other")); - ext3.getLatest().setTimestamp(LocalDateTime.parse("2021-10-11T00:00")); + ext3.getTargetPlatforms().get(0).getLatest().setTimestamp(LocalDateTime.parse("2021-10-11T00:00")); var ext4 = mockExtension("foo", 4.0, 100, 0, "bar", Arrays.asList("Other")); - ext4.getLatest().setTimestamp(LocalDateTime.parse("2021-10-06T00:00")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + ext4.getTargetPlatforms().get(0).getLatest().setTimestamp(LocalDateTime.parse("2021-10-06T00:00")); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, null, 50, 0, null, "timestamp", false); + var searchOptions = new ISearchService.Options(null, null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, "timestamp", false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // all extensions should be there assertThat(result.getTotalHits()).isEqualTo(4); @@ -259,9 +256,9 @@ public void testSortByDownloadCount() throws Exception { var ext2 = mockExtension("java", 4.0, 100, 1000, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext3 = mockExtension("openshift", 4.0, 100, 300, "redhat", Arrays.asList("Snippets", "Other")); var ext4 = mockExtension("foo", 4.0, 100, 500, "bar", Arrays.asList("Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, null, 50, 0, null, "downloadCount", false); + var searchOptions = new ISearchService.Options(null, null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, "downloadCount", false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // all extensions should be there assertThat(result.getTotalHits()).isEqualTo(4); @@ -280,9 +277,9 @@ public void testSortByAverageRating() throws Exception { var ext2 = mockExtension("java", 5.0, 0, 0, "redhat", Arrays.asList("Snippets", "Programming Languages")); var ext3 = mockExtension("openshift", 2.0, 0, 0, "redhat", Arrays.asList("Snippets", "Other")); var ext4 = mockExtension("foo", 1.0, 0, 0, "bar", Arrays.asList("Other")); - Mockito.when(repositories.findAllActiveExtensions()).thenReturn(Streamable.of(ext1, ext2, ext3, ext4)); + Mockito.when(repositories.findAllActiveExtensionTargetPlatforms(ExtensionTargetPlatform.NAME_ANY)).thenReturn(Streamable.of(List.of(ext1, ext2, ext3, ext4).stream().map(Extension::getTargetPlatforms).flatMap(Collection::stream).collect(Collectors.toList()))); - var searchOptions = new ISearchService.Options(null, null, 50, 0, null, "averageRating", false); + var searchOptions = new ISearchService.Options(null, null, ExtensionTargetPlatform.NAME_ANY, 50, 0, null, "averageRating", false); var result = search.search(searchOptions, PageRequest.of(0, 50)); // all extensions should be there assertThat(result.getTotalHits()).isEqualTo(4); @@ -319,12 +316,16 @@ private Extension mockExtension(String name, double averageRating, int ratingCou var isUnverified = false; Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(isUnverified ? 0l : 1l); + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); var extVer = new ExtensionVersion(); - extVer.setExtension(extension); + extVer.setTargetPlatform(targetPlatform); extVer.setCategories(categories); var timestamp = LocalDateTime.parse("2021-10-01T00:00"); extVer.setTimestamp(timestamp); - extension.setLatest(extVer); + targetPlatform.setLatest(extVer); var user = new UserData(); var token = new PersonalAccessToken(); token.setUser(user); @@ -347,5 +348,4 @@ RelevanceService relevanceService() { } } - } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java b/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java index 76ee4d28b..db497bedd 100644 --- a/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java @@ -16,12 +16,7 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.openvsx.entities.Extension; -import org.eclipse.openvsx.entities.ExtensionVersion; -import org.eclipse.openvsx.entities.Namespace; -import org.eclipse.openvsx.entities.NamespaceMembership; -import org.eclipse.openvsx.entities.PersonalAccessToken; -import org.eclipse.openvsx.entities.UserData; +import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.repositories.RepositoryService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -56,7 +51,7 @@ public void testRelevanceAverageRating() throws Exception { var ext2 = mockExtension("bar", 4.0, 100, 0, LocalDateTime.parse("2020-01-01T00:00"), false, false); search.updateSearchEntry(ext1); search.updateSearchEntry(ext2); - + assertThat(index.entries).hasSize(2); assertThat(index.entries.get(0).relevance).isLessThan(index.entries.get(1).relevance); } @@ -68,7 +63,7 @@ public void testRelevanceReviewCount() throws Exception { var ext2 = mockExtension("bar", 4.0, 100, 0, LocalDateTime.parse("2020-01-01T00:00"), false, false); search.updateSearchEntry(ext1); search.updateSearchEntry(ext2); - + assertThat(index.entries).hasSize(2); assertThat(index.entries.get(0).relevance).isLessThan(index.entries.get(1).relevance); } @@ -80,7 +75,7 @@ public void testRelevanceDownloadCount() throws Exception { var ext2 = mockExtension("bar", 0.0, 0, 10, LocalDateTime.parse("2020-01-01T00:00"), false, false); search.updateSearchEntry(ext1); search.updateSearchEntry(ext2); - + assertThat(index.entries).hasSize(2); assertThat(index.entries.get(0).relevance).isLessThan(index.entries.get(1).relevance); } @@ -92,7 +87,7 @@ public void testRelevanceTimestamp() throws Exception { var ext2 = mockExtension("bar", 0.0, 0, 0, LocalDateTime.parse("2020-10-01T00:00"), false, false); search.updateSearchEntry(ext1); search.updateSearchEntry(ext2); - + assertThat(index.entries).hasSize(2); assertThat(index.entries.get(0).relevance).isLessThan(index.entries.get(1).relevance); } @@ -104,7 +99,7 @@ public void testRelevanceUnverified1() throws Exception { var ext2 = mockExtension("bar", 4.0, 10, 10, LocalDateTime.parse("2020-10-01T00:00"), false, false); search.updateSearchEntry(ext1); search.updateSearchEntry(ext2); - + assertThat(index.entries).hasSize(2); assertThat(index.entries.get(0).relevance).isLessThan(index.entries.get(1).relevance); } @@ -116,7 +111,7 @@ public void testRelevanceUnverified2() throws Exception { var ext2 = mockExtension("bar", 4.0, 10, 10, LocalDateTime.parse("2020-10-01T00:00"), false, false); search.updateSearchEntry(ext1); search.updateSearchEntry(ext2); - + assertThat(index.entries).hasSize(2); assertThat(index.entries.get(0).relevance).isLessThan(index.entries.get(1).relevance); } @@ -126,7 +121,7 @@ public void testSoftUpdateExists() throws Exception { var index = mockIndex(true); mockExtensions(); search.updateSearchIndex(false); - + assertThat(index.created).isFalse(); assertThat(index.deleted).isFalse(); assertThat(index.entries).hasSize(3); @@ -137,7 +132,7 @@ public void testSoftUpdateNotExists() throws Exception { var index = mockIndex(false); mockExtensions(); search.updateSearchIndex(false); - + assertThat(index.created).isTrue(); assertThat(index.deleted).isFalse(); assertThat(index.entries).hasSize(3); @@ -148,7 +143,7 @@ public void testHardUpdateExists() throws Exception { var index = mockIndex(true); mockExtensions(); search.updateSearchIndex(true); - + assertThat(index.created).isTrue(); assertThat(index.deleted).isTrue(); assertThat(index.entries).hasSize(3); @@ -159,7 +154,7 @@ public void testHardUpdateNotExists() throws Exception { var index = mockIndex(false); mockExtensions(); search.updateSearchIndex(true); - + assertThat(index.created).isTrue(); assertThat(index.deleted).isFalse(); assertThat(index.entries).hasSize(3); @@ -228,10 +223,14 @@ private Extension mockExtension(String name, double averageRating, int ratingCou extension.setNamespace(namespace); Mockito.when(repositories.countMemberships(namespace, NamespaceMembership.ROLE_OWNER)) .thenReturn(isUnverified ? 0l : 1l); + var targetPlatform = new ExtensionTargetPlatform(); + targetPlatform.setName(ExtensionTargetPlatform.NAME_ANY); + targetPlatform.setExtension(extension); + extension.getTargetPlatforms().add(targetPlatform); var extVer = new ExtensionVersion(); - extVer.setExtension(extension); + extVer.setTargetPlatform(targetPlatform); extVer.setTimestamp(timestamp); - extension.setLatest(extVer); + targetPlatform.setLatest(extVer); var user = new UserData(); var token = new PersonalAccessToken(); token.setUser(user); diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-query-alpine.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-query-alpine.json new file mode 100644 index 000000000..5f13b3fdb --- /dev/null +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-query-alpine.json @@ -0,0 +1,26 @@ +{ + "filters": [ + { + "criteria": [ + { + "filterType": 8, + "value": "alpine-arm64" + }, + { + "filterType": 4, + "value": "test-1" + }, + { + "filterType": 12, + "value": "4096" + } + ], + "pageNumber": 1, + "pageSize": 1, + "sortBy": 0, + "sortOrder": 0 + } + ], + "assetTypes": [], + "flags": 946 +} \ No newline at end of file diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json new file mode 100644 index 000000000..9e1a2a079 --- /dev/null +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json @@ -0,0 +1,102 @@ +{ + "results": [ + { + "extensions": [ + { + "publisher": { + "publisherId": "test-2", + "publisherName": "redhat", + "displayName": null + }, + "extensionId": "test-1", + "extensionName": "vscode-yaml", + "displayName": "YAML", + "flags": "preview", + "shortDescription": "YAML Language Support", + "versions": [ + { + "version": "0.5.2", + "lastUpdated": "2000-01-01T10:00", + "targetPlatform": "alpine-arm64", + "files": [ + { + "assetType": "Microsoft.VisualStudio.Code.Manifest", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/package.json" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.Details", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/README.md" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.License", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/LICENSE.txt" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Icons.Default", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/icon128.png" + }, + { + "assetType": "Microsoft.VisualStudio.Services.VSIXPackage", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/redhat.vscode-yaml-0.5.2.vsix" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/CHANGELOG.md" + } + ], + "properties": [ + { + "key": "Microsoft.VisualStudio.Services.Links.Source", + "value": "https://github.com/redhat-developer/vscode-yaml" + }, + { + "key": "Microsoft.VisualStudio.Code.Engine", + "value": "^1.31.0" + }, + { + "key": "Microsoft.VisualStudio.Code.ExtensionDependencies", + "value": "" + }, + { + "key": "Microsoft.VisualStudio.Code.ExtensionPack", + "value": "" + }, + { + "key": "Microsoft.VisualStudio.Code.LocalizedLanguages", + "value": "" + } + ], + "assetUri": "http://localhost/vscode/asset/redhat/vscode-yaml/0.5.2", + "fallbackAssetUri": "http://localhost/vscode/asset/redhat/vscode-yaml/0.5.2" + } + ], + "statistics": [ + { + "statisticName": "install", + "value": 100 + }, + { + "statisticName": "averagerating", + "value": 3.0 + }, + { + "statisticName": "ratingcount", + "value": 10 + } + ] + } + ], + "resultMetadata": [ + { + "metadataType": "ResultCount", + "metadataItems": [ + { + "name": "TotalCount", + "count": 1 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-query-linux.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-query-linux.json new file mode 100644 index 000000000..576c8671a --- /dev/null +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-query-linux.json @@ -0,0 +1,26 @@ +{ + "filters": [ + { + "criteria": [ + { + "filterType": 8, + "value": "linux-x64" + }, + { + "filterType": 7, + "value": "redhat.vscode-yaml" + }, + { + "filterType": 12, + "value": "4096" + } + ], + "pageNumber": 1, + "pageSize": 1, + "sortBy": 0, + "sortOrder": 0 + } + ], + "assetTypes": [], + "flags": 946 +} \ No newline at end of file diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json new file mode 100644 index 000000000..a39b3abc6 --- /dev/null +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json @@ -0,0 +1,102 @@ +{ + "results": [ + { + "extensions": [ + { + "publisher": { + "publisherId": "test-2", + "publisherName": "redhat", + "displayName": null + }, + "extensionId": "test-1", + "extensionName": "vscode-yaml", + "displayName": "YAML", + "flags": "preview", + "shortDescription": "YAML Language Support", + "versions": [ + { + "version": "0.5.2", + "lastUpdated": "2000-01-01T10:00", + "targetPlatform": "linux-x64", + "files": [ + { + "assetType": "Microsoft.VisualStudio.Code.Manifest", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/package.json" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.Details", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/README.md" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.License", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/LICENSE.txt" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Icons.Default", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/icon128.png" + }, + { + "assetType": "Microsoft.VisualStudio.Services.VSIXPackage", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/redhat.vscode-yaml-0.5.2.vsix" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/CHANGELOG.md" + } + ], + "properties": [ + { + "key": "Microsoft.VisualStudio.Services.Links.Source", + "value": "https://github.com/redhat-developer/vscode-yaml" + }, + { + "key": "Microsoft.VisualStudio.Code.Engine", + "value": "^1.31.0" + }, + { + "key": "Microsoft.VisualStudio.Code.ExtensionDependencies", + "value": "" + }, + { + "key": "Microsoft.VisualStudio.Code.ExtensionPack", + "value": "" + }, + { + "key": "Microsoft.VisualStudio.Code.LocalizedLanguages", + "value": "" + } + ], + "assetUri": "http://localhost/vscode/asset/redhat/vscode-yaml/0.5.2", + "fallbackAssetUri": "http://localhost/vscode/asset/redhat/vscode-yaml/0.5.2" + } + ], + "statistics": [ + { + "statisticName": "install", + "value": 100 + }, + { + "statisticName": "averagerating", + "value": 3.0 + }, + { + "statisticName": "ratingcount", + "value": 10 + } + ] + } + ], + "resultMetadata": [ + { + "metadataType": "ResultCount", + "metadataItems": [ + { + "name": "TotalCount", + "count": 1 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-query-darwin.json b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-query-darwin.json new file mode 100644 index 000000000..b34a3eda2 --- /dev/null +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-query-darwin.json @@ -0,0 +1,26 @@ +{ + "filters": [ + { + "criteria": [ + { + "filterType": 8, + "value": "darwin-x64" + }, + { + "filterType": 10, + "value": "yaml" + }, + { + "filterType": 12, + "value": "4096" + } + ], + "pageNumber": 1, + "pageSize": 50, + "sortBy": 0, + "sortOrder": 0 + } + ], + "assetTypes": [], + "flags": 946 +} \ No newline at end of file diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json new file mode 100644 index 000000000..09ee15a11 --- /dev/null +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json @@ -0,0 +1,102 @@ +{ + "results": [ + { + "extensions": [ + { + "publisher": { + "publisherId": "test-2", + "publisherName": "redhat", + "displayName": null + }, + "extensionId": "test-1", + "extensionName": "vscode-yaml", + "displayName": "YAML", + "flags": "preview", + "shortDescription": "YAML Language Support", + "versions": [ + { + "version": "0.5.2", + "lastUpdated": "2000-01-01T10:00", + "targetPlatform": "darwin-x64", + "files": [ + { + "assetType": "Microsoft.VisualStudio.Code.Manifest", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/package.json" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.Details", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/README.md" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.License", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/LICENSE.txt" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Icons.Default", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/icon128.png" + }, + { + "assetType": "Microsoft.VisualStudio.Services.VSIXPackage", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/redhat.vscode-yaml-0.5.2.vsix" + }, + { + "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", + "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/CHANGELOG.md" + } + ], + "properties": [ + { + "key": "Microsoft.VisualStudio.Services.Links.Source", + "value": "https://github.com/redhat-developer/vscode-yaml" + }, + { + "key": "Microsoft.VisualStudio.Code.Engine", + "value": "^1.31.0" + }, + { + "key": "Microsoft.VisualStudio.Code.ExtensionDependencies", + "value": "" + }, + { + "key": "Microsoft.VisualStudio.Code.ExtensionPack", + "value": "" + }, + { + "key": "Microsoft.VisualStudio.Code.LocalizedLanguages", + "value": "" + } + ], + "assetUri": "http://localhost/vscode/asset/redhat/vscode-yaml/0.5.2", + "fallbackAssetUri": "http://localhost/vscode/asset/redhat/vscode-yaml/0.5.2" + } + ], + "statistics": [ + { + "statisticName": "install", + "value": 100 + }, + { + "statisticName": "averagerating", + "value": 3.0 + }, + { + "statisticName": "ratingcount", + "value": 10 + } + ] + } + ], + "resultMetadata": [ + { + "metadataType": "ResultCount", + "metadataItems": [ + { + "name": "TotalCount", + "count": 1 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/webui/package.json b/webui/package.json index 3c472807c..059fff6e2 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "openvsx-webui", - "version": "0.1.0", + "version": "0.2.0", "description": "User interface for Eclipse Open VSX", "keywords": [ "react", diff --git a/webui/src/extension-registry-service.ts b/webui/src/extension-registry-service.ts index 55afe2b3a..114821968 100644 --- a/webui/src/extension-registry-service.ts +++ b/webui/src/extension-registry-service.ts @@ -279,7 +279,7 @@ export class AdminService { }); } - async deleteExtension(req: { namespace: string, extension: string, version?: string }): Promise> { + async deleteExtension(req: { namespace: string, extension: string, version?: string, targetPlatform?: string }): Promise> { const csrfToken = await this.registry.getCsrfToken(); const headers: Record = {}; if (!isError(csrfToken)) { @@ -289,7 +289,7 @@ export class AdminService { method: 'POST', credentials: true, endpoint: createAbsoluteURL([this.registry.serverUrl, 'admin', 'extension', req.namespace, req.extension, 'delete'], - [{ key: 'version', value: req.version }]), + [{ key: 'version', value: req.version }, { key: 'targetPlatform', value: req.targetPlatform }]), headers }); } diff --git a/webui/src/extension-registry-types.ts b/webui/src/extension-registry-types.ts index b7d53c1f1..e2f1b0f8f 100644 --- a/webui/src/extension-registry-types.ts +++ b/webui/src/extension-registry-types.ts @@ -100,6 +100,12 @@ export interface Extension { badges?: Badge[]; dependencies?: ExtensionReference[]; bundledExtensions?: ExtensionReference[]; + + // key: target platform, value: download link + downloads: { [target: string]: UrlString }; + + // key: version, value: target platforms + allTargetPlatformVersions: { [version: string]: string[] }; } export interface Badge { diff --git a/webui/src/pages/admin-dashboard/extension-remove-dialog.tsx b/webui/src/pages/admin-dashboard/extension-remove-dialog.tsx index a40d5f086..b2c1fa1d0 100644 --- a/webui/src/pages/admin-dashboard/extension-remove-dialog.tsx +++ b/webui/src/pages/admin-dashboard/extension-remove-dialog.tsx @@ -13,6 +13,7 @@ import { Button, Dialog, DialogTitle, DialogContent, DialogContentText, DialogAc import { ButtonWithProgress } from '../../components/button-with-progress'; import { Extension } from '../../extension-registry-types'; import { MainContext } from '../../context'; +import { getTargetPlatformDisplayName } from '../../utils'; export const ExtensionRemoveDialog: FunctionComponent = props => { const { service, handleError } = useContext(MainContext); @@ -20,17 +21,37 @@ export const ExtensionRemoveDialog: FunctionComponent { + return props.versions.find(targetPlatformVersion => targetPlatformVersion[0] === WILDCARD && targetPlatformVersion[1] === WILDCARD); + }; + + const removeVersions = () => { + return props.versions.length > 1 || (props.versions.length === 1 && props.versions[0][1] === WILDCARD); + }; + const handleRemoveVersions = async () => { try { setWorking(true); - if (props.removeAll) { + const removeAll = props.versions.find(([version, target]) => version === WILDCARD && target === WILDCARD); + if (removeAll) { await service.admin.deleteExtension({ namespace: props.extension.namespace, extension: props.extension.name }); } else { - const prms = props.versions.map(version => - service.admin.deleteExtension({ namespace: props.extension.namespace, extension: props.extension.name, version }) - ); + const removeAllVersions = props.versions + .filter(([version, target]) => target === WILDCARD) + .map(([version, target]) => version); + + const prms = removeAllVersions.map(version => service.admin.deleteExtension({ namespace: props.extension.namespace, extension: props.extension.name, version: version })); + + props.versions + .filter(([version, target]) => !removeAllVersions.find(v => version === v)) + .filter(([version, target]) => target !== WILDCARD) + .map(([version, target]) => service.admin.deleteExtension({ namespace: props.extension.namespace, extension: props.extension.name, version: version, targetPlatform: target })) + .forEach(prm => prms.push(prm)); + await Promise.all(prms); } + props.onUpdate(); setDialogOpen(false); } catch (err) { @@ -47,8 +68,7 @@ export const ExtensionRemoveDialog: FunctionComponent setDialogOpen(true)} disabled={props.versions.length === 0} > { - props.removeAll || props.versions.length === 0 ? 'Remove Extension' - : props.versions.length > 1 ? 'Remove Versions' : 'Remove Version' + removeAll() ? 'Remove Extension' : removeVersions() ? 'Remove Versions' : 'Remove Version' } setDialogOpen(false)} > Remove { - props.removeAll ? 'all ' : '' + removeAll() ? 'all ' : '' }{ - !(props.removeAll && props.versions.length <= 1) ? props.versions.length : '' + !(removeAll() && removeVersions()) ? props.versions.filter(([version, target]) => version !== WILDCARD && target !== WILDCARD).length : '' } version{ - props.removeAll || props.versions.length > 1 ? 's' : '' + removeAll() || removeVersions() ? 's' : '' } of {props.extension.name}? - {props.versions.map((version, key) => {version})} + { + props.versions + .filter(([version, target]) => version !== WILDCARD && target !== WILDCARD) + .map(([version, target], key) => {version} ({getTargetPlatformDisplayName(target)}))} @@ -88,8 +111,7 @@ export const ExtensionRemoveDialog: FunctionComponent void; } diff --git a/webui/src/pages/admin-dashboard/extension-version-container.tsx b/webui/src/pages/admin-dashboard/extension-version-container.tsx index 6ffdaa566..c3bd943dc 100644 --- a/webui/src/pages/admin-dashboard/extension-version-container.tsx +++ b/webui/src/pages/admin-dashboard/extension-version-container.tsx @@ -12,8 +12,18 @@ import React, { FunctionComponent, useState, useEffect } from 'react'; import { Extension, VERSION_ALIASES } from '../../extension-registry-types'; import { Grid, makeStyles, Typography, FormControl, FormGroup, FormControlLabel, Checkbox } from '@material-ui/core'; import { ExtensionRemoveDialog } from './extension-remove-dialog'; +import { getTargetPlatformDisplayName } from '../../utils'; const useStyles = makeStyles((theme) => ({ + indent0: { + paddingLeft: '0 px' + }, + indent1: { + paddingLeft: `${theme.spacing(4)}px` + }, + indent2: { + paddingLeft: `${theme.spacing(8)}px` + }, extensionLogo: { height: '7.5rem', maxWidth: '9rem', @@ -37,50 +47,76 @@ const useStyles = makeStyles((theme) => ({ })); export const ExtensionVersionContainer: FunctionComponent = props => { + const WILDCARD = '*'; const { extension } = props; const classes = useStyles(); - const getVersions = () => { + const toTargetPlatformVersionKey = (version: string, target: string): string => { + return version + '/' + target; + }; + + const fromTargetPlatformVersionKey = (key: string): string[] => { + return key.split('/'); + }; + + const getTargetPlatformVersions = () => { const versionMap = new Map(); - Object.keys(extension.allVersions) + versionMap.set(toTargetPlatformVersionKey(WILDCARD, WILDCARD), false); + Object.keys(extension.allTargetPlatformVersions) .filter(version => VERSION_ALIASES.indexOf(version) < 0) .forEach(version => { - versionMap.set(version, false); - }); + versionMap.set(toTargetPlatformVersionKey(version, WILDCARD), false); + const targetPlatforms = extension.allTargetPlatformVersions[version]; + targetPlatforms.forEach(target => versionMap.set(toTargetPlatformVersionKey(version, target), false)); + }); + return versionMap; }; - const [versions, setVersions] = useState(getVersions()); - const [allChecked, setAllChecked] = useState(false); + const [targetPlatformVersions, setTargetPlatformVersions] = useState(getTargetPlatformVersions()); useEffect(() => { - setVersions(getVersions()); + setTargetPlatformVersions(getTargetPlatformVersions()); }, [props.extension]); const handleChange = (event: React.ChangeEvent) => { - const newVersionMap = new Map(); - let newAllChecked = true; - versions.forEach((checked, version) => { - if (version === event.target.name) { + const newTargetPlatformVersions = new Map(); + targetPlatformVersions.forEach((checked, targetPlatformVersion) => { + const changedTargetPlatformVersion = event.target.name; + const equals = (change: string, current: string) => { + return change === WILDCARD || current === change; + }; + + const [changedVersion, changedTarget] = fromTargetPlatformVersionKey(changedTargetPlatformVersion); + const [version, target] = fromTargetPlatformVersionKey(targetPlatformVersion); + if (equals(changedVersion, version) && equals(changedTarget, target)) { checked = event.target.checked; } - newVersionMap.set(version, checked); - if (!checked) { - newAllChecked = false; + + newTargetPlatformVersions.set(targetPlatformVersion, checked); + }); + + const newVersionsMap = new Map(); + newTargetPlatformVersions.forEach((newChecked, key) => { + const [version, target] = fromTargetPlatformVersionKey(key); + if (version !== WILDCARD && target !== WILDCARD) { + let checked = newVersionsMap.get(version); + if (checked === undefined) { + checked = true; + } + + newVersionsMap.set(version, checked && newChecked); } }); - setVersions(newVersionMap); - setAllChecked(newAllChecked); - }; - const handleChangeAll = (event: React.ChangeEvent) => { - const newVersionMap = new Map(); - const newAllChecked = event.target.checked; - versions.forEach((_, version) => { - newVersionMap.set(version, newAllChecked); + newVersionsMap.forEach((checked, version) => { + newTargetPlatformVersions.set(toTargetPlatformVersionKey(version, WILDCARD), checked); }); - setAllChecked(newAllChecked); - setVersions(newVersionMap); + + const allChecked = Array.from(newTargetPlatformVersions.values()).filter(checked => checked === true).length === newTargetPlatformVersions.size - 1; + newTargetPlatformVersions.set(toTargetPlatformVersionKey(WILDCARD, WILDCARD), allChecked); + + setTargetPlatformVersions(newTargetPlatformVersions); }; return <> @@ -116,18 +152,29 @@ export const ExtensionVersionContainer: FunctionComponent - } - label='All Versions' - /> { - Array.from(versions.entries()) - .map(([version, checked], index) => - } - label={version} /> - ) + Array.from(targetPlatformVersions.entries()) + .map(([targetPlatformVersion, checked], index) => { + const [version, target] = fromTargetPlatformVersionKey(targetPlatformVersion); + let label: string; + let indentClass: string; + if (version === WILDCARD && target === WILDCARD) { + label = 'All Versions'; + indentClass = classes.indent0; + } else if (target === WILDCARD) { + label = version; + indentClass = classes.indent1; + } else { + label = getTargetPlatformDisplayName(target); + indentClass = classes.indent2; + } + + return } + label={label} />; + }) } @@ -140,11 +187,10 @@ export const ExtensionVersionContainer: FunctionComponent checked) - .map(([version]) => version)} /> + .map(([targetPlatformVersion]) => fromTargetPlatformVersionKey(targetPlatformVersion))} /> diff --git a/webui/src/pages/extension-detail/extension-detail-downloads-menu.tsx b/webui/src/pages/extension-detail/extension-detail-downloads-menu.tsx new file mode 100644 index 000000000..237477354 --- /dev/null +++ b/webui/src/pages/extension-detail/extension-detail-downloads-menu.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import { Box, Button, Menu, MenuItem, Theme, Typography } from '@material-ui/core'; +import { Link as RouteLink, RouteComponentProps, withRouter } from 'react-router-dom'; +import { WithStyles, withStyles, createStyles } from '@material-ui/styles'; +import { getTargetPlatformDisplayName } from '../../utils'; +import { UrlString } from '../..'; + +const downloadsMenuStyles = (theme: Theme) => createStyles({ + link: { + cursor: 'pointer', + textDecoration: 'none' + }, + menuItem: { + cursor: 'auto' + }, + downloadButton: { + marginTop: theme.spacing(2) + }, + alignVertically: { + display: 'flex', + alignItems: 'center' + } +}); + +class ExtensionDetailDownloadsMenuComponent extends React.Component { + + constructor(props: ExtensionDetailDownloadsMenu.Props) { + super(props); + this.state = { anchorEl: null }; + } + + render(): React.ReactElement { + const open = Boolean(this.state.anchorEl); + const handleClick = (event: React.MouseEvent) => { + this.setState({ anchorEl: event.currentTarget }); + }; + const handleClose = () => { + this.setState({ anchorEl: null }); + }; + + return + + + { + Object.keys(this.props.downloads).map((target) => { + const downloadLink = this.props.downloads[target]; + return + + + {getTargetPlatformDisplayName(target)} + + + ; + }) + } + + ; + } +} + +export namespace ExtensionDetailDownloadsMenu { + export interface Props extends WithStyles, RouteComponentProps { + downloads: {[target: string]: UrlString}; + } + export interface State { + anchorEl: HTMLElement | null; + } +} + +export const ExtensionDetailDownloadsMenu = withStyles(downloadsMenuStyles)(withRouter(ExtensionDetailDownloadsMenuComponent)); \ No newline at end of file diff --git a/webui/src/pages/extension-detail/extension-detail-overview.tsx b/webui/src/pages/extension-detail/extension-detail-overview.tsx index 7fbd5ac83..8dd2d740b 100644 --- a/webui/src/pages/extension-detail/extension-detail-overview.tsx +++ b/webui/src/pages/extension-detail/extension-detail-overview.tsx @@ -16,13 +16,15 @@ import GitHubIcon from '@material-ui/icons/GitHub'; import BugReportIcon from '@material-ui/icons/BugReport'; import QuestionAnswerIcon from '@material-ui/icons/QuestionAnswer'; import { MainContext } from '../../context'; -import { addQuery, createRoute } from '../../utils'; +import { addQuery, createRoute, getTargetPlatformDisplayName } from '../../utils'; import { DelayedLoadIndicator } from '../../components/delayed-load-indicator'; import { SanitizedMarkdown } from '../../components/sanitized-markdown'; import { Timestamp } from '../../components/timestamp'; import { Extension, ExtensionReference, VERSION_ALIASES } from '../../extension-registry-types'; import { ExtensionListRoutes } from '../extension-list/extension-list-container'; import { ExtensionDetailRoutes } from './extension-detail'; +import { ExtensionDetailDownloadsMenu } from './extension-detail-downloads-menu'; +import { UrlString } from '../..'; const overviewStyles = (theme: Theme) => createStyles({ overview: { @@ -176,6 +178,12 @@ class ExtensionDetailOverviewComponent extends React.Component + + + Works With + {this.renderWorksWithList(extension.downloads)} + + Resources @@ -183,11 +191,15 @@ class ExtensionDetailOverviewComponent extends React.Component - Download - + { + Object.keys(extension.downloads).length == 1 ? + + : + } { extension.bundledExtensions !== undefined && extension.bundledExtensions.length > 0 ? @@ -311,6 +323,13 @@ class ExtensionDetailOverviewComponent extends React.Component; } + protected renderWorksWithList(downloads: {[target: string]: UrlString}): React.ReactNode { + return Object.keys(downloads).map((target, index) => { + const displayName = getTargetPlatformDisplayName(target); + return displayName ? {index > 0 ? ', ' : ''}{displayName} : null; + }); + } + protected renderResourceLink(label: string, href?: string): React.ReactNode { if (!href || !(href.startsWith('http') || href.startsWith('mailto'))) { return ''; diff --git a/webui/src/utils.ts b/webui/src/utils.ts index 7335a5018..78a602437 100644 --- a/webui/src/utils.ts +++ b/webui/src/utils.ts @@ -148,3 +148,22 @@ export function getCookieValueByKey(key: string): string | undefined { } return undefined; } + +export function getTargetPlatformDisplayName(target: string): string { + const targetPlatformDisplayNames = new Map([ + ['', 'Universal'], + ['win32-ia32', 'Windows x86'], + ['win32-x64', 'Windows x64'], + ['win32-arm64', 'Windows ARM'], + ['linux-x64', 'Linux x64'], + ['linux-arm64', 'Linux ARM64'], + ['linux-armhf', 'Linux ARMhf'], + ['alpine-x64', 'Alpine Linux 64 bit'], + ['alpine-arm64', 'Alpine Linux ARM64'], + ['darwin-x64', 'macOS Intel'], + ['darwin-arm64', 'macOS Apple Silicon'], + ['web', 'Web'] + ]); + + return targetPlatformDisplayNames.get(target) || ''; +}