diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 4e9a20b7c..38d0efc95 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -6,12 +6,26 @@ class Api::V1::BaseController < ApplicationController rescue_from CanCan::AccessDenied do |exception| respond_to do |format| - format.json { render :json => {:message => t("flash.exception_message")}.to_json, :status => 403 } + format.json { render json: {message: t('flash.exception_message')}.to_json, status: 403 } + format.csv { render text: t('flash.exception_message'), status: 403 } end end protected + def set_csv_file_headers(file_name) + headers['Content-Type'] = 'text/csv' + headers['Content-disposition'] = "attachment; filename=\"#{file_name}.csv\"" + end + + def set_streaming_headers + # nginx doc: Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications + headers['X-Accel-Buffering'] = 'no' + + headers['Cache-Control'] ||= 'no-cache' + headers.delete 'Content-Length' + end + def set_locale I18n.locale = :en end diff --git a/app/controllers/api/v1/repositories_controller.rb b/app/controllers/api/v1/repositories_controller.rb index 2f2b4d630..abb24c1bf 100644 --- a/app/controllers/api/v1/repositories_controller.rb +++ b/app/controllers/api/v1/repositories_controller.rb @@ -32,6 +32,36 @@ class Api::V1::RepositoriesController < Api::V1::BaseController def key_pair end + # Only one request per 15 minutes for each platform + def packages + key, now = [@repository.platform.id, :repository_packages], Time.zone.now + last_request = Rails.cache.read(key) + if last_request.present? && last_request + 15.minutes > now + raise CanCan::AccessDenied + else + + Rails.cache.write(key, now, expires_at: 15.minutes) + respond_to do |format| + format.csv do + set_csv_file_headers :packages + set_streaming_headers + + response.status = 200 + + # setting the body to an enumerator, rails will iterate this enumerator + self.response_body = Enumerator.new do |y| + y << Api::V1::RepositoryPackagePresenter.csv_header.to_s + BuildList::Package.by_repository(@repository) do |package| + y << Api::V1::RepositoryPackagePresenter.new(package).to_csv_row.to_s + end + end + + end + end + + end + end + def add_repo_lock_file @repository.add_repo_lock_file render_json_response @repository, "'.repo.lock' file has been added to repository successfully" diff --git a/app/models/ability.rb b/app/models/ability.rb index f2f4ca4a7..7935bf0f3 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -122,7 +122,7 @@ class Ability can([:read, :projects_list, :projects], Repository, read_relations_for('repositories')) {|repository| can? :show, repository.platform} can([:read, :projects_list, :projects], Repository, read_relations_for('repositories', 'platforms')) {|repository| local_reader? repository.platform} can([:create, :edit, :update, :destroy, :projects_list, :projects, :add_project, :remove_project, :regenerate_metadata, :sync_lock_file, :add_repo_lock_file, :remove_repo_lock_file], Repository) {|repository| local_admin? repository.platform} - can([:remove_members, :remove_member, :add_member, :signatures], Repository) {|repository| owner?(repository.platform) || local_admin?(repository.platform)} + can([:remove_members, :remove_member, :add_member, :signatures, :packages], Repository) {|repository| owner?(repository.platform) || local_admin?(repository.platform)} can([:add_project, :remove_project], Repository) {|repository| repository.members.exists?(:id => user.id)} can(:clear, Platform) {|platform| owner?(platform) && platform.personal?} can(:regenerate_metadata, Platform) {|platform| owner?(platform) || local_admin?(platform)} @@ -167,6 +167,7 @@ class Ability # Shared cannot rights for all users (registered, admin) cannot [:regenerate_metadata, :destroy], Platform, :platform_type => 'personal' cannot [:create, :destroy], Repository, :platform => {:platform_type => 'personal'}, :name => 'main' + cannot [:packages], Repository, :platform => {:platform_type => 'personal'} cannot [:remove_members, :remove_member, :add_member, :sync_lock_file, :add_repo_lock_file, :remove_repo_lock_file], Repository, :platform => {:platform_type => 'personal'} cannot :clear, Platform, :platform_type => 'main' diff --git a/app/models/build_list/package.rb b/app/models/build_list/package.rb index caffcfd03..c013b2478 100644 --- a/app/models/build_list/package.rb +++ b/app/models/build_list/package.rb @@ -28,7 +28,6 @@ class BuildList::Package < ActiveRecord::Base project.maintainer end - # Comparison between versions # @param [BuildList::Package] other # @return [Number] -1 if +other+ is greater than, 0 if +other+ is equal to, @@ -37,6 +36,15 @@ class BuildList::Package < ActiveRecord::Base RPM::C.rpmvercmp to_vre_epoch_zero, other.to_vre_epoch_zero end + def self.by_repository(repository, &block) + # find_each will batch the results instead of getting all in one go + actual.where( + build_lists: {save_to_repository_id: repository} + ).joins(build_list: :save_to_repository).includes(project: :maintainer).find_each do |package| + yield package + end + end + protected def set_epoch diff --git a/app/presenters/api/v1/repository_package_presenter.rb b/app/presenters/api/v1/repository_package_presenter.rb new file mode 100644 index 000000000..65226b389 --- /dev/null +++ b/app/presenters/api/v1/repository_package_presenter.rb @@ -0,0 +1,54 @@ +require 'csv' +class Api::V1::RepositoryPackagePresenter < ApplicationPresenter + CSV_HEADERS = { + project_owner: 'Project owner', + project_name: 'Project name', + package_name: 'Package name', + epoch: 'Epoch', + version: 'Version', + release: 'Release', + maintainer_uname: 'Maintainer uname', + maintainer_email: 'Maintainer email', + committer_uname: 'Committer uname', + committer_email: 'Committer email' + } + + attr_reader :package + delegate *%i(project name epoch version release assignee build_list), to: :package + + def initialize(package) + @package = package + end + + def to_csv_row + commit = project.repo.commit(build_list.commit_hash) + committer = User.where(email: commit.committer.email).first || commit.committer if commit + if committer.is_a? User + committer_uname, committer_email = committer.uname, committer.email + elsif committer.is_a? Grit::Actor + committer_uname, committer_email = committer.name, committer.email + end + CSV::Row.new( + CSV_HEADERS.keys, + [ + project.owner_uname, + project.name, + name, + epoch, + version, + release, + assignee.uname, + assignee.email, + committer_uname, + committer_email + ] + ) + end + + def self.csv_header + # Using ruby's built-in CSV::Row class + # true - means its a header + CSV::Row.new CSV_HEADERS.keys, CSV_HEADERS.values, true + end + +end diff --git a/config/routes.rb b/config/routes.rb index cfefca5ab..246b36a5a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,6 +44,7 @@ Rosa::Application.routes.draw do member { get :projects get :key_pair + get :packages put :add_member delete :remove_member put :add_project diff --git a/spec/controllers/api/v1/repositories_controller_spec.rb b/spec/controllers/api/v1/repositories_controller_spec.rb index bdf88710b..bf14067a7 100644 --- a/spec/controllers/api/v1/repositories_controller_spec.rb +++ b/spec/controllers/api/v1/repositories_controller_spec.rb @@ -12,6 +12,20 @@ shared_examples_for 'api repository user with reader rights for hidden platform' it_should_behave_like 'api repository user with show rights' end +shared_examples_for 'api repository user without packages rights' do + it 'should not be able to perform packages action' do + get :packages, :id => @repository.id, :format => :csv + response.should_not be_success + end +end + +shared_examples_for 'api repository user with packages rights' do + it 'should be able to perform packages action' do + get :packages, :id => @repository.id, :format => :csv + response.should be_success + end +end + shared_examples_for 'api repository user without reader rights for hidden platform' do before(:each) do @platform.update_column(:visibility, 'hidden') @@ -309,6 +323,7 @@ describe Api::V1::RepositoriesController do it_should_behave_like 'api repository user without writer rights' it_should_behave_like 'api repository user without project manage rights' it_should_behave_like 'api repository user without key_pair rights' + it_should_behave_like 'api repository user without packages rights' it 'should not be able to perform projects action', :anonymous_access => false do get :projects, :id => @repository.id, :format => :json @@ -325,6 +340,7 @@ describe Api::V1::RepositoriesController do it_should_behave_like 'api repository user with reader rights' it_should_behave_like 'api repository user with reader rights for hidden platform' it_should_behave_like 'api repository user with writer rights' + it_should_behave_like 'api repository user with packages rights' it_should_behave_like 'api repository user without key_pair rights' end @@ -342,6 +358,7 @@ describe Api::V1::RepositoriesController do it_should_behave_like 'api repository user with reader rights' it_should_behave_like 'api repository user with reader rights for hidden platform' it_should_behave_like 'api repository user with writer rights' + it_should_behave_like 'api repository user with packages rights' it_should_behave_like 'api repository user without key_pair rights' end @@ -356,6 +373,7 @@ describe Api::V1::RepositoriesController do it_should_behave_like 'api repository user with show rights' it_should_behave_like 'api repository user without writer rights' it_should_behave_like 'api repository user without project manage rights' + it_should_behave_like 'api repository user without packages rights' it_should_behave_like 'api repository user without key_pair rights' end @@ -371,6 +389,7 @@ describe Api::V1::RepositoriesController do it_should_behave_like 'api repository user with show rights' it_should_behave_like 'api repository user with project manage rights' it_should_behave_like 'api repository user without writer rights' + it_should_behave_like 'api repository user without packages rights' it_should_behave_like 'api repository user without key_pair rights' end