bitly 1.1.2 → 2.0.0.beta.1

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.
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