universal-git-client 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ require 'universal-git-client/http/base'
2
+
3
+ module UniversalGitClient
4
+ module Http
5
+ class Github < Base
6
+ def user
7
+ with_response_validation! do
8
+ self.class.get('/user', default_options)
9
+ end
10
+ end
11
+
12
+ def organizations(page: 1, per_page: nil)
13
+ with_response_validation! do
14
+ self.class.get(
15
+ '/user/orgs',
16
+ default_options.merge(
17
+ query: {
18
+ page: page,
19
+ per_page: per_page || default_elements_per_page,
20
+ },
21
+ )
22
+ )
23
+ end
24
+ end
25
+
26
+ def user_repos(page: 1, per_page: nil)
27
+ with_response_validation! do
28
+ self.class.get(
29
+ '/user/repos',
30
+ default_options.merge(
31
+ query: {
32
+ affiliation: 'owner',
33
+ page: page,
34
+ per_page: per_page || default_elements_per_page,
35
+ },
36
+ )
37
+ )
38
+ end
39
+ end
40
+
41
+ def orga_repos(organization:, page: 1, per_page: nil)
42
+ # TODO: Handle permissions
43
+ with_response_validation! do
44
+ self.class.get(
45
+ "/orgs/#{organization}/repos",
46
+ default_options.merge(
47
+ query: {
48
+ page: page,
49
+ per_page: per_page || default_elements_per_page,
50
+ },
51
+ )
52
+ )
53
+ end
54
+ end
55
+
56
+ def repository(owner:, repo:)
57
+ with_response_validation! do
58
+ self.class.get("/repos/#{owner}/#{repo}", default_options)
59
+ end
60
+ end
61
+
62
+ def branches(owner:, repo:, page: 1, per_page: nil)
63
+ with_response_validation! do
64
+ self.class.get(
65
+ "/repos/#{owner}/#{repo}/branches",
66
+ default_options.merge(
67
+ query: {
68
+ page: page,
69
+ per_page: per_page || default_elements_per_page,
70
+ },
71
+ )
72
+ )
73
+ end
74
+ end
75
+
76
+ def branch(owner:, repo:, branch:)
77
+ with_response_validation! do
78
+ self.class.get(
79
+ "/repos/#{owner}/#{repo}/branches/#{branch}",
80
+ default_options
81
+ )
82
+ end
83
+ end
84
+
85
+ def download_repo_archive(owner:, repo:, branch: nil)
86
+ path = "#{base_url}/repos/#{owner}/#{repo}/zipball"
87
+ path << "/#{branch}" if branch
88
+ Down.download(path, down_default_options)
89
+ end
90
+
91
+ def setup_repo_webhook(owner:, repo:, webhook_url:, webhook_secret: nil)
92
+ with_response_validation! do
93
+ self.class.post(
94
+ "/repos/#{owner}/#{repo}/hooks",
95
+ default_options.merge(
96
+ body: {
97
+ name: 'web',
98
+ active: true,
99
+ events: [
100
+ 'push',
101
+ ],
102
+ config: {
103
+ url: webhook_url,
104
+ secret: webhook_secret,
105
+ content_type: 'json',
106
+ }.compact,
107
+ }.to_json,
108
+ )
109
+ )
110
+ end
111
+ end
112
+
113
+ def delete_repo_webhook(owner:, repo:, webhook_id:)
114
+ with_response_validation! do
115
+ self.class.delete(
116
+ "/repos/#{owner}/#{repo}/hooks/#{webhook_id}",
117
+ default_options
118
+ )
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,130 @@
1
+ require 'universal-git-client/http/base'
2
+ require 'uri'
3
+
4
+ module UniversalGitClient
5
+ module Http
6
+ class Gitlab < Base
7
+ def user
8
+ with_response_validation! do
9
+ self.class.get('/user', default_options)
10
+ end
11
+ end
12
+
13
+ def organizations(page: 1, per_page: nil)
14
+ with_response_validation! do
15
+ self.class.get(
16
+ '/groups',
17
+ default_options.merge(
18
+ query: {
19
+ page: page,
20
+ per_page: per_page || default_elements_per_page,
21
+ },
22
+ )
23
+ )
24
+ end
25
+ end
26
+
27
+ def user_repos(page: 1, per_page: nil)
28
+ current_user_id = self.user['id']
29
+ with_response_validation! do
30
+ self.class.get(
31
+ "/users/#{current_user_id}/projects",
32
+ default_options.merge(
33
+ query: {
34
+ page: page,
35
+ per_page: per_page || default_elements_per_page,
36
+ },
37
+ )
38
+ )
39
+ end
40
+ end
41
+
42
+ def orga_repos(organization:, page: 1, per_page: nil)
43
+ encoded_namespace = URI.encode_www_form_component(organization)
44
+ with_response_validation! do
45
+ self.class.get(
46
+ "/groups/#{encoded_namespace}/projects",
47
+ default_options.merge(
48
+ query: {
49
+ page: page,
50
+ per_page: per_page || default_elements_per_page,
51
+ },
52
+ )
53
+ )
54
+ end
55
+ end
56
+
57
+ def repository(owner:, repo:)
58
+ encoded_namespace = encode_namespace(owner, repo)
59
+ with_response_validation! do
60
+ self.class.get("/projects/#{encoded_namespace}", default_options)
61
+ end
62
+ end
63
+
64
+ def branches(owner:, repo:, page: 1, per_page: nil)
65
+ encoded_namespace = encode_namespace(owner, repo)
66
+ with_response_validation! do
67
+ self.class.get(
68
+ "/projects/#{encoded_namespace}/repository/branches",
69
+ default_options.merge(
70
+ query: {
71
+ page: page,
72
+ per_page: per_page || default_elements_per_page,
73
+ },
74
+ )
75
+ )
76
+ end
77
+ end
78
+
79
+ def branch(owner:, repo:, branch:)
80
+ encoded_namespace = encode_namespace(owner, repo)
81
+ with_response_validation! do
82
+ self.class.get(
83
+ "/projects/#{encoded_namespace}/repository/branches/#{branch}",
84
+ default_options
85
+ )
86
+ end
87
+ end
88
+
89
+ def download_repo_archive(owner:, repo:, branch: nil)
90
+ encoded_namespace = encode_namespace(owner, repo)
91
+ path = "#{base_url}/projects/#{encoded_namespace}/repository/archive.zip"
92
+ path << "?sha=#{branch}" if branch
93
+ Down.download(path, down_default_options)
94
+ end
95
+
96
+ def setup_repo_webhook(owner:, repo:, webhook_url:, webhook_secret: nil)
97
+ encoded_namespace = encode_namespace(owner, repo)
98
+ with_response_validation! do
99
+ self.class.post(
100
+ "/projects/#{encoded_namespace}/hooks",
101
+ default_options.merge(
102
+ body: {
103
+ url: webhook_url,
104
+ token: webhook_secret,
105
+ push_events: true,
106
+ enable_ssl_verification: true,
107
+ }.compact.to_json,
108
+ )
109
+ )
110
+ end
111
+ end
112
+
113
+ def delete_repo_webhook(owner:, repo:, webhook_id:)
114
+ encoded_namespace = encode_namespace(owner, repo)
115
+ with_response_validation! do
116
+ self.class.delete(
117
+ "/projects/#{encoded_namespace}/hooks/#{webhook_id}",
118
+ default_options
119
+ )
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def encode_namespace(owner, repo)
126
+ URI.encode_www_form_component("#{owner}/#{repo}")
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,14 @@
1
+ module UniversalGitClient
2
+ module Http
3
+ module Helpers
4
+ class DummyResponse
5
+ def self.response(request: nil, response: nil, body: {}, options: {})
6
+ request ||= HTTParty::Request.new(Net::HTTP::Get, '/', options)
7
+ response ||= Net::HTTPSuccess.new('1.1', 200, 'Success')
8
+
9
+ HTTParty::Response.new(request, response, -> { '' }, body: body.to_json)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'universal-git-client/errors'
2
+
3
+ module UniversalGitClient
4
+ module Http
5
+ module ResponseValidation
6
+ def with_response_validation!
7
+ response = yield
8
+ case response.code
9
+ when 200..399 then response
10
+ when 400 then raise Errors::BadRequest.new('BadRequest', response)
11
+ when 401 then raise Errors::Unauthorized.new('Unauthorized', response)
12
+ when 403 then raise Errors::Forbidden.new('Forbidden', response)
13
+ when 404 then raise Errors::NotFound.new('NotFound', response)
14
+ when 405 then raise Errors::MethodNotAllowed.new('MethodNotAllowed', response)
15
+ when 406 then raise Errors::NotAcceptable.new('NotAcceptable', response)
16
+ when 409 then raise Errors::Conflict.new('Conflict', response)
17
+ when 415 then raise Errors::UnsupportedMediaType.new('UnsupportedMediaType', response)
18
+ when 422 then raise Errors::UnprocessableEntity.new('UnprocessableEntity', response)
19
+ when 400..499 then raise Errors::ClientError.new('ClientError', response)
20
+ when 500 then raise Errors::InternalServerError.new('InternalServerError', response)
21
+ when 502 then raise Errors::BadGateway.new('BadGateway', response)
22
+ when 503 then raise Errors::ServiceUnavailable.new('ServiceUnavailable', response)
23
+ when 500..599 then raise Errors::ServerError.new('ServerError', response)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,103 @@
1
+ require 'json'
2
+ require 'fast_jsonapi'
3
+ require 'deep_merge'
4
+ require 'uri'
5
+ require 'nitlink'
6
+
7
+ module UniversalGitClient
8
+ module Normalizers
9
+ class Base
10
+ attr_reader :response, :resource
11
+
12
+ def initialize(response, resource)
13
+ @response = response
14
+ @resource = resource
15
+ end
16
+
17
+ def normalize
18
+ object = body_as_object
19
+ serializer = serializer_for_resource
20
+ serializer_options = {}
21
+ if object.is_a?(Array)
22
+ serializer_options.deep_merge!(pagination_options)
23
+ end
24
+ serializer.new(object, serializer_options)
25
+ end
26
+
27
+ def serializer_for_resource
28
+ self.class.const_get("#{resource}Serializer")
29
+ end
30
+
31
+ def body_as_object
32
+ @body_as_object ||= begin
33
+ struct = JSON.parse(response.body, object_class: OpenStruct)
34
+ if !struct.is_a?(Array) && struct.values
35
+ # Bitbucket embeds collections inside values
36
+ struct.values
37
+ else
38
+ struct
39
+ end
40
+ end
41
+ end
42
+
43
+ def pagination_options
44
+ {
45
+ meta: {
46
+ pagination: {
47
+ self: current_page_index,
48
+ first: first_page_index,
49
+ prev: prev_page_index,
50
+ next: next_page_index,
51
+ last: last_page_index,
52
+ per_page: per_page_index,
53
+ },
54
+ },
55
+ }
56
+ end
57
+
58
+ def current_page_index
59
+ response.request.options[:query][:page].to_s
60
+ rescue StandardError
61
+ nil
62
+ end
63
+
64
+ def first_page_index
65
+ fetch_rel_value('first')
66
+ end
67
+
68
+ def prev_page_index
69
+ fetch_rel_value('prev')
70
+ end
71
+
72
+ def next_page_index
73
+ fetch_rel_value('next')
74
+ end
75
+
76
+ def last_page_index
77
+ fetch_rel_value('last')
78
+ end
79
+
80
+ def per_page_index
81
+ response.request.options[:query][:per_page].to_s
82
+ rescue StandardError
83
+ nil
84
+ end
85
+
86
+ private
87
+
88
+ def fetch_rel_value(rel)
89
+ Hash[URI::decode_www_form(links.by_rel(rel).target.query)]['page']
90
+ rescue StandardError
91
+ nil
92
+ end
93
+
94
+ def links
95
+ @links ||= Nitlink::Parser.new.parse(response)
96
+ end
97
+ end
98
+
99
+ class BaseSerializer
100
+ include FastJsonapi::ObjectSerializer
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,93 @@
1
+ require 'universal-git-client/normalizers/base'
2
+
3
+ module UniversalGitClient
4
+ module Normalizers
5
+ class Bitbucket < Base
6
+ def first_page_index
7
+ '1'
8
+ end
9
+
10
+ def prev_page_index
11
+ get_page_index('previous')
12
+ end
13
+
14
+ def next_page_index
15
+ get_page_index('next')
16
+ end
17
+
18
+ def last_page_index
19
+ nil
20
+ end
21
+
22
+ def per_page_index
23
+ response.request.options[:query][:pagelen].to_s
24
+ rescue StandardError
25
+ nil
26
+ end
27
+
28
+ private
29
+
30
+ def get_page_index(rel)
31
+ Hash[
32
+ URI::decode_www_form(
33
+ URI.parse(response[rel]).query
34
+ )
35
+ ]['page']
36
+ rescue StandardError
37
+ nil
38
+ end
39
+
40
+ class UserSerializer < BaseSerializer
41
+ set_id :account_id
42
+ attribute :login, &:username
43
+ attribute :name, &:display_name
44
+
45
+ attribute :avatar_url do |object|
46
+ object.links.avatar.href
47
+ end
48
+ end
49
+
50
+ class OrganizationSerializer < BaseSerializer
51
+ set_id :uuid
52
+ attribute :login, &:username
53
+
54
+ attribute :avatar_url do |object|
55
+ object.links.avatar.href
56
+ end
57
+ end
58
+
59
+ class RepositorySerializer < BaseSerializer
60
+ set_id :uuid
61
+ attributes :name, :full_name
62
+ attribute :full_path, &:full_name
63
+ attribute :private, &:is_private
64
+ attribute :archived, &:is_archived
65
+
66
+ attribute :default_branch do |object|
67
+ object.mainbranch&.name
68
+ end
69
+ end
70
+
71
+ class BranchSerializer < BaseSerializer
72
+ set_id :name
73
+ attributes :name, :protected
74
+
75
+ attribute :commit do |object|
76
+ object.target.to_h[:hash]
77
+ end
78
+ end
79
+
80
+ class WebhookSerializer < BaseSerializer
81
+ set_id :uuid
82
+ attributes :url, :active
83
+ attribute :events do |object|
84
+ events = []
85
+ events << 'push' if object.events.include?('repo:push')
86
+ end
87
+ attribute :type do |object|
88
+ object.subject.type.capitalize
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end