diff options
Diffstat (limited to 'app/presenters/packages')
12 files changed, 798 insertions, 0 deletions
diff --git a/app/presenters/packages/composer/packages_presenter.rb b/app/presenters/packages/composer/packages_presenter.rb new file mode 100644 index 00000000000..84f266989e9 --- /dev/null +++ b/app/presenters/packages/composer/packages_presenter.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Packages + module Composer + class PackagesPresenter + include API::Helpers::RelatedResourcesHelpers + + def initialize(group, packages) + @group = group + @packages = packages + end + + def root + path = api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%', format: '.json' }, true) + { 'packages' => [], 'provider-includes' => { 'p/%hash%.json' => { 'sha256' => provider_sha } }, 'providers-url' => path } + end + + def provider + { 'providers' => providers_map } + end + + def package_versions(packages = @packages) + { 'packages' => { packages.first.name => package_versions_map(packages) } } + end + + private + + def package_versions_map(packages) + packages.each_with_object({}) do |package, map| + map[package.version] = package_metadata(package) + end + end + + def package_metadata(package) + json = package.composer_metadatum.composer_json + + json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version) + end + + def package_dist(package) + sha = package.composer_metadatum.target_sha + archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true) + + { + 'type' => 'zip', + 'url' => expose_url(archive_api_path) + "?sha=#{sha}", + 'reference' => sha, + 'shasum' => '' + } + end + + def providers_map + map = {} + + @packages.group_by(&:name).each_pair do |package_name, packages| + map[package_name] = { 'sha256' => package_versions_sha(packages) } + end + + map + end + + def package_versions_sha(packages) + Digest::SHA256.hexdigest(package_versions(packages).to_json) + end + + def provider_sha + Digest::SHA256.hexdigest(provider.to_json) + end + end + end +end diff --git a/app/presenters/packages/conan/package_presenter.rb b/app/presenters/packages/conan/package_presenter.rb new file mode 100644 index 00000000000..5141c450412 --- /dev/null +++ b/app/presenters/packages/conan/package_presenter.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Packages + module Conan + class PackagePresenter + include API::Helpers::RelatedResourcesHelpers + include Gitlab::Utils::StrongMemoize + + attr_reader :params + + def initialize(recipe, user, project, params = {}) + @recipe = recipe + @user = user + @project = project + @params = params + end + + def recipe_urls + map_package_files do |package_file| + build_recipe_file_url(package_file) if package_file.conan_file_metadatum.recipe_file? + end + end + + def recipe_snapshot + map_package_files do |package_file| + package_file.file_md5 if package_file.conan_file_metadatum.recipe_file? + end + end + + def package_urls + map_package_files do |package_file| + next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file) + + build_package_file_url(package_file) + end + end + + def package_snapshot + map_package_files do |package_file| + next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file) + + package_file.file_md5 + end + end + + private + + def build_recipe_file_url(package_file) + expose_url( + api_v4_packages_conan_v1_files_export_path( + package_name: package.name, + package_version: package.version, + package_username: package.conan_metadatum.package_username, + package_channel: package.conan_metadatum.package_channel, + recipe_revision: package_file.conan_file_metadatum.recipe_revision, + file_name: package_file.file_name + ) + ) + end + + def build_package_file_url(package_file) + expose_url( + api_v4_packages_conan_v1_files_package_path( + package_name: package.name, + package_version: package.version, + package_username: package.conan_metadatum.package_username, + package_channel: package.conan_metadatum.package_channel, + recipe_revision: package_file.conan_file_metadatum.recipe_revision, + conan_package_reference: package_file.conan_file_metadatum.conan_package_reference, + package_revision: package_file.conan_file_metadatum.package_revision, + file_name: package_file.file_name + ) + ) + end + + def map_package_files + package_files.to_a.map do |package_file| + key = package_file.file_name + value = yield(package_file) + next unless key && value + + [key, value] + end.compact.to_h + end + + def package_files + return unless package + + @package_files ||= package.package_files.preload_conan_file_metadata + end + + def package + strong_memoize(:package) do + name, version = @recipe.split('@')[0].split('/') + + @project.packages + .conan + .with_name(name) + .with_version(version) + .order_created + .last + end + end + + def matching_reference?(package_file) + package_file.conan_file_metadatum.conan_package_reference == conan_package_reference + end + + def conan_package_reference + params[:conan_package_reference] + end + end + end +end diff --git a/app/presenters/packages/detail/package_presenter.rb b/app/presenters/packages/detail/package_presenter.rb new file mode 100644 index 00000000000..f6e068302c1 --- /dev/null +++ b/app/presenters/packages/detail/package_presenter.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Packages + module Detail + class PackagePresenter + def initialize(package) + @package = package + end + + def detail_view + package_detail = { + id: @package.id, + created_at: @package.created_at, + name: @package.name, + package_files: @package.package_files.map { |pf| build_package_file_view(pf) }, + package_type: @package.package_type, + project_id: @package.project_id, + tags: @package.tags.as_json, + updated_at: @package.updated_at, + version: @package.version + } + + package_detail[:maven_metadatum] = @package.maven_metadatum if @package.maven_metadatum + package_detail[:nuget_metadatum] = @package.nuget_metadatum if @package.nuget_metadatum + package_detail[:dependency_links] = @package.dependency_links.map(&method(:build_dependency_links)) + package_detail[:pipeline] = build_pipeline_info(@package.build_info.pipeline) if @package.build_info + + package_detail + end + + private + + def build_package_file_view(package_file) + { + created_at: package_file.created_at, + download_path: package_file.download_path, + file_name: package_file.file_name, + size: package_file.size + } + end + + def build_pipeline_info(pipeline_info) + { + created_at: pipeline_info.created_at, + id: pipeline_info.id, + sha: pipeline_info.sha, + ref: pipeline_info.ref, + git_commit_message: pipeline_info.git_commit_message, + user: build_user_info(pipeline_info.user), + project: { + name: pipeline_info.project.name, + web_url: pipeline_info.project.web_url + } + } + end + + def build_user_info(user) + return unless user + + { + avatar_url: user.avatar_url, + name: user.name + } + end + + def build_dependency_links(link) + { + name: link.dependency.name, + version_pattern: link.dependency.version_pattern, + target_framework: link.nuget_metadatum&.target_framework + }.compact + end + end + end +end diff --git a/app/presenters/packages/go/module_version_presenter.rb b/app/presenters/packages/go/module_version_presenter.rb new file mode 100644 index 00000000000..4c86eae46cd --- /dev/null +++ b/app/presenters/packages/go/module_version_presenter.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Packages + module Go + class ModuleVersionPresenter + def initialize(version) + @version = version + end + + def name + @version.name + end + + def time + @version.commit.committed_date + end + end + end +end diff --git a/app/presenters/packages/npm/package_presenter.rb b/app/presenters/packages/npm/package_presenter.rb new file mode 100644 index 00000000000..a3ab10d3913 --- /dev/null +++ b/app/presenters/packages/npm/package_presenter.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Packages + module Npm + class PackagePresenter + include API::Helpers::RelatedResourcesHelpers + + attr_reader :name, :packages + + NPM_VALID_DEPENDENCY_TYPES = %i[dependencies devDependencies bundleDependencies peerDependencies].freeze + + def initialize(name, packages) + @name = name + @packages = packages + end + + def versions + package_versions = {} + + packages.each do |package| + package_file = package.package_files.last + + next unless package_file + + package_versions[package.version] = build_package_version(package, package_file) + end + + package_versions + end + + def dist_tags + build_package_tags.tap { |t| t["latest"] ||= sorted_versions.last } + end + + private + + def build_package_tags + Hash[ + package_tags.map { |tag| [tag.name, tag.package.version] } + ] + end + + def build_package_version(package, package_file) + { + name: package.name, + version: package.version, + dist: { + shasum: package_file.file_sha1, + tarball: tarball_url(package, package_file) + } + }.tap do |package_version| + package_version.merge!(build_package_dependencies(package)) + end + end + + def tarball_url(package, package_file) + expose_url "#{api_v4_projects_path(id: package.project_id)}" \ + "/packages/npm/#{package.name}" \ + "/-/#{package_file.file_name}" + end + + def build_package_dependencies(package) + dependencies = Hash.new { |h, key| h[key] = {} } + dependency_links = package.dependency_links + .with_dependency_type(NPM_VALID_DEPENDENCY_TYPES) + .includes_dependency + + dependency_links.find_each do |dependency_link| + dependency = dependency_link.dependency + dependencies[dependency_link.dependency_type][dependency.name] = dependency.version_pattern + end + + dependencies + end + + def sorted_versions + versions = packages.map(&:version).compact + VersionSorter.sort(versions) + end + + def package_tags + Packages::Tag.for_packages(packages) + .preload_package + end + end + end +end diff --git a/app/presenters/packages/nuget/package_metadata_presenter.rb b/app/presenters/packages/nuget/package_metadata_presenter.rb new file mode 100644 index 00000000000..500fc982e11 --- /dev/null +++ b/app/presenters/packages/nuget/package_metadata_presenter.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Packages + module Nuget + class PackageMetadataPresenter + include Packages::Nuget::PresenterHelpers + + def initialize(package) + @package = package + end + + def json_url + json_url_for(@package) + end + + def archive_url + archive_url_for(@package) + end + + def catalog_entry + catalog_entry_for(@package) + end + end + end +end diff --git a/app/presenters/packages/nuget/packages_metadata_presenter.rb b/app/presenters/packages/nuget/packages_metadata_presenter.rb new file mode 100644 index 00000000000..5f22d5dd8a1 --- /dev/null +++ b/app/presenters/packages/nuget/packages_metadata_presenter.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Packages + module Nuget + class PackagesMetadataPresenter + include Packages::Nuget::PresenterHelpers + include Gitlab::Utils::StrongMemoize + + COUNT = 1.freeze + + def initialize(packages) + @packages = packages + end + + def count + COUNT + end + + def items + [summary] + end + + private + + def summary + { + json_url: json_url, + lower_version: lower_version, + upper_version: upper_version, + packages_count: @packages.count, + packages: @packages.map { |pkg| metadata_for(pkg) } + } + end + + def metadata_for(package) + { + json_url: json_url_for(package), + archive_url: archive_url_for(package), + catalog_entry: catalog_entry_for(package) + } + end + + def json_url + json_url_for(@packages.first) + end + + def lower_version + sorted_versions.first + end + + def upper_version + sorted_versions.last + end + + def sorted_versions + strong_memoize(:sorted_versions) do + versions = @packages.map(&:version).compact + VersionSorter.sort(versions) + end + end + end + end +end diff --git a/app/presenters/packages/nuget/packages_versions_presenter.rb b/app/presenters/packages/nuget/packages_versions_presenter.rb new file mode 100644 index 00000000000..7f4ce4dbb2f --- /dev/null +++ b/app/presenters/packages/nuget/packages_versions_presenter.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Packages + module Nuget + class PackagesVersionsPresenter + def initialize(packages) + @packages = packages + end + + def versions + @packages.pluck_versions.sort + end + end + end +end diff --git a/app/presenters/packages/nuget/presenter_helpers.rb b/app/presenters/packages/nuget/presenter_helpers.rb new file mode 100644 index 00000000000..cc7e8619220 --- /dev/null +++ b/app/presenters/packages/nuget/presenter_helpers.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module Packages + module Nuget + module PresenterHelpers + include ::API::Helpers::RelatedResourcesHelpers + + BLANK_STRING = '' + PACKAGE_DEPENDENCY_GROUP = 'PackageDependencyGroup' + PACKAGE_DEPENDENCY = 'PackageDependency' + + private + + def json_url_for(package) + path = api_v4_projects_packages_nuget_metadata_package_name_package_version_path( + { + id: package.project_id, + package_name: package.name, + package_version: package.version, + format: '.json' + }, + true + ) + + expose_url(path) + end + + def archive_url_for(package) + path = api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path( + { + id: package.project_id, + package_name: package.name, + package_version: package.version, + package_filename: package.package_files.last&.file_name + }, + true + ) + + expose_url(path) + end + + def catalog_entry_for(package) + { + json_url: json_url_for(package), + authors: BLANK_STRING, + dependency_groups: dependency_groups_for(package), + package_name: package.name, + package_version: package.version, + archive_url: archive_url_for(package), + summary: BLANK_STRING, + tags: tags_for(package), + metadatum: metadatum_for(package) + } + end + + def dependency_groups_for(package) + base_nuget_id = "#{json_url_for(package)}#dependencyGroup" + + dependency_links_grouped_by_target_framework(package).map do |target_framework, dependency_links| + nuget_id = target_framework_nuget_id(base_nuget_id, target_framework) + { + id: nuget_id, + type: PACKAGE_DEPENDENCY_GROUP, + target_framework: target_framework, + dependencies: dependencies_for(nuget_id, dependency_links) + }.compact + end + end + + def dependency_links_grouped_by_target_framework(package) + package + .dependency_links + .includes_dependency + .preload_nuget_metadatum + .group_by { |dependency_link| dependency_link.nuget_metadatum&.target_framework } + end + + def dependencies_for(nuget_id, dependency_links) + return [] if dependency_links.empty? + + dependency_links.map do |dependency_link| + dependency = dependency_link.dependency + { + id: "#{nuget_id}/#{dependency.name.downcase}", + type: PACKAGE_DEPENDENCY, + name: dependency.name, + range: dependency.version_pattern + } + end + end + + def target_framework_nuget_id(base_nuget_id, target_framework) + target_framework.blank? ? base_nuget_id : "#{base_nuget_id}/#{target_framework.downcase}" + end + + def metadatum_for(package) + metadatum = package.nuget_metadatum + return {} unless metadatum + + metadatum.slice(:project_url, :license_url, :icon_url) + .compact + end + + def base_path_for(package) + api_v4_projects_packages_nuget_path(id: package.project_id) + end + + def tags_for(package) + package.tag_names.join(::Packages::Tag::NUGET_TAGS_SEPARATOR) + end + end + end +end diff --git a/app/presenters/packages/nuget/search_results_presenter.rb b/app/presenters/packages/nuget/search_results_presenter.rb new file mode 100644 index 00000000000..96c8fe7dd2a --- /dev/null +++ b/app/presenters/packages/nuget/search_results_presenter.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Packages + module Nuget + class SearchResultsPresenter + include Packages::Nuget::PresenterHelpers + include Gitlab::Utils::StrongMemoize + + delegate :total_count, to: :@search + + def initialize(search) + @search = search + @package_versions = {} + end + + def data + strong_memoize(:data) do + @search.results.group_by(&:name).map do |package_name, packages| + latest_version = latest_version(packages) + latest_package = packages.find { |pkg| pkg.version == latest_version } + + { + type: 'Package', + authors: '', + name: package_name, + version: latest_version, + versions: build_package_versions(packages), + summary: '', + total_downloads: 0, + verified: true, + tags: tags_for(latest_package), + metadatum: metadatum_for(latest_package) + } + end + end + end + + private + + def build_package_versions(packages) + packages.map do |pkg| + { + json_url: json_url_for(pkg), + downloads: 0, + version: pkg.version + } + end + end + + def latest_version(packages) + versions = packages.map(&:version).compact + VersionSorter.sort(versions).last # rubocop: disable Style/UnneededSort + end + end + end +end diff --git a/app/presenters/packages/nuget/service_index_presenter.rb b/app/presenters/packages/nuget/service_index_presenter.rb new file mode 100644 index 00000000000..ed00b36b362 --- /dev/null +++ b/app/presenters/packages/nuget/service_index_presenter.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module Packages + module Nuget + class ServiceIndexPresenter + include API::Helpers::RelatedResourcesHelpers + + SERVICE_VERSIONS = { + download: %w[PackageBaseAddress/3.0.0], + search: %w[SearchQueryService SearchQueryService/3.0.0-beta SearchQueryService/3.0.0-rc], + publish: %w[PackagePublish/2.0.0], + metadata: %w[RegistrationsBaseUrl RegistrationsBaseUrl/3.0.0-beta RegistrationsBaseUrl/3.0.0-rc] + }.freeze + + SERVICE_COMMENTS = { + download: 'Get package content (.nupkg).', + search: 'Filter and search for packages by keyword.', + publish: 'Push and delete (or unlist) packages.', + metadata: 'Get package metadata.' + }.freeze + + VERSION = '3.0.0'.freeze + + def initialize(project) + @project = project + end + + def version + VERSION + end + + def resources + [ + build_service(:download), + build_service(:search), + build_service(:publish), + build_service(:metadata) + ].flatten + end + + private + + def build_service(service_type) + url = build_service_url(service_type) + comment = SERVICE_COMMENTS[service_type] + + SERVICE_VERSIONS[service_type].map do |version| + { :@id => url, :@type => version, :comment => comment } + end + end + + def build_service_url(service_type) + base_path = api_v4_projects_packages_nuget_path(id: @project.id) + + full_path = case service_type + when :download + api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path( + { + id: @project.id, + package_name: nil, + package_version: nil, + package_filename: nil + }, + true + ) + when :search + "#{base_path}/query" + when :metadata + api_v4_projects_packages_nuget_metadata_package_name_package_version_path( + { + id: @project.id, + package_name: nil, + package_version: nil + }, + true + ) + when :publish + base_path + end + + expose_url(full_path) + end + end + end +end diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb new file mode 100644 index 00000000000..4192e974645 --- /dev/null +++ b/app/presenters/packages/pypi/package_presenter.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# Display package version data acording to PyPi +# Simple API: https://warehouse.pypa.io/api-reference/legacy/#simple-project-api +module Packages + module Pypi + class PackagePresenter + include API::Helpers::RelatedResourcesHelpers + + def initialize(packages, project) + @packages = packages + @project = project + end + + # Returns the HTML body for PyPi simple API. + # Basically a list of package download links for a specific + # package + def body + <<-HTML + <!DOCTYPE html> + <html> + <head> + <title>Links for #{escape(name)}</title> + </head> + <body> + <h1>Links for #{escape(name)}</h1> + #{links} + </body> + </html> + HTML + end + + private + + def links + refs = [] + + @packages.map do |package| + package.package_files.each do |file| + url = build_pypi_package_path(file) + + refs << package_link(url, package.pypi_metadatum.required_python, file.file_name) + end + end + + refs.join + end + + def package_link(url, required_python, filename) + "<a href=\"#{url}\" data-requires-python=\"#{escape(required_python)}\">#{filename}</a><br>" + end + + def build_pypi_package_path(file) + expose_url( + api_v4_projects_packages_pypi_files_file_identifier_path( + { + id: @project.id, + sha256: file.file_sha256, + file_identifier: file.file_name + }, + true + ) + ) + "#sha256=#{file.file_sha256}" + end + + def name + @packages.first.name + end + + def escape(str) + ERB::Util.html_escape(str) + end + end + end +end |