Merge pull request #170 from travis-ci/rkh-v3-repos-by-owner
API v3: list repo by owner
This commit is contained in:
commit
40563b0864
|
@ -9,6 +9,11 @@ module Travis::API::V3
|
||||||
new
|
new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visible_repositories(list)
|
||||||
|
return [] unless unrestricted_api?
|
||||||
|
list.where(private: false)
|
||||||
|
end
|
||||||
|
|
||||||
def admin_for(repository)
|
def admin_for(repository)
|
||||||
raise LoginRequired
|
raise LoginRequired
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,6 +30,12 @@ module Travis::API::V3
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visible_repositories(list)
|
||||||
|
# naïve implementation, replaced with smart implementation in specific subclasses
|
||||||
|
return list if full_access?
|
||||||
|
list.select { |r| visible?(r) }
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def build_visible?(build)
|
def build_visible?(build)
|
||||||
|
|
|
@ -19,6 +19,10 @@ module Travis::API::V3
|
||||||
permission?(:admin, repository) ? user : super
|
permission?(:admin, repository) ? user : super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visible_repositories(list)
|
||||||
|
list.where('repositories.private = false OR repositories.id IN (?)'.freeze, permissions.map(&:repository_id))
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def repository_writable?(repository)
|
def repository_writable?(repository)
|
||||||
|
|
|
@ -27,6 +27,11 @@ module Travis::API::V3
|
||||||
polymorfic_foreign_types << (options[:foreign_type] || "#{field}_type") if options[:polymorphic]
|
polymorfic_foreign_types << (options[:foreign_type] || "#{field}_type") if options[:polymorphic]
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
return super unless caller_locations.first.base_label == 'add_constraints'.freeze
|
||||||
|
@constraint_name ||= super.sub("#{parent}::", ''.freeze)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
|
|
|
@ -6,16 +6,21 @@ module Travis::API::V3
|
||||||
all.joins(:users).where(users: user_condition(user))
|
all.joins(:users).where(users: user_condition(user))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def for_owner(owner)
|
||||||
|
filter(owner.repositories)
|
||||||
|
end
|
||||||
|
|
||||||
def all
|
def all
|
||||||
@all ||= begin
|
@all ||= filter(Models::Repository)
|
||||||
all = Models::Repository
|
end
|
||||||
all = all.where(active: bool(active)) unless active.nil?
|
|
||||||
all = all.where(private: bool(private)) unless private.nil?
|
def filter(list)
|
||||||
all = all.includes(:owner) if includes? 'repository.owner'.freeze
|
list = list.where(active: bool(active)) unless active.nil?
|
||||||
all = all.includes(:last_build) if includes? 'repository.last_build'.freeze
|
list = list.where(private: bool(private)) unless private.nil?
|
||||||
all = all.includes(:default_branch)
|
list = list.includes(:owner) if includes? 'repository.owner'.freeze
|
||||||
all
|
list = list.includes(:last_build) if includes? 'repository.last_build'.freeze
|
||||||
end
|
list = list.includes(:default_branch)
|
||||||
|
list
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,11 +42,12 @@ module Travis::API::V3
|
||||||
|
|
||||||
def render_value(value, **options)
|
def render_value(value, **options)
|
||||||
case value
|
case value
|
||||||
when Hash then value.map { |k, v| [k, render_value(v)] }.to_h
|
when Hash then value.map { |k, v| [k, render_value(v)] }.to_h
|
||||||
when Array then value.map { |v | render_value(v) }
|
when Array then value.map { |v | render_value(v) }
|
||||||
when *PRIMITIVE then value
|
when *PRIMITIVE then value
|
||||||
when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ')
|
when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
when Model then render_model(value, **options)
|
when Model then render_model(value, **options)
|
||||||
|
when ActiveRecord::Relation then render_value(value.to_a, **options)
|
||||||
else raise ArgumentError, 'cannot render %p (%p)' % [value.class, value]
|
else raise ArgumentError, 'cannot render %p (%p)' % [value.class, value]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,15 +34,16 @@ module Travis::API::V3
|
||||||
new(model, **options).render(representation)
|
new(model, **options).render(representation)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :model, :options, :script_name, :include, :included
|
attr_reader :model, :options, :script_name, :include, :included, :access_control
|
||||||
attr_writer :href
|
attr_writer :href
|
||||||
|
|
||||||
def initialize(model, script_name: nil, include: [], included: [], **options)
|
def initialize(model, script_name: nil, include: [], included: [], access_control: nil, **options)
|
||||||
@model = model
|
@model = model
|
||||||
@options = options
|
@options = options
|
||||||
@script_name = script_name
|
@script_name = script_name
|
||||||
@include = include
|
@include = include
|
||||||
@included = included
|
@included = included
|
||||||
|
@access_control = access_control
|
||||||
end
|
end
|
||||||
|
|
||||||
def href
|
def href
|
||||||
|
@ -69,7 +70,11 @@ module Travis::API::V3
|
||||||
nested_included = included + [model]
|
nested_included = included + [model]
|
||||||
modes = {}
|
modes = {}
|
||||||
|
|
||||||
excepted_type = result[:@type].to_s if include.any?
|
if include.any?
|
||||||
|
excepted_type = result[:@type].to_s
|
||||||
|
fields = fields.dup
|
||||||
|
end
|
||||||
|
|
||||||
include.each do |qualified_field|
|
include.each do |qualified_field|
|
||||||
raise WrongParams, 'illegal format for include parameter'.freeze unless /\A(?<prefix>\w+)\.(?<field>\w+)\Z$/ =~ qualified_field
|
raise WrongParams, 'illegal format for include parameter'.freeze unless /\A(?<prefix>\w+)\.(?<field>\w+)\Z$/ =~ qualified_field
|
||||||
next if prefix != excepted_type
|
next if prefix != excepted_type
|
||||||
|
|
|
@ -5,7 +5,19 @@ module Travis::API::V3
|
||||||
class Renderer::Owner < Renderer::ModelRenderer
|
class Renderer::Owner < Renderer::ModelRenderer
|
||||||
include Renderer::AvatarURL
|
include Renderer::AvatarURL
|
||||||
|
|
||||||
representation(:minimal, :id, :login)
|
representation(:minimal, :id, :login)
|
||||||
representation(:standard, :id, :login, :name, :github_id, :avatar_url)
|
representation(:standard, :id, :login, :name, :github_id, :avatar_url)
|
||||||
|
representation(:additional, :repositories)
|
||||||
|
|
||||||
|
def initialize(*)
|
||||||
|
super
|
||||||
|
|
||||||
|
owner_includes = include.select { |i| i.start_with?('owner.'.freeze) }
|
||||||
|
owner_includes.each { |i| include << i.sub('owner.'.freeze, "#{self.class.type}.") }
|
||||||
|
end
|
||||||
|
|
||||||
|
def repositories
|
||||||
|
access_control.visible_repositories(@model.repositories)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
module Travis::API::V3
|
module Travis::API::V3
|
||||||
class Result
|
class Result
|
||||||
attr_accessor :type, :resource, :status, :href
|
attr_accessor :access_control, :type, :resource, :status, :href
|
||||||
|
|
||||||
def initialize(type, resource = [], status: 200)
|
def initialize(access_control, type, resource = [], status: 200)
|
||||||
@type, @resource, @status = type, resource, status
|
@access_control, @type, @resource, @status = access_control, type, resource, status
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_to_missing?(method, *)
|
def respond_to_missing?(method, *)
|
||||||
|
@ -19,7 +19,7 @@ module Travis::API::V3
|
||||||
href = self.href
|
href = self.href
|
||||||
href = V3.location(env) if href.nil? and env['REQUEST_METHOD'.freeze] == 'GET'.freeze
|
href = V3.location(env) if href.nil? and env['REQUEST_METHOD'.freeze] == 'GET'.freeze
|
||||||
include = params['include'.freeze].to_s.split(?,.freeze)
|
include = params['include'.freeze].to_s.split(?,.freeze)
|
||||||
Renderer[type].render(resource, href: href, script_name: env['SCRIPT_NAME'.freeze], include: include)
|
Renderer[type].render(resource, href: href, script_name: env['SCRIPT_NAME'.freeze], include: include, access_control: access_control)
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(method, *args)
|
def method_missing(method, *args)
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Travis::API::V3
|
||||||
result = service.run
|
result = service.run
|
||||||
render(result, env_params, env)
|
render(result, env_params, env)
|
||||||
rescue Error => error
|
rescue Error => error
|
||||||
result = Result.new(:error, error)
|
result = Result.new(access_control, :error, error)
|
||||||
headers = error.status == 404 ? CASCADE : {}
|
headers = error.status == 404 ? CASCADE : {}
|
||||||
V3.response(result.render(env_params, env), headers, status: error.status)
|
V3.response(result.render(env_params, env), headers, status: error.status)
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@ module Travis::API::V3
|
||||||
resource :owner do
|
resource :owner do
|
||||||
route '/owner/({owner.login}|{user.login}|{organization.login})'
|
route '/owner/({owner.login}|{user.login}|{organization.login})'
|
||||||
get :find
|
get :find
|
||||||
|
get :repositories, '/repos'
|
||||||
end
|
end
|
||||||
|
|
||||||
resource :repository do
|
resource :repository do
|
||||||
|
|
|
@ -61,9 +61,13 @@ module Travis::API::V3
|
||||||
self.class.result_type
|
self.class.result_type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def result(*args)
|
||||||
|
Result.new(access_control, *args)
|
||||||
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
not_found unless result = run!
|
not_found unless result = run!
|
||||||
result = Result.new(result_type, result) unless result.is_a? Result
|
result = result(result_type, result) unless result.is_a? Result
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,7 +79,7 @@ module Travis::API::V3
|
||||||
|
|
||||||
def accepted(**payload)
|
def accepted(**payload)
|
||||||
payload[:resource_type] ||= result_type
|
payload[:resource_type] ||= result_type
|
||||||
Result.new(:accepted, payload, status: 202)
|
result(:accepted, payload, status: 202)
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_implemented
|
def not_implemented
|
||||||
|
|
10
lib/travis/api/v3/services/owner/repositories.rb
Normal file
10
lib/travis/api/v3/services/owner/repositories.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module Travis::API::V3
|
||||||
|
class Services::Owner::Repositories < Service
|
||||||
|
result_type :repositories
|
||||||
|
|
||||||
|
def run!
|
||||||
|
unfiltered = query(:repositories).for_owner(find(:owner))
|
||||||
|
access_control.visible_repositories(unfiltered)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,5 +13,6 @@ describe Travis::API::V3::Extensions::BelongsTo do
|
||||||
|
|
||||||
example { expect(repo.owner).to be_a(Travis::API::V3::Models::User) }
|
example { expect(repo.owner).to be_a(Travis::API::V3::Models::User) }
|
||||||
example { expect(::Repository.find(repo.id).owner).to be_a(::User) }
|
example { expect(::Repository.find(repo.id).owner).to be_a(::User) }
|
||||||
|
example { expect(user.repositories).to include(repo) }
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -1,11 +1,13 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Travis::API::V3::Result do
|
describe Travis::API::V3::Result do
|
||||||
subject(:result) { described_class.new(:example) }
|
let(:access_control) { Object.new }
|
||||||
|
subject(:result) { described_class.new(access_control, :example) }
|
||||||
|
|
||||||
example { expect(result.type) .to be == :example }
|
example { expect(result.type) .to be == :example }
|
||||||
example { expect(result.resource) .to be == [] }
|
example { expect(result.resource) .to be == [] }
|
||||||
example { expect(result.example) .to be == [] }
|
example { expect(result.example) .to be == [] }
|
||||||
|
example { expect(result.access_control) .to be == access_control }
|
||||||
|
|
||||||
example do
|
example do
|
||||||
result << 42
|
result << 42
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe Travis::API::V3::Services::Owner::Find do
|
||||||
after { org.delete }
|
after { org.delete }
|
||||||
|
|
||||||
describe 'existing org, public api' do
|
describe 'existing org, public api' do
|
||||||
before { get("/v3/owner/example-org") }
|
before { get("/v3/owner/example-org") }
|
||||||
example { expect(last_response).to be_ok }
|
example { expect(last_response).to be_ok }
|
||||||
example { expect(JSON.load(body)).to be == {
|
example { expect(JSON.load(body)).to be == {
|
||||||
"@type" => "organization",
|
"@type" => "organization",
|
||||||
|
@ -20,8 +20,58 @@ describe Travis::API::V3::Services::Owner::Find do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'eager loading repositories via organization.repositories' do
|
||||||
|
let(:repo) { Repository.new(name: 'example-repo', owner_name: 'example-org', owner_id: org.id, owner_type: 'Organization')}
|
||||||
|
|
||||||
|
before { repo.save! }
|
||||||
|
after { repo.destroy }
|
||||||
|
|
||||||
|
before { get("/v3/owner/example-org?include=organization.repositories,user.repositories") }
|
||||||
|
example { expect(last_response).to be_ok }
|
||||||
|
example { expect(JSON.load(body)).to be == {
|
||||||
|
"@type" => "organization",
|
||||||
|
"@href" => "/v3/org/#{org.id}",
|
||||||
|
"id" => org.id,
|
||||||
|
"login" => "example-org",
|
||||||
|
"name" => nil,
|
||||||
|
"github_id" => nil,
|
||||||
|
"avatar_url" => nil,
|
||||||
|
"repositories" => [{
|
||||||
|
"@type" => "repository",
|
||||||
|
"@href" => "/repo/#{repo.id}",
|
||||||
|
"id" => repo.id,
|
||||||
|
"slug" => "example-org/example-repo"
|
||||||
|
}]
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'eager loading repositories via owner.repositories' do
|
||||||
|
let(:repo) { Repository.new(name: 'example-repo', owner_name: 'example-org', owner_id: org.id, owner_type: 'Organization')}
|
||||||
|
|
||||||
|
before { repo.save! }
|
||||||
|
after { repo.destroy }
|
||||||
|
|
||||||
|
before { get("/v3/owner/example-org?include=owner.repositories") }
|
||||||
|
example { expect(last_response).to be_ok }
|
||||||
|
example { expect(JSON.load(body)).to be == {
|
||||||
|
"@type" => "organization",
|
||||||
|
"@href" => "/v3/org/#{org.id}",
|
||||||
|
"id" => org.id,
|
||||||
|
"login" => "example-org",
|
||||||
|
"name" => nil,
|
||||||
|
"github_id" => nil,
|
||||||
|
"avatar_url" => nil,
|
||||||
|
"repositories" => [{
|
||||||
|
"@type" => "repository",
|
||||||
|
"@href" => "/repo/#{repo.id}",
|
||||||
|
"id" => repo.id,
|
||||||
|
"slug" => "example-org/example-repo"
|
||||||
|
}]
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
describe 'it is not case sensitive' do
|
describe 'it is not case sensitive' do
|
||||||
before { get("/v3/owner/example-ORG") }
|
before { get("/v3/owner/example-ORG") }
|
||||||
example { expect(last_response).to be_ok }
|
example { expect(last_response).to be_ok }
|
||||||
example { expect(JSON.load(body)).to be == {
|
example { expect(JSON.load(body)).to be == {
|
||||||
"@type" => "organization",
|
"@type" => "organization",
|
||||||
|
|
69
spec/v3/services/owner/repositories_spec.rb
Normal file
69
spec/v3/services/owner/repositories_spec.rb
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Travis::API::V3::Services::Owner::Repositories do
|
||||||
|
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
|
||||||
|
|
||||||
|
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||||
|
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
|
||||||
|
before { Permission.create(repository: repo, user: repo.owner, pull: true) }
|
||||||
|
before { repo.update_attribute(:private, true) }
|
||||||
|
after { repo.update_attribute(:private, false) }
|
||||||
|
|
||||||
|
describe "private repository, private API, authenticated as user with access" do
|
||||||
|
before { get("/v3/owner/svenfuchs/repos", {}, headers) }
|
||||||
|
example { expect(last_response).to be_ok }
|
||||||
|
example { expect(JSON.load(body)).to be == {
|
||||||
|
"@type" => "repositories",
|
||||||
|
"@href" => "/v3/owner/svenfuchs/repos",
|
||||||
|
"repositories" => [{
|
||||||
|
"@type" => "repository",
|
||||||
|
"@href" => "/v3/repo/#{repo.id}",
|
||||||
|
"id" => repo.id,
|
||||||
|
"name" => "minimal",
|
||||||
|
"slug" => "svenfuchs/minimal",
|
||||||
|
"description" => nil,
|
||||||
|
"github_language" => nil,
|
||||||
|
"active" => true,
|
||||||
|
"private" => true,
|
||||||
|
"owner" => {
|
||||||
|
"@type" => "user",
|
||||||
|
"id" => repo.owner_id,
|
||||||
|
"login" => "svenfuchs" },
|
||||||
|
"last_build" => {
|
||||||
|
"@type" => "build",
|
||||||
|
"@href" => "/v3/build/#{repo.last_build_id}",
|
||||||
|
"id" => repo.last_build_id,
|
||||||
|
"number" => "2",
|
||||||
|
"state" => "passed",
|
||||||
|
"duration" => nil,
|
||||||
|
"started_at" => "2010-11-12T12:30:00Z",
|
||||||
|
"finished_at" => "2010-11-12T12:30:20Z"},
|
||||||
|
"default_branch" => {
|
||||||
|
"@type" => "branch",
|
||||||
|
"@href" => "/v3/repo/#{repo.id}/branch/master",
|
||||||
|
"name" => "master",
|
||||||
|
"last_build" => {
|
||||||
|
"@type" => "build",
|
||||||
|
"@href" => "/v3/build/#{repo.last_build.id}",
|
||||||
|
"id" => repo.last_build.id,
|
||||||
|
"number" => "3",
|
||||||
|
"state" => "configured",
|
||||||
|
"duration" => nil,
|
||||||
|
"started_at" => "2010-11-12T13:00:00Z",
|
||||||
|
"finished_at" => nil}}}]
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "filter: private=false" do
|
||||||
|
before { get("/v3/repos", {"repository.private" => "false"}, headers) }
|
||||||
|
example { expect(last_response) .to be_ok }
|
||||||
|
example { expect(JSON.load(body)['repositories']) .to be == [] }
|
||||||
|
example { expect(JSON.load(body)['@href']) .to be == "/v3/repos?repository.private=false" }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "filter: active=false" do
|
||||||
|
before { get("/v3/repos", {"repository.active" => "false"}, headers) }
|
||||||
|
example { expect(last_response) .to be_ok }
|
||||||
|
example { expect(JSON.load(body)['repositories']) .to be == [] }
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user