bitly 1.1.2 → 2.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +36 -3
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -2
  7. data/History.txt +18 -1
  8. data/LICENSE.md +1 -1
  9. data/README.md +151 -58
  10. data/Rakefile +4 -9
  11. data/bin/console +14 -0
  12. data/bin/oauth +10 -0
  13. data/bin/setup +8 -0
  14. data/bitly.gemspec +24 -29
  15. data/config/env.yml.example +5 -0
  16. data/docs/authentication.md +51 -0
  17. data/docs/bitlinks.md +2 -0
  18. data/docs/branded_short_domains.md +20 -0
  19. data/docs/groups.md +80 -0
  20. data/docs/oauth_apps.md +20 -0
  21. data/docs/organizations.md +74 -0
  22. data/docs/premium_apis.md +0 -0
  23. data/docs/users.md +1 -0
  24. data/lib/bitly.rb +6 -7
  25. data/lib/bitly/api.rb +16 -0
  26. data/lib/bitly/api/base.rb +22 -0
  27. data/lib/bitly/api/bitlink.rb +341 -0
  28. data/lib/bitly/api/bitlink/clicks_summary.rb +35 -0
  29. data/lib/bitly/api/bitlink/deeplink.rb +29 -0
  30. data/lib/bitly/api/bitlink/link_click.rb +74 -0
  31. data/lib/bitly/api/bitlink/paginated_list.rb +51 -0
  32. data/lib/bitly/api/bsd.rb +24 -0
  33. data/lib/bitly/api/click_metric.rb +185 -0
  34. data/lib/bitly/api/client.rb +588 -0
  35. data/lib/bitly/api/group.rb +232 -0
  36. data/lib/bitly/api/group/preferences.rb +73 -0
  37. data/lib/bitly/api/list.rb +22 -0
  38. data/lib/bitly/api/oauth_app.rb +26 -0
  39. data/lib/bitly/api/organization.rb +104 -0
  40. data/lib/bitly/api/shorten_counts.rb +61 -0
  41. data/lib/bitly/api/user.rb +107 -0
  42. data/lib/bitly/error.rb +33 -0
  43. data/lib/bitly/http.rb +6 -0
  44. data/lib/bitly/http/adapters/net_http.rb +27 -0
  45. data/lib/bitly/http/client.rb +33 -0
  46. data/lib/bitly/http/request.rb +116 -0
  47. data/lib/bitly/http/response.rb +65 -0
  48. data/lib/bitly/oauth.rb +109 -0
  49. data/lib/bitly/version.rb +3 -1
  50. metadata +88 -108
  51. data/Manifest +0 -37
  52. data/lib/bitly/url.rb +0 -103
  53. data/lib/bitly/utils.rb +0 -57
  54. data/lib/bitly/v3.rb +0 -14
  55. data/lib/bitly/v3/bitly.rb +0 -7
  56. data/lib/bitly/v3/country.rb +0 -13
  57. data/lib/bitly/v3/day.rb +0 -13
  58. data/lib/bitly/v3/missing_url.rb +0 -15
  59. data/lib/bitly/v3/oauth.rb +0 -41
  60. data/lib/bitly/v3/realtime_link.rb +0 -18
  61. data/lib/bitly/v3/referrer.rb +0 -13
  62. data/lib/bitly/v3/url.rb +0 -154
  63. data/test/bitly/test_client.rb +0 -266
  64. data/test/bitly/test_config.rb +0 -28
  65. data/test/bitly/test_url.rb +0 -167
  66. data/test/bitly/test_utils.rb +0 -79
  67. data/test/fixtures/cnn.json +0 -1
  68. data/test/fixtures/cnn_and_google.json +0 -1
  69. data/test/fixtures/expand_cnn.json +0 -1
  70. data/test/fixtures/expand_cnn_and_google.json +0 -1
  71. data/test/fixtures/google_and_cnn_info.json +0 -1
  72. data/test/fixtures/google_info.json +0 -1
  73. data/test/fixtures/google_stats.json +0 -1
  74. data/test/fixtures/shorten_error.json +0 -1
  75. data/test/test_helper.rb +0 -39
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./base"
3
+
4
+ module Bitly
5
+ module API
6
+ class ShortenCounts
7
+ include Base
8
+
9
+ def self.attributes
10
+ [:units, :facet, :unit_reference, :unit]
11
+ end
12
+
13
+ attr_reader(*attributes)
14
+ attr_reader :metrics
15
+
16
+ Metric = Struct.new(:key, :value)
17
+
18
+ ##
19
+ # Shorten counts by group
20
+ # [`GET /v4/groups/{group_guid}/shorten_counts`](https://dev.bitly.com/v4/#operation/getGroupShortenCounts)
21
+ #
22
+ # @example
23
+ # shorten_counts = Bitly::API::ShortenCounts.by_group(client: client, group_guid: group_guid)
24
+ #
25
+ # @param client [Bitly::API::Client] An authorized API client
26
+ # @param group_guid [String] The guid of the group for which you want
27
+ # shorten counts
28
+ #
29
+ # @return [Bitly::API::ShortenCounts]
30
+ def self.by_group(client:, group_guid:)
31
+ response = client.request(path: "/groups/#{group_guid}/shorten_counts")
32
+ new(data: response.body, response: response)
33
+ end
34
+
35
+ ##
36
+ # Shorten counts by organization
37
+ # [`GET /v4/organizations/{organization_guid}/shorten_counts`](https://dev.bitly.com/v4/#operation/getOrganizationShortenCounts)
38
+ #
39
+ # @example
40
+ # shorten_counts = Bitly::API::ShortenCounts.by_organization(client: client, organization_guid: organization_guid)
41
+ #
42
+ # @param client [Bitly::API::Client] An authorized API client
43
+ # @param organization_guid [String] The guid of the organization for which
44
+ # you want shorten counts
45
+ #
46
+ # @return [Bitly::API::ShortenCounts]
47
+ def self.by_organization(client:, organization_guid:)
48
+ response = client.request(path: "/organizations/#{organization_guid}/shorten_counts")
49
+ new(data: response.body, response: response)
50
+ end
51
+
52
+ def initialize(data:, response: nil)
53
+ assign_attributes(data)
54
+ @metrics = data["metrics"].map do |metric|
55
+ Metric.new(metric["key"], metric["value"])
56
+ end
57
+ @response = response
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+ require_relative "./base"
3
+
4
+ module Bitly
5
+ module API
6
+ ##
7
+ # A User represents the authorized user
8
+ class User
9
+ class Email
10
+ attr_reader :email, :is_verified, :is_primary
11
+ def initialize(data)
12
+ @email = data["email"]
13
+ @is_verified = data["is_verified"]
14
+ @is_primary = data["is_primary"]
15
+ end
16
+ end
17
+
18
+ include Base
19
+ ##
20
+ # Gets the authorized user from the API.
21
+ # [`GET /v4/user`](https://dev.bitly.com/v4/#operation/getUser)
22
+ #
23
+ # @example
24
+ # user = Bitly::API::User.fetch(client: client)
25
+ #
26
+ # @param client [Bitly::API::Client] The authorized API client
27
+ #
28
+ # @return [Bitly::API::User]
29
+ def self.fetch(client:)
30
+ response = client.request(path: "/user")
31
+ new(data: response.body, client: client, response: response)
32
+ end
33
+
34
+ # @return [Array<Symbol>] The attributes the API returns for a user
35
+ def self.attributes
36
+ [:login, :is_active, :is_2fa_enabled, :name, :is_sso_user, :default_group_guid]
37
+ end
38
+ # @return [Array<Symbol>] The attributes the API returns that need to be
39
+ # converted to `Time` objects.
40
+ def self.time_attributes
41
+ [:created, :modified]
42
+ end
43
+
44
+ attr_reader(*(attributes + time_attributes))
45
+ attr_reader :emails
46
+
47
+ ##
48
+ # Creates a Bitly::API::User object.
49
+ #
50
+ # @example
51
+ # user = Bitly::API::User.new(data: user_data, client: client)
52
+ #
53
+ # @param data [Hash<String, String | Boolean>] The user data from the API
54
+ # @param client [Bitly::API::Client] The authorized API client
55
+ # @param response [Bitly::HTTP::Response] The original HTTP response
56
+ #
57
+ # @return [Bitly::API::User]
58
+ def initialize(data:, client:, response: nil)
59
+ assign_attributes(data)
60
+ @client = client
61
+ @response = response
62
+ if data["emails"]
63
+ @emails = data["emails"].map { |e| Email.new(e) }
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Returns the default group for the user from the default group guid
69
+ #
70
+ # @example
71
+ # user.default_group
72
+ #
73
+ # @returns [Bitly::API::Group]
74
+ def default_group
75
+ @default_group ||= Group.fetch(client: @client, guid: default_group_guid)
76
+ end
77
+
78
+ ##
79
+ # Allows you to update the authorized user's name or default group guid.
80
+ # If you update the default group ID and have already loaded the default
81
+ # group, it is nilled out so it can be reloaded with the correct ID.
82
+ # [`PATCH /v4/user`](https://dev.bitly.com/v4/#operation/updateUser)]
83
+ #
84
+ # @example
85
+ # user.update(name: "New Name", default_group_guid: "aaabbb")
86
+ #
87
+ # @param name [String] A new name
88
+ # @param default_group_guid [String] A new default guid
89
+ #
90
+ # @return [Bitly::API::User]
91
+ def update(name: nil, default_group_guid: nil)
92
+ params = { "name" => name }
93
+ if default_group_guid
94
+ params["default_group_guid"] = default_group_guid
95
+ @default_group = nil
96
+ end
97
+ @response = @client.request(
98
+ path: "/user",
99
+ method: "PATCH",
100
+ params: params
101
+ )
102
+ assign_attributes(@response.body)
103
+ self
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitly
4
+ ##
5
+ # An error class that covers all potential errors from the Bitly API. In an
6
+ # error scenario, the API is only guaranteed to return a status_code and
7
+ # status_txt: https://dev.bitly.com/formats.html
8
+ class Error < StandardError
9
+ ##
10
+ # @return [String] The status code of the failed request
11
+ attr_reader :status_code
12
+
13
+ ##
14
+ # @return [String] The description of the failed request
15
+ attr_reader :description
16
+
17
+ ##
18
+ # @return [Bitly::HTTP::Response] The response that caused the error
19
+ attr_reader :response
20
+
21
+ ##
22
+ # Creates a new Bitly::Error object
23
+ #
24
+ # @param [Bitly::HTTP::Response] response The parsed response to the HTTP request
25
+ def initialize(response)
26
+ @response = response
27
+ @status_code = response.status
28
+ @description = response.body["description"]
29
+ @message = "[#{@status_code}] #{response.body["message"]}"
30
+ super(@message)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./http/adapters/net_http"
4
+ require_relative "./http/response"
5
+ require_relative "./http/request"
6
+ require_relative "./http/client"
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+
5
+ module Bitly
6
+ module HTTP
7
+ module Adapters
8
+ class NetHTTP
9
+ def request(request)
10
+ Net::HTTP.start(request.uri.host, request.uri.port, use_ssl: true) do |http|
11
+ method = Object.const_get("Net::HTTP::#{request.method.capitalize}")
12
+ full_path = request.uri.path
13
+ full_path += "?#{request.uri.query}" if request.uri.query
14
+ http_request = method.new full_path
15
+ http_request.body = request.body
16
+ request.headers.each do |header, value|
17
+ http_request[header] = value
18
+ end
19
+ response = http.request http_request
20
+ success = response.kind_of? Net::HTTPSuccess
21
+ return [response.code, response.body, response.to_hash, success]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitly
4
+ module HTTP
5
+ class Client
6
+ def initialize(adapter=Bitly::HTTP::Adapters::NetHTTP.new)
7
+ @adapter = adapter
8
+ raise ArgumentError, "Adapter must have a request method." unless @adapter.respond_to?(:request)
9
+ end
10
+
11
+ ##
12
+ # The main method for the HTTP client. It receives a Bitly::HTTP::Request
13
+ # object, makes the request described and returns a Bitly::HTTP::Response.
14
+ #
15
+ # @param [Bitly::HTTP::Request] request The request that should be made
16
+ #
17
+ # @return [Bitly::HTTP::Response] The response from the request.
18
+ #
19
+ # @raise [Bitly::Error] If the response is not a successful response
20
+ # in the 2xx range, then we raise an error with the response passed as
21
+ # an argument. It is up to the application to catch this error.
22
+ def request(request)
23
+ status, body, headers, success = @adapter.request(request)
24
+ response = Bitly::HTTP::Response.new(status: status, body: body, headers: headers, request: request)
25
+ if success
26
+ return response
27
+ else
28
+ raise Bitly::Error, response
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitly
4
+ module HTTP
5
+ class Request
6
+ # @return [String] The HTTP method that the request should be.
7
+ attr_reader :method
8
+
9
+ # @return [Hash] A hash of parameters that will be turned into query
10
+ # parameters or a request body
11
+ attr_reader :params
12
+
13
+ # @return [Hash] A hash of HTTP headers that will be included with the
14
+ # request
15
+ attr_reader :headers
16
+
17
+ ##
18
+ # Creates a new Bitly::HTTP::Request object, which is to be used by the
19
+ # [Bitly::HTTP::Client].
20
+ #
21
+ # @example
22
+ # request = Bitly::HTTP::Request.new(uri: URI.parse('https://api-ssl.bitly.com/v3/shorten'), method: "GET")
23
+ #
24
+ # @param [URI] uri A [URI] that you want to make the request to.
25
+ # @param [String] method The HTTP method that should be used to make the
26
+ # request.
27
+ # @param [Hash] params The parameters to be sent as part of the request.
28
+ # GET parameters will be sent as part of the query string and other
29
+ # methods will be added to the request body.
30
+ def initialize(uri: , method: "GET", params: {}, headers: {})
31
+ errors = []
32
+ @uri = uri
33
+ errors << "uri must be an object of type URI. Received a #{uri.class}" unless uri.kind_of?(URI)
34
+ @method = method
35
+ errors << "method must be a valid HTTP method. Received: #{method}." unless HTTP_METHODS.include?(method)
36
+ @params = params
37
+ errors << "params must be a hash. Received: #{params.inspect}." unless params.kind_of?(Hash)
38
+ @headers = headers
39
+ errors << "headers must be a hash. Received: #{headers.inspect}." unless headers.kind_of?(Hash)
40
+ raise ArgumentError, errors.join("\n") if errors.any?
41
+ end
42
+
43
+ ##
44
+ # Returns the uri for the request. If the request is an HTTP method that
45
+ # uses a body to send data, then the uri is the one that the request was
46
+ # initialised with. If the request uses query parameters, then the
47
+ # parameters are serialised and added to the uri's query.
48
+ #
49
+ # @example
50
+ # uri = URI.parse("https://api-ssl.bitly.com/v3/shorten")
51
+ # request = Bitly::HTTP::Request.new(uri: uri, params: { foo: "bar" })
52
+ # request.uri.to_s
53
+ # # => "https://api-ssl.bitly.com/v3/shorten?foo=bar"
54
+ #
55
+ # @return [URI] The full URI for the request
56
+ def uri
57
+ uri = @uri.dup
58
+ return uri if HTTP_METHODS_WITH_BODY.include?(@method)
59
+ if uri.query
60
+ existing_query = URI.decode_www_form(uri.query)
61
+ new_query = hash_to_arrays(@params)
62
+ uri.query = URI.encode_www_form((existing_query + new_query).uniq)
63
+ else
64
+ uri.query = URI.encode_www_form(@params) if @params.any?
65
+ end
66
+ uri
67
+ end
68
+
69
+ ##
70
+ # Returns the body of the request if the request is an HTTP method that
71
+ # uses a body to send data. The body is a JSON string of the parameters.
72
+ # If the request doesn't use a body to send data, this returns nil.
73
+ #
74
+ # @example
75
+ # uri = URI.parse("https://api-ssl.bitly.com/v3/shorten")
76
+ # request = Bitly::HTTP::Request.new(uri: uri, method: 'POST', params: { foo: "bar" })
77
+ # request.body
78
+ # # => "{\"foo\":\"bar\"}"
79
+ #
80
+ # @return [String] The request body
81
+ def body
82
+ return nil if HTTP_METHODS_WITHOUT_BODY.include?(@method)
83
+ return JSON.generate(params)
84
+ end
85
+
86
+ private
87
+
88
+ def hash_to_arrays(hash)
89
+ hash.map do |key, value|
90
+ if value.is_a?(Array)
91
+ value.map { |v| [key, v] }
92
+ else
93
+ [[key, value]]
94
+ end
95
+ end.flatten(1)
96
+ end
97
+
98
+ HTTP_METHODS_WITHOUT_BODY = [
99
+ "GET",
100
+ "HEAD",
101
+ "DELETE",
102
+ "TRACE",
103
+ "OPTIONS"
104
+ ]
105
+
106
+ HTTP_METHODS_WITH_BODY = [
107
+ "POST",
108
+ "PUT",
109
+ "PATCH",
110
+ "CONNECT"
111
+ ]
112
+
113
+ HTTP_METHODS = HTTP_METHODS_WITH_BODY + HTTP_METHODS_WITHOUT_BODY
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitly
4
+ module HTTP
5
+ ##
6
+ # The Response class handles generic responses from the API. It is made up
7
+ # of a status code, body and headers. The body is expected to be JSON and it
8
+ # will parse the body. The status should lie within the range 100 - 599 and
9
+ # the headers should be a hash.
10
+ class Response
11
+
12
+ # @return [String] The response's status code
13
+ attr_reader :status
14
+
15
+ # @return [Hash] The response's parsed body
16
+ attr_reader :body
17
+
18
+ # @return [Hash] The response's headers
19
+ attr_reader :headers
20
+
21
+ # @return [Bitly::HTTP::Request] The request that caused this response
22
+ attr_reader :request
23
+
24
+ ##
25
+ # Creates a new Bitly::HTTP::Response object, which can be used by other
26
+ # objects in the library.
27
+ #
28
+ # @example
29
+ # response = Bitly::HTTP::Response.new(status: "200", body: "{}", headers: {})
30
+ #
31
+ # @param [String] status The status code of the response, which should be
32
+ # between 100 and 599
33
+ # @param [String] body The body of the response, a String that is valid
34
+ # JSON and will be parsed
35
+ # @param [Hash] headers The response headers
36
+ def initialize(status:, body:, headers:, request: nil)
37
+ errors = []
38
+ @status = status
39
+ errors << "Status must be a valid HTTP status code. Received #{status}" unless is_status?(status)
40
+ if body.nil? || body.empty?
41
+ @body = nil
42
+ else
43
+ begin
44
+ @body = JSON.parse(body)
45
+ rescue JSON::ParserError
46
+ @body = {
47
+ "message" => body
48
+ }
49
+ end
50
+ end
51
+ @headers = headers
52
+ errors << "Headers must be a hash. Received #{headers}" unless headers.is_a?(Hash)
53
+ @request = request
54
+ errors << "Request must be a Bitly::HTTP::Request. Received #{request}" if request && !request.is_a?(Request)
55
+ raise ArgumentError, errors.join("\n"), caller if errors.any?
56
+ end
57
+
58
+ private
59
+
60
+ def is_status?(status)
61
+ !!status.match(/\A[1-5][0-9][0-9]\z/)
62
+ end
63
+ end
64
+ end
65
+ end