From 2b8d990a2a25c738607f8eec224ba0afc77192f5 Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 22:59:51 -0400 Subject: [PATCH 01/24] Updates --- lib/api/auth_api.rb | 2 ++ lib/api/profile_api.rb | 2 ++ lib/api/user_api.rb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index 68cb374..45b5e1d 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -1,3 +1,5 @@ +require_relative 'base_api.rb' + class AuthApi < BaseApi # PATHS diff --git a/lib/api/profile_api.rb b/lib/api/profile_api.rb index 84e7cf5..c75a7cb 100644 --- a/lib/api/profile_api.rb +++ b/lib/api/profile_api.rb @@ -1,3 +1,5 @@ +require_relative 'base_api.rb' + class ProfileApi < BaseApi # PATHS diff --git a/lib/api/user_api.rb b/lib/api/user_api.rb index af5923b..ae83af6 100644 --- a/lib/api/user_api.rb +++ b/lib/api/user_api.rb @@ -1,3 +1,5 @@ +require_relative 'base_api.rb' + class UserApi < BaseApi # PATHS From 85e88bac2bb1721ec4bdd9b58fc8b7d0ff61200e Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:02:07 -0400 Subject: [PATCH 02/24] Added require --- readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index dcc09f0..902e6ec 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (http * https://buffer.com/developers/api/links * https://buffer.com/developers/api/info -### Gemfile +### Installing Put this sucker in your Gemfile and bundle install @@ -26,6 +26,12 @@ gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :ref => "cd461c Apologies for the SHA, I'll add a tag shortly +Then drop this into your config/application.rb (or wherever you want to use it) + +```ruby +require 'buffer_app' +``` + ### Basic Usage You can define the client up front or configure it later. The options array the BufferClient (and the API objects behind the scenes) uses takes in the following parameters: From 732add64c572ff824d6baedbfb0647576b5e8632 Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:10:04 -0400 Subject: [PATCH 03/24] Typo fix --- lib/buffer_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index d2b0106..292689c 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -11,7 +11,7 @@ def initialize(options = {}) # Initialize the API objects we may use @auth_api = AuthApi.new(options) @user_api = UserApi.new(options) - @profile_api = ProflieApi.new(options) + @profile_api = ProfileApi.new(options) end def error From 54836fef70d848a96f5cf31abc9751395652e8e4 Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:12:45 -0400 Subject: [PATCH 04/24] Typo fix --- lib/api/auth_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index 45b5e1d..7f6a49f 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -5,7 +5,7 @@ class AuthApi < BaseApi # PATHS OAUTH_PATH = "oauth2/token.json" - def get_auth_code + def get_auth_token return nil unless verify_user_code && verify_env_vars oauth_url = "#{API_URL}/#{API_VERSION}/#{OAUTH_PATH}" From b639822ef963513ceb8a9433469ed52b76dd7a4a Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:16:35 -0400 Subject: [PATCH 05/24] Add configure to client --- lib/buffer_client.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index 292689c..16ba89b 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -14,20 +14,27 @@ def initialize(options = {}) @profile_api = ProfileApi.new(options) end + # Reconfigure API objects + def configure(options = {}) + @auth_api = AuthApi.configure(options) + @user_api = UserApi.configure(options) + @profile_api = ProfileApi.configure(options) + end + def error @error end def get_auth_token - AuthApi.get_auth_token + @auth_api.get_auth_token end def get_user_id - UserApi.get_user_id + @user_api.get_user_id end def get_user_json - UserApi.get_user_json + @profile_api.get_user_json end def get_user_profiles From b643551ff58b87ca2a2e570541879d127b55d4c9 Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:22:49 -0400 Subject: [PATCH 06/24] Added some error functionality --- lib/api/base_api.rb | 10 ++++++++++ lib/buffer_client.rb | 14 +++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/api/base_api.rb b/lib/api/base_api.rb index 040250c..0b692a0 100644 --- a/lib/api/base_api.rb +++ b/lib/api/base_api.rb @@ -21,6 +21,16 @@ def configure(options = {}) @logger = options[:logger] if options[:logger] end + def has_error? + @error.present? + end + + def get_error + tmp_error = @error + @error = nil + tmp_error + end + def error @error end diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index 16ba89b..8704ed2 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -21,12 +21,24 @@ def configure(options = {}) @profile_api = ProfileApi.configure(options) end + def has_error? + @error.present? + end + + def get_error + tmp_error = @error + @error = nil + tmp_error + end + def error @error end def get_auth_token - @auth_api.get_auth_token + token = @auth_api.get_auth_token + if @auth_api.has_error? + end def get_user_id From eb56584a10638dfa941d3ffae4853570cb4e874a Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:26:02 -0400 Subject: [PATCH 07/24] Updated client to include errors --- lib/buffer_client.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index 8704ed2..761dad1 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -38,18 +38,36 @@ def error def get_auth_token token = @auth_api.get_auth_token if @auth_api.has_error? + @error = @auth_api.get_error + end + token end def get_user_id - @user_api.get_user_id + user_id = @user_api.get_user_id + if @user_api.has_error? + @error = @user_api.get_error + end + + user_id end def get_user_json - @profile_api.get_user_json + user_json = @user_api.get_user_json + if @user_api.has_error? + @error = @user_api.get_error + end + + user_json end def get_user_profiles - ProfileApi.get_profiles + profiles = @profile_api.get_profiles + if @profile_api.has_error? + @error = @profile_api.get_error + end + + profiles end end From 506e88f0f219c4a73c0fc303011ecd3207f2ffc1 Mon Sep 17 00:00:00 2001 From: kaneda Date: Thu, 16 Jul 2015 23:31:39 -0400 Subject: [PATCH 08/24] Fail message for auth token --- lib/api/auth_api.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index 7f6a49f..309cf5f 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -20,6 +20,9 @@ def get_auth_token json_response = get_post_response(uri, post_data) if json_response.present? json_response["access_token"] + else + @error = "Failed to get JSON token, received: #{json_response}" + nil end end end From c443bc6885545e9d5bfe7e602c0865d3c622fcac Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 00:08:20 -0400 Subject: [PATCH 09/24] All the fixes --- lib/api/auth_api.rb | 4 ++-- lib/api/helpers/api_helpers.rb | 35 ++++++++++++++++------------------ lib/api/profile_api.rb | 2 +- lib/buffer_client.rb | 8 +++----- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index 309cf5f..44b8c34 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -13,12 +13,12 @@ def get_auth_token post_data = "client_id=#{ENV['BUFFER_KEY']}&" + "client_secret=#{ENV['BUFFER_SECRET']}&" + - "redirect_uri=#{Rails.configuration.buffer_redirect_uri}&" + + "redirect_uri=#{ENV['REDIRECT_URI']}&" + "code=#{@user_code}&" + "grant_type=authorization_code" json_response = get_post_response(uri, post_data) - if json_response.present? + if json_response.present? && json_response["access_token"].present? json_response["access_token"] else @error = "Failed to get JSON token, received: #{json_response}" diff --git a/lib/api/helpers/api_helpers.rb b/lib/api/helpers/api_helpers.rb index 167d3f5..33ed8d5 100644 --- a/lib/api/helpers/api_helpers.rb +++ b/lib/api/helpers/api_helpers.rb @@ -17,11 +17,23 @@ def has_data?(response) end def parse_data(response) + return nil unless has_data?(response) + begin - JSON.parse(response.body) + json_response = JSON.parse(response.body) + if json_response.present? + if response.code == GOOD_RESPONSE + return json_response + elsif json_response["error"].present? + @error = json_response["error"] + elsif json_response["code"].present? + @error = get_error_message(response.code, json_response["code"]) + else + @error = DEFAULT_ERR + end + end rescue => e Rails.logger.error "Failed to parse JSON return from Buffer: #{e}" - nil end end @@ -39,9 +51,7 @@ def get_get_response(uri) req = Net::HTTP::Get.new(uri) response = http.request(req) - if has_data?(response) - parse_data(response) - end + parse_data(response) end def get_post_response(uri, post_data) @@ -52,20 +62,7 @@ def get_post_response(uri, post_data) req.body = post_data response = http.request(req) - if has_data?(response) - json_response = parse_data(response) - if json_response.present? - if response.code == GOOD_RESPONSE - return json_response["access_token"] - elsif json_response["error"].present? - @error = json_response["error"] - elsif json_response["error_code"].present? - @error = get_error_message(response.code, json_response["error_code"]) - else - @error = DEFAULT_ERR - end - end - end + parse_data(response) end def get_error_message(http_code, error_code) diff --git a/lib/api/profile_api.rb b/lib/api/profile_api.rb index c75a7cb..387c0b7 100644 --- a/lib/api/profile_api.rb +++ b/lib/api/profile_api.rb @@ -8,7 +8,7 @@ class ProfileApi < BaseApi def get_profiles return nil unless verify_token - profile_url = "#{API_URL}/#{API_VERSION}/#{PROFILES_PATH}?access_token=#{@auth_code}" + profile_url = "#{API_URL}/#{API_VERSION}/#{PROFILES_PATH}?access_token=#{@auth_token}" uri = URI.parse(profile_url) get_get_response(uri) diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index 761dad1..94251dc 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -1,5 +1,3 @@ -require "net/http" -require "uri" require_relative "api/auth_api.rb" require_relative "api/user_api.rb" require_relative "api/profile_api.rb" @@ -16,9 +14,9 @@ def initialize(options = {}) # Reconfigure API objects def configure(options = {}) - @auth_api = AuthApi.configure(options) - @user_api = UserApi.configure(options) - @profile_api = ProfileApi.configure(options) + @auth_api.configure(options) + @user_api.configure(options) + @profile_api.configure(options) end def has_error? From 96a9360b60ba7dfa69f13e38d708fe34d4ea06a1 Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 00:10:50 -0400 Subject: [PATCH 10/24] Updated tag --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 902e6ec..d51575d 100644 --- a/readme.md +++ b/readme.md @@ -21,11 +21,9 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (http Put this sucker in your Gemfile and bundle install ```ruby -gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :ref => "cd461c8a9f934c9a499e7bfe80f93d22e0f6481a" +gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :tag => "v1.0" ``` -Apologies for the SHA, I'll add a tag shortly - Then drop this into your config/application.rb (or wherever you want to use it) ```ruby From 737fb022b7de12e469bd016df80dfed9179cc253 Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 13:03:24 -0400 Subject: [PATCH 11/24] readme update --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index d51575d..6b7df7f 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,8 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (https://buffer.com/developers/api), which is intended to return JSON as opposed to objects. +[![Code Climate](https://codeclimate.com/github/kaneda/buffer-ruby/badges/gpa.svg)](https://codeclimate.com/github/kaneda/buffer-ruby) + ## Client API ### What's Imlemented? @@ -15,6 +17,7 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (http * https://buffer.com/developers/api/updates * https://buffer.com/developers/api/links * https://buffer.com/developers/api/info +* Rspecs ### Installing From 2eae11562b82bd8062037102103f7b1cac76029d Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 13:10:32 -0400 Subject: [PATCH 12/24] Fixing code climate issues --- lib/api/auth_api.rb | 3 +-- lib/api/helpers/api_helpers.rb | 33 ++++++++++++++++++++------------- lib/api/profile_api.rb | 4 +--- lib/api/user_api.rb | 4 +--- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index 44b8c34..c40b4a5 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -9,7 +9,6 @@ def get_auth_token return nil unless verify_user_code && verify_env_vars oauth_url = "#{API_URL}/#{API_VERSION}/#{OAUTH_PATH}" - uri = URI.parse(oauth_url) post_data = "client_id=#{ENV['BUFFER_KEY']}&" + "client_secret=#{ENV['BUFFER_SECRET']}&" + @@ -17,7 +16,7 @@ def get_auth_token "code=#{@user_code}&" + "grant_type=authorization_code" - json_response = get_post_response(uri, post_data) + json_response = get_post_response(oauth_url, post_data) if json_response.present? && json_response["access_token"].present? json_response["access_token"] else diff --git a/lib/api/helpers/api_helpers.rb b/lib/api/helpers/api_helpers.rb index 33ed8d5..3fe2e15 100644 --- a/lib/api/helpers/api_helpers.rb +++ b/lib/api/helpers/api_helpers.rb @@ -16,6 +16,16 @@ def has_data?(response) response.present? && response.body.present? end + def set_err(json_response) + if json_response["error"].present? + @error = json_response["error"] + elsif json_response["code"].present? + @error = get_error_message(response.code, json_response["code"]) + else + @error = DEFAULT_ERR + end + end + def parse_data(response) return nil unless has_data?(response) @@ -24,12 +34,8 @@ def parse_data(response) if json_response.present? if response.code == GOOD_RESPONSE return json_response - elsif json_response["error"].present? - @error = json_response["error"] - elsif json_response["code"].present? - @error = get_error_message(response.code, json_response["code"]) else - @error = DEFAULT_ERR + set_err(json_response) end end rescue => e @@ -37,16 +43,17 @@ def parse_data(response) end end - def get_http_obj(uri) - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true + def get_http_obj(url) + uri = URI.parse(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE - http + [ uri, http ] end - def get_get_response(uri) - http = get_http_obj(uri) + def get_get_response(url) + uri, http = get_http_obj(uri) req = Net::HTTP::Get.new(uri) response = http.request(req) @@ -54,8 +61,8 @@ def get_get_response(uri) parse_data(response) end - def get_post_response(uri, post_data) - http = get_http_obj(uri) + def get_post_response(url, post_data) + uri, http = get_http_obj(uri) req = Net::HTTP::Post.new(uri) req.content_type = "application/x-www-form-urlencoded" diff --git a/lib/api/profile_api.rb b/lib/api/profile_api.rb index 387c0b7..5e2d4f3 100644 --- a/lib/api/profile_api.rb +++ b/lib/api/profile_api.rb @@ -9,8 +9,6 @@ def get_profiles return nil unless verify_token profile_url = "#{API_URL}/#{API_VERSION}/#{PROFILES_PATH}?access_token=#{@auth_token}" - uri = URI.parse(profile_url) - - get_get_response(uri) + get_get_response(profile_url) end end diff --git a/lib/api/user_api.rb b/lib/api/user_api.rb index ae83af6..68412c6 100644 --- a/lib/api/user_api.rb +++ b/lib/api/user_api.rb @@ -13,8 +13,6 @@ def get_user_json return nil unless verify_token user_url = "#{API_URL}/#{API_VERSION}/#{USER_PATH}?access_token=#{@auth_token}" - uri = URI.parse(user_url) - - get_get_response(uri) + get_get_response(user_url) end end From 7866361747bedd3e2cbde9eb2fcab0019c528d9e Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 13:15:16 -0400 Subject: [PATCH 13/24] Second code climate cleanup --- lib/api/auth_api.rb | 2 +- lib/api/helpers/api_helpers.rb | 5 +++++ lib/api/profile_api.rb | 4 +--- lib/api/user_api.rb | 4 +--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index c40b4a5..0f507c0 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -8,7 +8,7 @@ class AuthApi < BaseApi def get_auth_token return nil unless verify_user_code && verify_env_vars - oauth_url = "#{API_URL}/#{API_VERSION}/#{OAUTH_PATH}" + oauth_url = build_url(OAUTH_PATH, false) post_data = "client_id=#{ENV['BUFFER_KEY']}&" + "client_secret=#{ENV['BUFFER_SECRET']}&" + diff --git a/lib/api/helpers/api_helpers.rb b/lib/api/helpers/api_helpers.rb index 3fe2e15..06c3ced 100644 --- a/lib/api/helpers/api_helpers.rb +++ b/lib/api/helpers/api_helpers.rb @@ -12,6 +12,11 @@ def log_or_print(msg, method = :error) end end + def build_url(path, auth_token = true) + url = "#{API_URL}/#{API_VERSION}/#{path}" + url += "?access_token=#{@auth_token}" if auth_token + end + def has_data?(response) response.present? && response.body.present? end diff --git a/lib/api/profile_api.rb b/lib/api/profile_api.rb index 5e2d4f3..310b7c9 100644 --- a/lib/api/profile_api.rb +++ b/lib/api/profile_api.rb @@ -7,8 +7,6 @@ class ProfileApi < BaseApi def get_profiles return nil unless verify_token - - profile_url = "#{API_URL}/#{API_VERSION}/#{PROFILES_PATH}?access_token=#{@auth_token}" - get_get_response(profile_url) + get_get_response( build_url(PROFILES_PATH) ) end end diff --git a/lib/api/user_api.rb b/lib/api/user_api.rb index 68412c6..b9cabe7 100644 --- a/lib/api/user_api.rb +++ b/lib/api/user_api.rb @@ -11,8 +11,6 @@ def get_user_id def get_user_json return nil unless verify_token - - user_url = "#{API_URL}/#{API_VERSION}/#{USER_PATH}?access_token=#{@auth_token}" - get_get_response(user_url) + get_get_response( build_url(USER_PATH) ) end end From cef9fb487a2f3d61e82b57e095cb11c6d0168169 Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 17:30:34 -0400 Subject: [PATCH 14/24] Finished API implementation --- lib/api/auth_api.rb | 2 +- lib/api/base_api.rb | 2 - lib/api/helpers/api_helpers.rb | 54 ++++++++---- lib/api/link_api.rb | 11 +++ lib/api/profile_api.rb | 34 +++++++- lib/api/update_api.rb | 83 ++++++++++++++++++ lib/buffer_client.rb | 152 +++++++++++++++++++++++++++++---- readme.md | 56 ++++++++++-- 8 files changed, 350 insertions(+), 44 deletions(-) create mode 100644 lib/api/link_api.rb create mode 100644 lib/api/update_api.rb diff --git a/lib/api/auth_api.rb b/lib/api/auth_api.rb index 0f507c0..568f233 100644 --- a/lib/api/auth_api.rb +++ b/lib/api/auth_api.rb @@ -8,7 +8,7 @@ class AuthApi < BaseApi def get_auth_token return nil unless verify_user_code && verify_env_vars - oauth_url = build_url(OAUTH_PATH, false) + oauth_url = build_url(OAUTH_PATH, {}, false) post_data = "client_id=#{ENV['BUFFER_KEY']}&" + "client_secret=#{ENV['BUFFER_SECRET']}&" + diff --git a/lib/api/base_api.rb b/lib/api/base_api.rb index 0b692a0..e01f1c2 100644 --- a/lib/api/base_api.rb +++ b/lib/api/base_api.rb @@ -2,11 +2,9 @@ require "net/http" require "uri" require_relative "helpers/api_helpers.rb" -require_relative "../buffer_vars.rb" class BaseApi include ApiHelpers - include BufferVars def initialize(options = {}) @user_code = options[:user_code] diff --git a/lib/api/helpers/api_helpers.rb b/lib/api/helpers/api_helpers.rb index 06c3ced..e99a511 100644 --- a/lib/api/helpers/api_helpers.rb +++ b/lib/api/helpers/api_helpers.rb @@ -1,6 +1,9 @@ require_relative '../error_codes.rb' +require_relative '../../buffer_vars.rb' module ApiHelpers + include BufferVars + GOOD_RESPONSE = "200" DEFAULT_ERR = "An unknown error occurred, please contact an administrator for assistance" @@ -12,9 +15,23 @@ def log_or_print(msg, method = :error) end end - def build_url(path, auth_token = true) - url = "#{API_URL}/#{API_VERSION}/#{path}" - url += "?access_token=#{@auth_token}" if auth_token + def build_options_string(options = {}) + options.collect { |k, v| "#{k}=#{v}" }.join("&") + end + + def build_url(path, options = {}, auth_token = true) + url = "#{API_URL}/#{API_VERSION}/#{path}" + options_string = build_options_string(options) + token_param = "access_token=#{@auth_token}" + + if options_string.present? + url += "?#{options_string}" + url += "&#{token_param}" if auth_token + elsif auth_token + url += "?#{token_param}" + end + + url end def has_data?(response) @@ -22,10 +39,14 @@ def has_data?(response) end def set_err(json_response) - if json_response["error"].present? - @error = json_response["error"] - elsif json_response["code"].present? - @error = get_error_message(response.code, json_response["code"]) + if json_response.present? + if json_response["error"].present? + @error = json_response["error"] + elsif json_response["code"].present? + @error = get_error_message(response.code, json_response["code"]) + else + @error = DEFAULT_ERR + end else @error = DEFAULT_ERR end @@ -36,16 +57,15 @@ def parse_data(response) begin json_response = JSON.parse(response.body) - if json_response.present? - if response.code == GOOD_RESPONSE - return json_response - else - set_err(json_response) - end - end rescue => e Rails.logger.error "Failed to parse JSON return from Buffer: #{e}" end + + if response.code == GOOD_RESPONSE + return json_response + else + set_err(json_response) + end end def get_http_obj(url) @@ -58,7 +78,7 @@ def get_http_obj(url) end def get_get_response(url) - uri, http = get_http_obj(uri) + uri, http = get_http_obj(url) req = Net::HTTP::Get.new(uri) response = http.request(req) @@ -66,8 +86,8 @@ def get_get_response(url) parse_data(response) end - def get_post_response(url, post_data) - uri, http = get_http_obj(uri) + def get_post_response(url, post_data = "") + uri, http = get_http_obj(url) req = Net::HTTP::Post.new(uri) req.content_type = "application/x-www-form-urlencoded" diff --git a/lib/api/link_api.rb b/lib/api/link_api.rb new file mode 100644 index 0000000..5609a58 --- /dev/null +++ b/lib/api/link_api.rb @@ -0,0 +1,11 @@ +require_relative 'base_api.rb' + +class LinkApi < BaseApi + + # PATHS + SHARES_PATH = "links/shares.json" + + def get_shares(encoded_url) + get_get_response( build_url("#{SHARES_PATH}?url=#{encoded_url}", {}, false) ) + end +end diff --git a/lib/api/profile_api.rb b/lib/api/profile_api.rb index 310b7c9..67a2826 100644 --- a/lib/api/profile_api.rb +++ b/lib/api/profile_api.rb @@ -3,10 +3,42 @@ class ProfileApi < BaseApi # PATHS - PROFILES_PATH = "profiles.json" + PROFILE_PATH = "profiles" + PROFILES_PATH = "#{PROFILE_PATH}.json" + SCHEDULE_PATH = "schedules" + SCHEDULES_PATH = "#{SCHEDULE_PATH}.json" + UPDATE_PATH = "#{SCHEDULE_PATH}/update.json" def get_profiles return nil unless verify_token get_get_response( build_url(PROFILES_PATH) ) end + + def get_profile(id) + return nil unless verify_token + get_get_response( build_url("#{PROFILE_PATH}/#{id}.json") ) + end + + def get_schedule(id) + return nil unless verify_token + get_get_response( build_url("#{PROFILE_PATH}/#{id}/#{SCHEDULES_PATH}") ) + end + + def update_schedule(id, sched_array) + return nil unless verify_token + + update_url = build_url("#{PROFILE_PATH}/#{id}/#{UPDATE_PATH}") + + post_data = "" + sched_array.each_with_index do |sched, index| + base = "schedules[#{index}]" + [:days, :times].each do |key| + sched[key].each do |val| + post_data += "#{base}[#{key}][]=#{val}&" + end + end + end + + get_post_response(update_url, post_data) + end end diff --git a/lib/api/update_api.rb b/lib/api/update_api.rb new file mode 100644 index 0000000..e704ef5 --- /dev/null +++ b/lib/api/update_api.rb @@ -0,0 +1,83 @@ +require_relative 'base_api.rb' + +class UpdateApi < BaseApi + + # PATHS + PROFILE_PATH = "profiles" + UPDATE_PATH = "updates" + PENDING_PATH = "#{UPDATE_PATH}/pending.json" + SENT_PATH = "#{UPDATE_PATH}/sent.json" + INTERACTIONS_PATH = "interactions.json" + REORDER_PATH = "#{UPDATE_PATH}/reorder.json" + SHUFFLE_PATH = "#{UPDATE_PATH}/shuffle.json" + CREATE_PATH = "#{UPDATE_PATH}/create.json" + UPDATE_STATUS_PATH = "update.json" + SHARE_PATH = "share.json" + DESTROY_PATH = "destroy.json" + MOVE_TOP_PATH = "move_to_top.json" + + def get_update(id) + return nil unless verify_token + get_get_response( build_url("#{UPDATE_PATH}/#{id}.json") ) + end + + def get_pending_updates(id, options = {}) + return nil unless verify_token + get_get_response( build_url("#{PROFILE_PATH}/#{id}/#{PENDING_PATH}", options) ) + end + + def get_sent_updates(id, options = {}) + return nil unless verify_token + get_get_response( build_url("#{PROFILE_PATH}/#{id}/#{SENT_PATH}", options) ) + end + + def get_interactions(id, options = {}) + return nil unless verify_token + get_get_response( build_url("#{UPDATE_PATH}/#{id}/#{INTERACTIONS_PATH}", options) ) + end + + def reorder_updates(id, updates_array, options = {}) + return nil unless verify_token + post_data = "#{build_options_string(options)}&" + post_data += updates_array.map { |u| "order[]=#{u}" }.join("&") + + get_post_response( build_url("#{PROFILE_PATH}/#{id}/#{REORDER_PATH}"), post_data ) + end + + def shuffle_updates(id, options = {}) + return nil unless verify_token + post_data = build_options_string(options) + + get_post_response( build_url("#{PROFILE_PATH}/#{id}/#{SHUFFLE_PATH}"), post_data ) + end + + def create_update(profile_ids, options = {}) + return nil unless verify_token + post_data = "#{build_options_string(options)}&" + post_data += profile_ids.map { |i| "profile_ids[]=#{i}" }.join("&") + + get_post_response( build_url(CREATE_PATH), post_data ) + end + + def update_status(id, options = {}) + return nil unless verify_token + post_data = build_options_string(options) + + get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{UPDATE_STATUS_PATH}"), post_data ) + end + + def share_update(id) + return nil unless verify_token + get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{SHARE_PATH}") ) + end + + def destroy_update(id) + return nil unless verify_token + get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{DESTROY_PATH}") ) + end + + def move_to_top(id) + return nil unless verify_token + get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{MOVE_TOP_PATH}") ) + end +end diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index 94251dc..b84f752 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -1,6 +1,9 @@ +require 'cgi' require_relative "api/auth_api.rb" require_relative "api/user_api.rb" require_relative "api/profile_api.rb" +require_relative "api/update_api.rb" +require_relative "api/link_api.rb" class BufferClient def initialize(options = {}) @@ -10,13 +13,23 @@ def initialize(options = {}) @auth_api = AuthApi.new(options) @user_api = UserApi.new(options) @profile_api = ProfileApi.new(options) + @update_api = UpdateApi.new(options) + @link_api = LinkApi.new(options) + + @api_objects = [ + @auth_api, + @user_api, + @profile_api, + @update_api, + @link_api + ] end # Reconfigure API objects def configure(options = {}) - @auth_api.configure(options) - @user_api.configure(options) - @profile_api.configure(options) + @api_objects.each do |api| + api.configure(options) + end end def has_error? @@ -35,37 +48,146 @@ def error def get_auth_token token = @auth_api.get_auth_token - if @auth_api.has_error? - @error = @auth_api.get_error - end + record_err(@auth_api) token end def get_user_id user_id = @user_api.get_user_id - if @user_api.has_error? - @error = @user_api.get_error - end + record_err(@user_api) user_id end def get_user_json user_json = @user_api.get_user_json - if @user_api.has_error? - @error = @user_api.get_error - end + record_err(@user_api) user_json end def get_user_profiles profiles = @profile_api.get_profiles - if @profile_api.has_error? - @error = @profile_api.get_error - end + record_err(@profile_api) profiles end + + def get_user_profile(id) + profile = @profile_api.get_profile(id) + record_err(@profile_api) + + profile + end + + def get_schedule(id) + schedule = @profile_api.get_schedule(id) + record_err(@profile_api) + + schedule + end + + def update_schedule(id, sched_array) + success_json = @profile_api.update_schedule(id, sched_array) + record_err(@profile_api) + + is_success?(success_json) + end + + def get_update(id) + update_json = @update_api.get_update(id) + record_err(@update_api) + + update_json + end + + def get_pending_updates(id, options = {}) + pending_updates_json = @update_api.get_pending_updates(id, options) + record_err(@update_api) + + pending_updates_json + end + + def get_sent_updates(id, options = {}) + sent_updates_json = @update_api.get_sent_updates(id, options) + record_err(@update_api) + + sent_updates_json + end + + def get_interactions(id, event, options = {}) + # Forces the user to enter a value for event without + # needing to validate the hash + options[:event] = event + interactions_json = @update_api.get_interactions(id, options) + record_err(@update_api) + + interactions_json + end + + def reorder_updates(id, updates_array, options = {}) + new_order_json = @update_api.reorder_updates(id, updates_array, options) + record_err(@update_api) + + new_order_json + end + + def create_update(profile_ids, options = {}) + post_json = @update_api.create_update(profile_ids, options) + record_err(@update_api) + + post_json + end + + def update_status(id, text, options = {}) + # Forces the user to enter a value for text without + # needing to validate the hash + options[:text] = text + update_json = @update_api.update_status(id, options) + record_err(@update_api) + + update_json + end + + def share_update(id) + success_json = @update_api.share_update(id) + record_err(@update_api) + + is_success?(success_json) + end + + def destroy_update(id) + success_json = @update_api.destroy_update(id) + record_err(@update_api) + + is_success?(success_json) + end + + def move_to_top(id) + success_json = @update_api.move_to_top(id) + record_err(@update_api) + + is_success?(success_json) + end + + def get_shares(url) + # Encode URL + encoded_url = CGI.escape(url) + share_json = @link_api.get_shares(encoded_url) + record_err(@link_api) + + if share_json.present? && share_json["shares"].present? + share_json["shares"] + end + end + + private + def is_success?(success_json) + success_json.present? && success_json.include?("success") && success_json["success"] == true + end + + def record_err(api) + @error = api.get_error if api.has_error? + end end diff --git a/readme.md b/readme.md index 6b7df7f..d51b202 100644 --- a/readme.md +++ b/readme.md @@ -10,12 +10,12 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (http * https://buffer.com/developers/api/oauth * https://buffer.com/developers/api/user * https://buffer.com/developers/api/profiles +* https://buffer.com/developers/api/links * https://buffer.com/developers/api/errors ### What's Missing? -* https://buffer.com/developers/api/updates -* https://buffer.com/developers/api/links +* https://buffer.com/developers/api/updates (WIP) * https://buffer.com/developers/api/info * Rspecs @@ -55,12 +55,52 @@ buffer_client.configure({ ``` ### Available Calls -| Client call | Description | Notes -| :-----------: | :----------- | :----- -| get_auth_token | Returns the user's long-lasting auth token | user_code must be defined in the buffer_client, as well as the ENV variables "BUFFER_KEY" and "BUFFER_SECRET" | -| get_user_id | Returns the user's Buffer ID | auth_token must be defined in the buffer client | -| get_user_json | Returns the entirety of the user JSON | auth_token must be defined in the buffer client | -| get_user_profiles | Returns the entirety of the profile JSON | auth_token must be defined in the buffer client | +| Client call | Input | Description | Notes +| :-----------: | :----- | :----------- | :----- +| get_auth_token | | Returns the user's long-lasting auth token | user_code must be defined in the buffer_client, as well as the ENV variables "BUFFER_KEY" and "BUFFER_SECRET" | +| get_user_id | | Returns the user's Buffer ID | | +| get_user_json | | Returns the entirety of the user JSON | | +| get_user_profiles | | Returns the entirety of the profile JSON | | +| get_user_profile | Profile ID | Get a single profile as JSON by ID | | +| get_schedule | Profile ID | Get the schedule of a profile as JSON by ID | | +| update_schedule | Profile ID, Schedule Hash | Set the schedule of a profile as JSON by ID | auth_token must be defined in the buffer client. See below for schedule representation | +| get_update | Social Media Post ID | Gets an update by post ID | | +| get_pending_updates | Profile ID, Options Hash (optional) | Gets pending updates as JSON by profile ID | Takes in hash of options, see Buffer API docs for optional parameters | +| get_sent_updates | Profile ID, Options Hash (optional) | Gets sent updates as JSON by profile ID | Takes in hash of options, see Buffer API docs for optional parameters | +| get_interactions | Social Media Post ID, Event, Options Hash (optional) | Gets interactions based on event type (see https://bufferapp.com/developers/api/info#configuration) | Takes in a hash of options, see Buffer API docs for optional parameters | +| reorder_updates | Profile ID, Updates Array, Options Hash (optional) | Updates order of updates in a profile based on updates array | | +| shuffle_updates | Profile ID, Options Hash (optional) | Randomize the order of updates to be sent | | +| create_update | Profile ID Array, Options Hash (optional) | Create a new post | Note that for the "media" option, please specify each media option in the hash separately, e.g. ```{ "media[link]" => "http%3A%2F%2Fgoogle.com", "media[description]" => "The%20google%20homepage" }``` | +| update_status | Social Media Post ID, Text, Options Hash (optional) | Update an existing status | For the "media" option see the note on create_update | +| share_update | Social Media Post ID | Share a post immediately | | +| destroy_update | Social Media Post ID| Permanently destroy an update | | +| move_to_top | Social Media Post ID| Move post to top of queue | | +| get_shares | URL (unencoded) | Gets the number of shares for a given URL through Buffer | You can pass a normal URL here, the client will encode it. This is one of the only calls to not require an auth_token | + +### Helper methods +| Method | Description | +| :---------: | :----- | +| configure | Takes in a hash (as above) and reconfigures all API objects | +| has_error? | Returns true if an error has been set in the client | +| get_error | Returns the current error and wipes it out in the client | +| error | Returns the current error, leaving it in tact | + + +### Defining a schedule + +To update a schedule the BufferClient is expecting a schedule of the form: + +```ruby +[ + { + :days => ['mon', 'tue', 'thu'], + :times => ['12:45', '15:30', '17:43'] + }, + . + . + . +] +``` ## Contributing From 78c6d1684f483b4976e5abf3fc3b9fde0379c0de Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 17:30:59 -0400 Subject: [PATCH 15/24] Roll version in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index d51b202..33ea284 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (http Put this sucker in your Gemfile and bundle install ```ruby -gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :tag => "v1.0" +gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :tag => "v1.1" ``` Then drop this into your config/application.rb (or wherever you want to use it) From 9a76cfb116464d7b6839c818b4f897412f517b36 Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 17:42:39 -0400 Subject: [PATCH 16/24] Added final piece, info --- lib/api/info_api.rb | 12 ++++++++++++ lib/buffer_client.rb | 41 ++++++++++++++++++++++++++++++++++++++++- readme.md | 5 +++-- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 lib/api/info_api.rb diff --git a/lib/api/info_api.rb b/lib/api/info_api.rb new file mode 100644 index 0000000..7aac66a --- /dev/null +++ b/lib/api/info_api.rb @@ -0,0 +1,12 @@ +require_relative 'base_api.rb' + +class InfoApi < BaseApi + + # PATHS + CONFIG_PATH = "info/configuration.json" + + def get_configuration + return nil unless verify_token + get_get_response( build_url(CONFIG_PATH) ) + end +end diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index b84f752..6fa50c0 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -4,6 +4,7 @@ require_relative "api/profile_api.rb" require_relative "api/update_api.rb" require_relative "api/link_api.rb" +require_relative "api/info_api.rb" class BufferClient def initialize(options = {}) @@ -15,16 +16,22 @@ def initialize(options = {}) @profile_api = ProfileApi.new(options) @update_api = UpdateApi.new(options) @link_api = LinkApi.new(options) + @info_api = InfoApi.new(options) @api_objects = [ @auth_api, @user_api, @profile_api, @update_api, - @link_api + @link_api, + @info_api ] end + ################## + # HELPER METHODS # + ################## + # Reconfigure API objects def configure(options = {}) @api_objects.each do |api| @@ -46,6 +53,10 @@ def error @error end + ############ + # AUTH API # + ############ + def get_auth_token token = @auth_api.get_auth_token record_err(@auth_api) @@ -53,6 +64,10 @@ def get_auth_token token end + ############ + # USER API # + ############ + def get_user_id user_id = @user_api.get_user_id record_err(@user_api) @@ -67,6 +82,10 @@ def get_user_json user_json end + ############### + # PROFILE API # + ############### + def get_user_profiles profiles = @profile_api.get_profiles record_err(@profile_api) @@ -95,6 +114,10 @@ def update_schedule(id, sched_array) is_success?(success_json) end + ############## + # UPDATE API # + ############## + def get_update(id) update_json = @update_api.get_update(id) record_err(@update_api) @@ -171,6 +194,10 @@ def move_to_top(id) is_success?(success_json) end + ############ + # LINK API # + ############ + def get_shares(url) # Encode URL encoded_url = CGI.escape(url) @@ -182,7 +209,19 @@ def get_shares(url) end end + ############ + # INFO API # + ############ + + def get_configuration + info_json = @info_api.get_configuration + record_err(@info_api) + + info_json + end + private + def is_success?(success_json) success_json.present? && success_json.include?("success") && success_json["success"] == true end diff --git a/readme.md b/readme.md index 33ea284..b4311ac 100644 --- a/readme.md +++ b/readme.md @@ -10,13 +10,13 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (http * https://buffer.com/developers/api/oauth * https://buffer.com/developers/api/user * https://buffer.com/developers/api/profiles +* https://buffer.com/developers/api/updates * https://buffer.com/developers/api/links +* https://buffer.com/developers/api/info * https://buffer.com/developers/api/errors ### What's Missing? -* https://buffer.com/developers/api/updates (WIP) -* https://buffer.com/developers/api/info * Rspecs ### Installing @@ -76,6 +76,7 @@ buffer_client.configure({ | destroy_update | Social Media Post ID| Permanently destroy an update | | | move_to_top | Social Media Post ID| Move post to top of queue | | | get_shares | URL (unencoded) | Gets the number of shares for a given URL through Buffer | You can pass a normal URL here, the client will encode it. This is one of the only calls to not require an auth_token | +| get_configuration | | Gets the current Buffer config | | ### Helper methods | Method | Description | From e94984a40bb7c61db076fc4210d94a8b0630ce27 Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 17:43:58 -0400 Subject: [PATCH 17/24] Updated readme spacing --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b4311ac..565e516 100644 --- a/readme.md +++ b/readme.md @@ -56,7 +56,7 @@ buffer_client.configure({ ### Available Calls | Client call | Input | Description | Notes -| :-----------: | :----- | :----------- | :----- +| :---------: | :------ | :---- | :--------- | get_auth_token | | Returns the user's long-lasting auth token | user_code must be defined in the buffer_client, as well as the ENV variables "BUFFER_KEY" and "BUFFER_SECRET" | | get_user_id | | Returns the user's Buffer ID | | | get_user_json | | Returns the entirety of the user JSON | | From cfdfbbe8a375526e0b125f5b7f1c940cf341f932 Mon Sep 17 00:00:00 2001 From: kaneda Date: Fri, 17 Jul 2015 17:52:17 -0400 Subject: [PATCH 18/24] Code climate update --- lib/api/update_api.rb | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/api/update_api.rb b/lib/api/update_api.rb index e704ef5..0a1ccd1 100644 --- a/lib/api/update_api.rb +++ b/lib/api/update_api.rb @@ -23,17 +23,17 @@ def get_update(id) def get_pending_updates(id, options = {}) return nil unless verify_token - get_get_response( build_url("#{PROFILE_PATH}/#{id}/#{PENDING_PATH}", options) ) + get_get_response( build_profile_url(id, PENDING_PATH, options) ) end def get_sent_updates(id, options = {}) return nil unless verify_token - get_get_response( build_url("#{PROFILE_PATH}/#{id}/#{SENT_PATH}", options) ) + get_get_response( build_profile_url(id, SENT_PATH, options) ) end def get_interactions(id, options = {}) return nil unless verify_token - get_get_response( build_url("#{UPDATE_PATH}/#{id}/#{INTERACTIONS_PATH}", options) ) + get_get_response( build_update_url(id, INTERACTIONS_PATH, options) ) end def reorder_updates(id, updates_array, options = {}) @@ -41,14 +41,14 @@ def reorder_updates(id, updates_array, options = {}) post_data = "#{build_options_string(options)}&" post_data += updates_array.map { |u| "order[]=#{u}" }.join("&") - get_post_response( build_url("#{PROFILE_PATH}/#{id}/#{REORDER_PATH}"), post_data ) + get_post_response( build_profile_url(id, REORDER_PATH), post_data ) end def shuffle_updates(id, options = {}) return nil unless verify_token post_data = build_options_string(options) - get_post_response( build_url("#{PROFILE_PATH}/#{id}/#{SHUFFLE_PATH}"), post_data ) + get_post_response( build_profile_url(id, SHUFFLE_PATH), post_data ) end def create_update(profile_ids, options = {}) @@ -63,21 +63,31 @@ def update_status(id, options = {}) return nil unless verify_token post_data = build_options_string(options) - get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{UPDATE_STATUS_PATH}"), post_data ) + get_post_response( build_update_url(id, UPDATE_STATUS_PATH), post_data ) end def share_update(id) return nil unless verify_token - get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{SHARE_PATH}") ) + get_post_response( build_update_url(id, SHARE_PATH) ) end def destroy_update(id) return nil unless verify_token - get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{DESTROY_PATH}") ) + get_post_response( build_update_url(id, DESTROY_PATH) ) end def move_to_top(id) return nil unless verify_token - get_post_response( build_url("#{UPDATE_PATH}/#{id}/#{MOVE_TOP_PATH}") ) + get_post_response( build_update_url(id, MOVE_TOP_PATH) ) + end + + private + + def build_update_url(id, path, options = {}) + build_url("#{UPDATE_PATH}/#{id}/#{path}", options) + end + + def build_profile_url(id, path, options = {}) + build_url("#{PROFILE_PATH}/#{id}/#{path}", options) end end From 1cad3f811040d622ceddcdf4e191f700cddb4224 Mon Sep 17 00:00:00 2001 From: kaneda Date: Sat, 18 Jul 2015 01:54:01 -0400 Subject: [PATCH 19/24] [dev-rspecs] Started adding specs - Added travis.yml - Fixed Rakefile - Fixed Gemfile - Allow code climate explicitly in WebMock - Added coverage badge to readme --- .rspec | 3 + .travis.yml | 6 ++ Gemfile | 10 ++-- Gemfile.lock | 70 +++++++++++------------ Rakefile | 21 +++++-- readme.md | 1 + spec/factories.rb | 49 ++++++++++++++++ spec/lib/api/auth_api_spec.rb | 24 ++++++++ spec/lib/api/base_api_spec.rb | 97 ++++++++++++++++++++++++++++++++ spec/lib/api/profile_api_spec.rb | 35 ++++++++++++ spec/spec_helper.rb | 23 ++++++++ 11 files changed, 293 insertions(+), 46 deletions(-) create mode 100644 .rspec create mode 100644 .travis.yml create mode 100644 spec/factories.rb create mode 100644 spec/lib/api/auth_api_spec.rb create mode 100644 spec/lib/api/base_api_spec.rb create mode 100644 spec/lib/api/profile_api_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..2559e39 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--color +--format progress +--require spec_helper diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6aeddbf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: ruby +rvm: + - "1.9.3" + - "2.1.2" +env: + - TRAVIS_CI=true diff --git a/Gemfile b/Gemfile index 661f721..c9f693b 100644 --- a/Gemfile +++ b/Gemfile @@ -27,11 +27,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'spring', group: :development # Use ActiveModel has_secure_password -gem 'bcrypt', '~> 3.1.7' - -gem 'buff' - -gem 'omniauth-buffer2' +# gem 'bcrypt', '~> 3.1.7' # Use unicorn as the app server # gem 'unicorn' @@ -42,3 +38,7 @@ gem 'omniauth-buffer2' # Use debugger # gem 'debugger', group: [:development, :test] +gem 'rspec', group: :test +gem 'webmock', group: :test +gem 'factory_girl', group: :test +gem "codeclimate-test-reporter", group: :test, require: nil diff --git a/Gemfile.lock b/Gemfile.lock index af80d08..1a56586 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,15 +29,9 @@ GEM tzinfo (~> 1.1) addressable (2.3.8) arel (5.0.1.20140414130214) - bcrypt (3.1.10) - buff (0.0.6) - addressable - faraday - faraday_middleware - multi_json - rash - yajl-ruby builder (3.2.2) + codeclimate-test-reporter (0.4.7) + simplecov (>= 0.7.1, < 1.0.0) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -45,13 +39,14 @@ GEM coffee-script-source execjs coffee-script-source (1.9.1.1) + crack (0.4.2) + safe_yaml (~> 1.0.0) + diff-lcs (1.2.5) + docile (1.1.5) erubis (2.7.0) execjs (2.5.2) - faraday (0.9.1) - multipart-post (>= 1.2, < 3) - faraday_middleware (0.10.0) - faraday (>= 0.7.4, < 0.10) - hashie (2.0.5) + factory_girl (4.5.0) + activesupport (>= 3.0.0) hike (1.2.3) i18n (0.7.0) jbuilder (2.3.1) @@ -61,7 +56,6 @@ GEM railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.3) - jwt (1.5.1) libv8 (3.16.14.3) mail (2.5.4) mime-types (~> 1.16) @@ -69,23 +63,6 @@ GEM mime-types (1.25.1) minitest (5.7.0) multi_json (1.11.2) - multi_xml (0.5.5) - multipart-post (2.0.0) - oauth2 (1.0.0) - faraday (>= 0.8, < 0.10) - jwt (~> 1.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (~> 1.2) - omniauth (1.2.2) - hashie (>= 1.2, < 4) - rack (~> 1.0) - omniauth-buffer2 (0.0.1) - omniauth (~> 1.0) - omniauth-oauth2 (~> 1.1) - omniauth-oauth2 (1.3.1) - oauth2 (~> 1.0) - omniauth (~> 1.2) pg (0.17.1) polyglot (0.3.5) rack (1.5.5) @@ -107,11 +84,22 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (10.4.2) - rash (0.4.0) - hashie (~> 2.0.0) rdoc (4.2.0) json (~> 1.4) ref (1.0.5) + rspec (3.0.0) + rspec-core (~> 3.0.0) + rspec-expectations (~> 3.0.0) + rspec-mocks (~> 3.0.0) + rspec-core (3.0.4) + rspec-support (~> 3.0.0) + rspec-expectations (3.0.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.0.0) + rspec-mocks (3.0.4) + rspec-support (~> 3.0.0) + rspec-support (3.0.4) + safe_yaml (1.0.4) sass (3.2.19) sass-rails (4.0.5) railties (>= 4.0.0, < 5.0) @@ -121,6 +109,11 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + simplecov (0.10.0) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) spring (1.3.6) sprockets (2.12.4) hike (~> 1.2) @@ -147,23 +140,26 @@ GEM uglifier (2.7.1) execjs (>= 0.3.0) json (>= 1.8.0) - yajl-ruby (1.2.1) + webmock (1.21.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) PLATFORMS ruby DEPENDENCIES - bcrypt (~> 3.1.7) - buff + codeclimate-test-reporter coffee-rails (~> 4.0.0) + factory_girl jbuilder (~> 2.0) jquery-rails - omniauth-buffer2 pg rails (= 4.1.5) + rspec sass-rails (~> 4.0.3) sdoc (~> 0.4.0) spring therubyracer turbolinks uglifier (>= 1.3.0) + webmock diff --git a/Rakefile b/Rakefile index ba6b733..560cc2c 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,19 @@ -# Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. +require 'rake' -require File.expand_path('../config/application', __FILE__) +task :console do + require 'irb' + require 'irb/completion' + require 'buffer_app' + ARGV.clear + IRB.start +end -Rails.application.load_tasks +begin + require 'rspec/core/rake_task' + + RSpec::Core::RakeTask.new(:spec) + + task :default => :spec +rescue LoadError + # no rspec available +end diff --git a/readme.md b/readme.md index 565e516..38abf9f 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,7 @@ Modelled after Octokit <3. This is a lightweight client for the Buffer API (https://buffer.com/developers/api), which is intended to return JSON as opposed to objects. [![Code Climate](https://codeclimate.com/github/kaneda/buffer-ruby/badges/gpa.svg)](https://codeclimate.com/github/kaneda/buffer-ruby) +[![Test Coverage](https://codeclimate.com/github/kaneda/buffer-ruby/badges/coverage.svg)](https://codeclimate.com/github/kaneda/buffer-ruby/coverage) ## Client API diff --git a/spec/factories.rb b/spec/factories.rb new file mode 100644 index 0000000..ccc6496 --- /dev/null +++ b/spec/factories.rb @@ -0,0 +1,49 @@ +require_relative '../lib/api/base_api.rb' +require_relative '../lib/api/auth_api.rb' +require_relative '../lib/api/user_api.rb' +require_relative '../lib/api/profile_api.rb' +require_relative '../lib/api/update_api.rb' +require_relative '../lib/api/link_api.rb' +require_relative '../lib/api/info_api.rb' + +FactoryGirl.define do + trait :api_traits do + user_code "123456789" + auth_token "123456789abcd" + end + + factory :base_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end + + factory :auth_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end + + factory :user_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end + + factory :profile_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end + + factory :update_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end + + factory :link_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end + + factory :info_api, traits: [:api_traits] do + skip_create + initialize_with { new(attributes) } + end +end diff --git a/spec/lib/api/auth_api_spec.rb b/spec/lib/api/auth_api_spec.rb new file mode 100644 index 0000000..d57d7f6 --- /dev/null +++ b/spec/lib/api/auth_api_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../lib/api/auth_api.rb' + +describe AuthApi do + let(:auth_api) { build(:auth_api) } + let(:token) { "123456789" } + let(:good_json) { {"access_token" => token} } + + before(:each) do + allow_any_instance_of(AuthApi).to receive(:verify_user_code).and_return(true) + allow_any_instance_of(AuthApi).to receive(:verify_env_vars).and_return(true) + end + + describe "#get_auth_token" do + it "returns the access token with a good JSON response" do + allow_any_instance_of(AuthApi).to receive(:get_post_response).and_return(good_json) + expect(auth_api.get_auth_token).to eq(token) + end + + it "returns nil with bad JSON response" do + allow_any_instance_of(AuthApi).to receive(:get_post_response).and_return(nil) + expect(auth_api.get_auth_token).to be_nil + end + end +end diff --git a/spec/lib/api/base_api_spec.rb b/spec/lib/api/base_api_spec.rb new file mode 100644 index 0000000..c127a8a --- /dev/null +++ b/spec/lib/api/base_api_spec.rb @@ -0,0 +1,97 @@ +require_relative '../../../lib/api/base_api.rb' + +describe BaseApi do + let(:base_api) { build(:base_api) } + let(:err) { "An error" } + + describe "#configure" do + let(:new_user_code) { "987654321" } + let(:new_auth_token) { "abc98764321" } + + it "updates the user code" do + base_api.configure({:user_code => new_user_code}) + expect(base_api.instance_variable_get(:@user_code)).to eql(new_user_code) + end + + it "updates the auth token" do + base_api.configure({:auth_token => new_auth_token}) + expect(base_api.instance_variable_get(:@auth_token)).to eql(new_auth_token) + end + + it "ignores updates that are nil or blank" do + base_api.configure({:auth_token => "", :user_code => ""}) + expect(base_api.instance_variable_get(:@auth_token)).to_not eq(new_auth_token) + expect(base_api.instance_variable_get(:@user_code)).to_not eq(new_user_code) + end + end + + describe "#has_error?" do + it "returns false when error is nil or empty" do + expect(base_api.has_error?).to eq(false) + end + + it "returns true when error is present" do + base_api.instance_variable_set(:@error, err) + expect(base_api.has_error?).to eq(true) + end + end + + describe "#get_error" do + before(:each) do + base_api.instance_variable_set(:@error, err) + end + + it "returns the error" do + expect(base_api.get_error).to eq(err) + end + + it "wipes the error" do + base_api.get_error + + expect(base_api.error).to be_nil + end + end + + describe "#verify_token" do + it "returns true when auth token is present" do + expect(base_api.send(:verify_token)).to eq(true) + end + + it "returns false when auth token is nil or empty" do + base_api.instance_variable_set(:@auth_token, nil) + expect(base_api.send(:verify_token)).to eq(false) + end + end + + describe "#verify_user_code" do + it "returns true when user_code is present" do + expect(base_api.send(:verify_user_code)).to eq(true) + end + + it "returns false when user_code is nil or empty" do + base_api.instance_variable_set(:@user_code, nil) + expect(base_api.send(:verify_user_code)).to eq(false) + end + end + + describe "#verify_env_vars" do + before(:each) do + ENV["BUFFER_KEY"] = "a value" + ENV["BUFFER_SECRET"] = "a value" + end + + it "returns true when all env vars are present" do + expect(base_api.send(:verify_env_vars)).to eq(true) + end + + it "returns false when BUFFER_KEY is nil or empty" do + ENV["BUFFER_KEY"] = nil + expect(base_api.send(:verify_env_vars)).to eq(false) + end + + it "returns false when BUFFER_SECRET is nil or empty" do + ENV["BUFFER_SECRET"] = nil + expect(base_api.send(:verify_env_vars)).to eq(false) + end + end +end diff --git a/spec/lib/api/profile_api_spec.rb b/spec/lib/api/profile_api_spec.rb new file mode 100644 index 0000000..2f3b4e6 --- /dev/null +++ b/spec/lib/api/profile_api_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../../lib/api/profile_api.rb' + +describe ProfileApi do + let(:profile_api) { build(:profile_api) } + let(:success_json) { {"success" => "true"} } + let(:id) { "123456789" } + let(:days) { ["mon", "tue"] } + let(:good_sched) do + [{ + :days => days, + :times => ["00:01", "12:23"] + }] + end + + let(:bad_sched) do + [{ + :days => days + }] + end + + before(:each) do + allow_any_instance_of(ProfileApi).to receive(:verify_token).and_return(true) + allow_any_instance_of(ProfileApi).to receive(:get_post_response).and_return(success_json) + end + + describe "#update_schedule" do + it "raises an exception when the schedule is improperly defined" do + expect { profile_api.update_schedule(id, bad_sched) }.to raise_error + end + + it "returns succes with JSON response when schedule is properly defined" do + expect(profile_api.update_schedule(id, good_sched)).to eq(success_json) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..bc360b2 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,23 @@ +require "codeclimate-test-reporter" +CodeClimate::TestReporter.start + +require 'webmock/rspec' +require 'factory_girl' + +RSpec.configure do |config| + WebMock.disable_net_connect!(:allow => "codeclimate.com") + + config.order = 'random' + + # FactoryGirl + FactoryGirl.find_definitions + config.include FactoryGirl::Syntax::Methods + + config.before(:suite) do + begin + FactoryGirl.lint + ensure + # + end + end +end From 294ea86abc2dac9027b9c6f1bf6dfd8273f77f29 Mon Sep 17 00:00:00 2001 From: kaneda Date: Sat, 18 Jul 2015 10:46:52 -0400 Subject: [PATCH 20/24] [dev-rspecs-2] Updates - Fixed docs - Added missing methods to buffer client - Add deauth to user API - Added new specs - Updated readme --- lib/api/helpers/api_helpers.rb | 12 +- lib/api/user_api.rb | 8 +- lib/buffer_client.rb | 21 +++- readme.md | 80 ++++++------- spec/lib/api/helpers/api_helpers_spec.rb | 137 +++++++++++++++++++++++ spec/lib/api/update_api_spec.rb | 25 +++++ 6 files changed, 236 insertions(+), 47 deletions(-) create mode 100644 spec/lib/api/helpers/api_helpers_spec.rb create mode 100644 spec/lib/api/update_api_spec.rb diff --git a/lib/api/helpers/api_helpers.rb b/lib/api/helpers/api_helpers.rb index e99a511..c28471c 100644 --- a/lib/api/helpers/api_helpers.rb +++ b/lib/api/helpers/api_helpers.rb @@ -38,12 +38,12 @@ def has_data?(response) response.present? && response.body.present? end - def set_err(json_response) + def set_err(response_code, json_response) if json_response.present? if json_response["error"].present? @error = json_response["error"] elsif json_response["code"].present? - @error = get_error_message(response.code, json_response["code"]) + @error = get_error_message(response_code, json_response["code"]) else @error = DEFAULT_ERR end @@ -58,13 +58,15 @@ def parse_data(response) begin json_response = JSON.parse(response.body) rescue => e - Rails.logger.error "Failed to parse JSON return from Buffer: #{e}" + msg = "Failed to parse JSON return from Buffer: #{e}" + log_or_print msg + @error = msg end - if response.code == GOOD_RESPONSE + if response.code == GOOD_RESPONSE || json_response.nil? return json_response else - set_err(json_response) + set_err(response.code, json_response) end end diff --git a/lib/api/user_api.rb b/lib/api/user_api.rb index b9cabe7..2f420ca 100644 --- a/lib/api/user_api.rb +++ b/lib/api/user_api.rb @@ -3,7 +3,8 @@ class UserApi < BaseApi # PATHS - USER_PATH = "user.json" + USER_PATH = "user.json" + DEAUTH_PATH = "user/deauthorize.json" def get_user_id get_user_json["id"] rescue nil @@ -13,4 +14,9 @@ def get_user_json return nil unless verify_token get_get_response( build_url(USER_PATH) ) end + + def deauthorize + return nil unless verify_token + get_get_response( build_url(DEAUTH_PATH) ) + end end diff --git a/lib/buffer_client.rb b/lib/buffer_client.rb index 6fa50c0..ec5d2b8 100644 --- a/lib/buffer_client.rb +++ b/lib/buffer_client.rb @@ -82,6 +82,13 @@ def get_user_json user_json end + def deauthorize + success_json = @user_api.deauthorize + record_err(@user_api) + + is_success?(success_json) + end + ############### # PROFILE API # ############### @@ -108,6 +115,7 @@ def get_schedule(id) end def update_schedule(id, sched_array) + sched_array.deep_symbolize_keys! success_json = @profile_api.update_schedule(id, sched_array) record_err(@profile_api) @@ -153,7 +161,14 @@ def reorder_updates(id, updates_array, options = {}) new_order_json = @update_api.reorder_updates(id, updates_array, options) record_err(@update_api) - new_order_json + extract_key(new_order_json, "updates") + end + + def shuffle_updates(id, options = {}) + shuffle_json = @update_api.shuffle_updates(id, options) + record_err(@update_api) + + extract_key(shuffle_json, "updates") end def create_update(profile_ids, options = {}) @@ -229,4 +244,8 @@ def is_success?(success_json) def record_err(api) @error = api.get_error if api.has_error? end + + def extract_key(json, key) + return json[key] if json.present? + end end diff --git a/readme.md b/readme.md index 38abf9f..23adfcc 100644 --- a/readme.md +++ b/readme.md @@ -1,24 +1,23 @@ # Buffer Gem -Modelled after Octokit <3. This is a lightweight client for the Buffer API (https://buffer.com/developers/api), which is intended to return JSON as opposed to objects. +Modelled after [Octokit](https://github.com/octokit/octokit.rb) <3, this is a lightweight client for the [Buffer API](https://buffer.com/developers/api), which is intended to return (mostly) JSON as opposed to objects. [![Code Climate](https://codeclimate.com/github/kaneda/buffer-ruby/badges/gpa.svg)](https://codeclimate.com/github/kaneda/buffer-ruby) [![Test Coverage](https://codeclimate.com/github/kaneda/buffer-ruby/badges/coverage.svg)](https://codeclimate.com/github/kaneda/buffer-ruby/coverage) +[![Build Status](https://travis-ci.org/kaneda/buffer-ruby.svg?branch=master)](https://travis-ci.org/kaneda/buffer-ruby) ## Client API ### What's Imlemented? -* https://buffer.com/developers/api/oauth -* https://buffer.com/developers/api/user -* https://buffer.com/developers/api/profiles -* https://buffer.com/developers/api/updates -* https://buffer.com/developers/api/links -* https://buffer.com/developers/api/info -* https://buffer.com/developers/api/errors - - -### What's Missing? -* Rspecs +* [OAuth](https://buffer.com/developers/api/oauth) +* [Users](https://buffer.com/developers/api/user) +* [Profiles](https://buffer.com/developers/api/profiles) +* [Updates](https://buffer.com/developers/api/updates) +* [Links](https://buffer.com/developers/api/links) +* [Info](https://buffer.com/developers/api/info) +* [Errors](https://buffer.com/developers/api/errors) +* Automated builds through Travis-CI +* Automated tests using RSpec ### Installing @@ -45,39 +44,39 @@ You can define the client up front or configure it later. The options array the buffer_client = BufferClient.new({ :user_code => "yourcode" }) -``` -```ruby -buffer_client = BufferClient.new +auth_tok = buffer_client.get_auth_token buffer_client.configure({ - :user_code => "yourcode" + :auth_token => auth_tok }) ``` ### Available Calls -| Client call | Input | Description | Notes -| :---------: | :------ | :---- | :--------- -| get_auth_token | | Returns the user's long-lasting auth token | user_code must be defined in the buffer_client, as well as the ENV variables "BUFFER_KEY" and "BUFFER_SECRET" | -| get_user_id | | Returns the user's Buffer ID | | -| get_user_json | | Returns the entirety of the user JSON | | -| get_user_profiles | | Returns the entirety of the profile JSON | | -| get_user_profile | Profile ID | Get a single profile as JSON by ID | | -| get_schedule | Profile ID | Get the schedule of a profile as JSON by ID | | -| update_schedule | Profile ID, Schedule Hash | Set the schedule of a profile as JSON by ID | auth_token must be defined in the buffer client. See below for schedule representation | -| get_update | Social Media Post ID | Gets an update by post ID | | -| get_pending_updates | Profile ID, Options Hash (optional) | Gets pending updates as JSON by profile ID | Takes in hash of options, see Buffer API docs for optional parameters | -| get_sent_updates | Profile ID, Options Hash (optional) | Gets sent updates as JSON by profile ID | Takes in hash of options, see Buffer API docs for optional parameters | -| get_interactions | Social Media Post ID, Event, Options Hash (optional) | Gets interactions based on event type (see https://bufferapp.com/developers/api/info#configuration) | Takes in a hash of options, see Buffer API docs for optional parameters | -| reorder_updates | Profile ID, Updates Array, Options Hash (optional) | Updates order of updates in a profile based on updates array | | -| shuffle_updates | Profile ID, Options Hash (optional) | Randomize the order of updates to be sent | | -| create_update | Profile ID Array, Options Hash (optional) | Create a new post | Note that for the "media" option, please specify each media option in the hash separately, e.g. ```{ "media[link]" => "http%3A%2F%2Fgoogle.com", "media[description]" => "The%20google%20homepage" }``` | -| update_status | Social Media Post ID, Text, Options Hash (optional) | Update an existing status | For the "media" option see the note on create_update | -| share_update | Social Media Post ID | Share a post immediately | | -| destroy_update | Social Media Post ID| Permanently destroy an update | | -| move_to_top | Social Media Post ID| Move post to top of queue | | -| get_shares | URL (unencoded) | Gets the number of shares for a given URL through Buffer | You can pass a normal URL here, the client will encode it. This is one of the only calls to not require an auth_token | -| get_configuration | | Gets the current Buffer config | | + +| Client call | Input | Output | Notes +| :---------: | :------ | :------ | :--------- +| [get_auth_token](https://buffer.com/developers/api/oauth) | | Auth Token:String | ENV variables "BUFFER_KEY" and "BUFFER_SECRET" must be defined here | +| get_user_id | | ID:String | Convenience wrapper, calls get_user_json under the hood | +| [get_user_json](https://buffer.com/developers/api/user#user) | | User:Hash | | +| [deauthorize](https://buffer.com/developers/api/user#deauthorize) | | Success:Boolean | | +| [get_user_profiles](https://buffer.com/developers/api/profiles#profiles) | | Profiles:Array | | +| [get_user_profile](https://buffer.com/developers/api/profiles#profilesid) | Profile ID:String | Profile:Hash | | +| [get_schedule](https://buffer.com/developers/api/profiles#schedules) | Profile ID:String | Schedule:Hash | | +| [update_schedule](https://buffer.com/developers/api/profiles#schedulesupdate) | Profile ID:String, Schedule:Hash | Success:Boolean | See below for schedule representation | +| [get_update](https://buffer.com/developers/api/updates#updatesid) | Social Media Post ID:String | Update:Hash | | +| [get_pending_updates](https://buffer.com/developers/api/updates#updatespending) | Profile ID:String, Options:Hash (optional) | UpdateResult:Hash | See Buffer API doc (link to the left) for optional parameters. "updates" key contains the Updates:Array | +| [get_sent_updates](https://buffer.com/developers/api/updates#updatessent) | Profile ID:String, Options:Hash (optional) | UpdateResult:Hash | See Buffer API doc (link to the left) for optional parameters. "updates" key contains the Updates:Array | +| [get_interactions](https://buffer.com/developers/api/updates#updatesinteractions) | Social Media Post ID:String, Event:ENUM, Options:Hash (optional) | Interactions:Hash | See [event types](https://bufferapp.com/developers/api/info#configuration) for possible event values and Buffer API doc (link to the left) for optional parameters. "interactions" key contains the Interactions:Array | +| [reorder_updates](https://buffer.com/developers/api/updates#updatesreorder) | Profile ID:String, Updates:Array, Options:Hash (optional) | Updates:Array | | +| [shuffle_updates](https://buffer.com/developers/api/updates#updatesshuffle) | Profile ID:String, Options:Hash (optional) | Updates:Array | | +| [create_update](https://buffer.com/developers/api/updates#updatescreate) | Profile IDs:Array, Options:Hash (optional) | Update:Hash | Note that for the "media" option, please specify each media option in the hash separately, e.g. ```{ "media[link]" => "http%3A%2F%2Fgoogle.com", "media[description]" => "The%20google%20homepage" }```. See all available options in the Buffer docs (link to the left) | +| [update_status](https://buffer.com/developers/api/updates#updatesupdate) | Social Media Post ID:String, Text:String, Options:Hash (optional) | Update:Hash | For the "media" option see the note on create_update above | +| [share_update](https://buffer.com/developers/api/updates#updatesshare) | Social Media Post ID:String | Success:Boolean | | +| [destroy_update](https://buffer.com/developers/api/updates#updatesdestroy) | Social Media Post ID:String | Success:Boolean | | +| [move_to_top](https://buffer.com/developers/api/updates#updatesmovetotop) | Social Media Post ID:String | Success:Boolean | | +| [get_shares](https://buffer.com/developers/api/links#shares) | Unencodded URL:String | Shares:Integer | You can pass a normal URL here, the client will encode it. This is one of the only calls to not require an auth_token | +| [get_configuration](https://buffer.com/developers/api/info#configuration) | | Configuration:Hash | "services" key has internal keys for each service | ### Helper methods | Method | Description | @@ -104,6 +103,7 @@ To update a schedule the BufferClient is expecting a schedule of the form: ] ``` +Note that you can make the keys symbols or strings, the client will accept either one. ## Contributing To contribute simply: @@ -111,13 +111,13 @@ To contribute simply: 1. Fork this project 2. Make your changes in a new branch 3. Create a PR -4. Once approved squash your commits (http://davidwalsh.name/squash-commits-git) +4. Once approved [squash your commits](http://davidwalsh.name/squash-commits-git) 5. Party ## Contact Email: kanedasan@gmail.com -Twitter: @kanedasan +Twitter: [@kanedasan](https://twitter.com/kanedasan) IRC: kaneda^ on FreeNode (##hackers) diff --git a/spec/lib/api/helpers/api_helpers_spec.rb b/spec/lib/api/helpers/api_helpers_spec.rb new file mode 100644 index 0000000..d444d63 --- /dev/null +++ b/spec/lib/api/helpers/api_helpers_spec.rb @@ -0,0 +1,137 @@ +require 'action_dispatch' +require 'uri' +require_relative '../../../../lib/api/helpers/api_helpers.rb' + +describe ApiHelpers do + let(:base_api) { build(:base_api) } + let(:options) do + { + :key => "value", + :key_2 => "value_2" + } + end + + let(:err) { "An error" } + + describe "#build_options_string" do + let(:path_regex) do + /\A(\w+=[\w\d]+(&\w+=[\w\d]+)+)*\Z/ + end + + it "returns a string representing a valid path" do + expect(base_api.build_options_string(options)).to match(path_regex) + end + end + + describe "#build_url" do + let(:path) { "some_path" } + let(:auth_tok) { "123456789" } + + it "returns a valid URL when given only a path" do + expect(base_api.build_url(path, {}, false)).to match(URI::regexp) + end + + it "returns a valid URL when given a path and options" do + expect(base_api.build_url(path, options, false)).to match(URI::regexp) + end + + it "returns a valid URL when given a path and an auth token" do + base_api.instance_variable_set(:@auth_token, auth_tok) + + expect(base_api.build_url(path)).to match(URI::regexp) + end + + it "returns a valid URL when given a path, options, and an auth token" do + base_api.instance_variable_set(:@auth_token, auth_tok) + + expect(base_api.build_url(path, options)).to match(URI::regexp) + end + end + + describe "#set_err" do + let(:response_code) { "403" } + let(:err) { "An error" } + let(:err_2) { "A second err" } + let(:json_with_error) { { "error" => err } } + let(:json_with_code) { { "code" => "1001" } } + let(:json_with_other) { { "other" => "stuff" } } + + it "returns the default error when response is blank" do + expect(base_api.set_err(response_code, {})).to eq(ApiHelpers::DEFAULT_ERR) + end + + it "returns an error when the error key is present in the response" do + expect(base_api.set_err(response_code, json_with_error)).to eq(err) + end + + it "returns another error when only the code key is present" do + allow_any_instance_of(BaseApi).to receive(:get_error_message).and_return(err_2) + expect(base_api.set_err(response_code, json_with_code)).to eq(err_2) + end + + it "returns the default error when response is present but neither error nor code is present" do + expect(base_api.set_err(response_code, json_with_other)).to eq(ApiHelpers::DEFAULT_ERR) + end + end + + describe "#has_data?" do + let(:empty_response) do + ActionDispatch::Response.new(500, {}, "") + end + + let(:valid_response) do + ActionDispatch::Response.new(200, {}, "A body") + end + + it "returns false when passed a blank response" do + expect(base_api.has_data?(empty_response)).to eq(false) + end + + it "returns true when passed a response with a body" do + expect(base_api.has_data?(valid_response)).to eq(true) + end + end + + describe "#parse_data" do + let(:bad_json) { '{"foo": "bar"' } + let(:bad_json_err) { "Failed to parse JSON return from Buffer" } + let(:bad_response) do + ActionDispatch::Response.new(403, {}, bad_json) + end + + let(:good_json) { "#{bad_json} }" } + let(:good_response) do + ActionDispatch::Response.new(200, {}, good_json) + end + + it "returns nil and sets @error if the response is invalid JSON" do + json = base_api.parse_data(bad_response) + expect(json).to be_nil + expect(base_api.error).to include(bad_json_err) + end + + it "returns a hash when passed good JSON" do + json = base_api.parse_data(good_response) + expect(json).to be_present + expect(json.is_a?(Hash)).to eq(true) + end + end + + describe "#get_error_message" do + let(:event_err) { "Event type not supported." } + let(:http_code) { "400" } + let(:err_code) { "1029" } + + it "returns the event err when the given codes are passed" do + expect(base_api.get_error_message(http_code, err_code)).to eq(event_err) + end + + it "returns the default error when the http code is unknown" do + expect(base_api.get_error_message("10101", err_code)).to eq(ApiHelpers::DEFAULT_ERR) + end + + it "returns the default error when the error code is unknown" do + expect(base_api.get_error_message(http_code, "10101")).to eq(ApiHelpers::DEFAULT_ERR) + end + end +end diff --git a/spec/lib/api/update_api_spec.rb b/spec/lib/api/update_api_spec.rb new file mode 100644 index 0000000..b9f8d9b --- /dev/null +++ b/spec/lib/api/update_api_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../lib/api/update_api.rb' + +describe UpdateApi do + let(:update_api) { build(:update_api) } + let(:id) { "123456789" } + let(:path) { "some_path" } + + before(:each) do + allow_any_instance_of(UpdateApi).to receive(:verify_token).and_return(true) + end + + describe "#build_profile_url" do + it "returns a proper profile URL" do + expected_val = "#{UpdateApi::PROFILE_PATH}/#{id}/#{path}" + expect(update_api.send(:build_profile_url, id, path)).to include(expected_val) + end + end + + describe "#build_update_url" do + it "returns a proper update URL" do + expected_val = "#{UpdateApi::UPDATE_PATH}/#{id}/#{path}" + expect(update_api.send(:build_update_url, id, path)).to include(expected_val) + end + end +end From a3df6775777c58248f9bfe0436c30f93a8ba3694 Mon Sep 17 00:00:00 2001 From: kaneda Date: Sat, 18 Jul 2015 18:08:38 -0400 Subject: [PATCH 21/24] [dev-rspecs-3] Added more coverage - Full coverage of api helpers - 1.9.3 fix - Added placeholder specs to ensure that logic doesn't get broken, however simple --- lib/api/helpers/api_helpers.rb | 18 ++++- readme.md | 6 ++ spec/lib/api/helpers/api_helpers_spec.rb | 69 ++++++++++++++++++- spec/lib/api/info_api_spec.rb | 19 ++++++ spec/lib/api/link_api_spec.rb | 19 ++++++ spec/lib/api/profile_api_spec.rb | 33 ++++++++- spec/lib/api/update_api_spec.rb | 85 ++++++++++++++++++++++++ spec/lib/api/user_api_spec.rb | 25 +++++++ 8 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 spec/lib/api/info_api_spec.rb create mode 100644 spec/lib/api/link_api_spec.rb create mode 100644 spec/lib/api/user_api_spec.rb diff --git a/lib/api/helpers/api_helpers.rb b/lib/api/helpers/api_helpers.rb index c28471c..b8e72e0 100644 --- a/lib/api/helpers/api_helpers.rb +++ b/lib/api/helpers/api_helpers.rb @@ -71,7 +71,9 @@ def parse_data(response) end def get_http_obj(url) - uri = URI.parse(url) + uri = URI.parse(url) + raise URI::InvalidURIError if uri.host.nil? + http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE @@ -82,7 +84,12 @@ def get_http_obj(url) def get_get_response(url) uri, http = get_http_obj(url) - req = Net::HTTP::Get.new(uri) + req = begin + Net::HTTP::Get.new(uri) + rescue + # Fall back in case of Ruby 1.9.3 + Net::HTTP::Get.new(uri.request_uri) + end response = http.request(req) parse_data(response) @@ -91,7 +98,12 @@ def get_get_response(url) def get_post_response(url, post_data = "") uri, http = get_http_obj(url) - req = Net::HTTP::Post.new(uri) + req = begin + Net::HTTP::Post.new(uri) + rescue + # Fall back in case of Ruby 1.9.3 + Net::HTTP::Post.new(uri.request_uri) + end req.content_type = "application/x-www-form-urlencoded" req.body = post_data response = http.request(req) diff --git a/readme.md b/readme.md index 23adfcc..eaf8bb9 100644 --- a/readme.md +++ b/readme.md @@ -6,6 +6,12 @@ Modelled after [Octokit](https://github.com/octokit/octokit.rb) <3, this is a li [![Test Coverage](https://codeclimate.com/github/kaneda/buffer-ruby/badges/coverage.svg)](https://codeclimate.com/github/kaneda/buffer-ruby/coverage) [![Build Status](https://travis-ci.org/kaneda/buffer-ruby.svg?branch=master)](https://travis-ci.org/kaneda/buffer-ruby) +## Ruby Versions + +This gem is built to work with Ruby 1.9.3+ + +See the Travis-CI build information above for build status on 1.9.3 and 2.1.2 + ## Client API ### What's Imlemented? diff --git a/spec/lib/api/helpers/api_helpers_spec.rb b/spec/lib/api/helpers/api_helpers_spec.rb index d444d63..3a999cf 100644 --- a/spec/lib/api/helpers/api_helpers_spec.rb +++ b/spec/lib/api/helpers/api_helpers_spec.rb @@ -11,7 +11,12 @@ } end + let(:good_hash) { options } + let(:err) { "An error" } + let(:good_url) { "http://jbegleiter.com" } + let(:bad_json) { '{"foo": "bar"' } + let(:good_json) { "#{bad_json} }" } describe "#build_options_string" do let(:path_regex) do @@ -50,7 +55,6 @@ describe "#set_err" do let(:response_code) { "403" } - let(:err) { "An error" } let(:err_2) { "A second err" } let(:json_with_error) { { "error" => err } } let(:json_with_code) { { "code" => "1001" } } @@ -93,13 +97,11 @@ end describe "#parse_data" do - let(:bad_json) { '{"foo": "bar"' } let(:bad_json_err) { "Failed to parse JSON return from Buffer" } let(:bad_response) do ActionDispatch::Response.new(403, {}, bad_json) end - let(:good_json) { "#{bad_json} }" } let(:good_response) do ActionDispatch::Response.new(200, {}, good_json) end @@ -134,4 +136,65 @@ expect(base_api.get_error_message(http_code, "10101")).to eq(ApiHelpers::DEFAULT_ERR) end end + + describe "#get_http_obj" do + let(:bad_url) { "foo" } + + it "raises an error when passed an invalid URL" do + expect { base_api.get_http_obj(bad_url) }.to raise_error + end + + it "returns an array of URI, HTTP object when passed a valid URL" do + uri, http = base_api.get_http_obj(good_url) + expect(uri).to be_present + expect(http).to be_present + end + end + + # Test that these don't blow up keeping in mind that + # get_http_obj and parse_data are tested above + describe "#get_functions" do + before(:each) do + # Stubbed, as this is tested above + allow_any_instance_of(BaseApi).to receive(:parse_data).and_return(good_hash) + + # Since we're stubbing parse it doesn't matter what this returns + allow_any_instance_of(Net::HTTP).to receive(:request).and_return({}) + end + + describe "#get_post_response" do + let(:post_data) { "foo=bar&boz=bot" } + + it "doesn't explode when post data is set" do + expect(base_api.get_post_response(good_url, post_data)).to eq(good_hash) + end + + it "doesn't explode when post data isn't set" do + expect(base_api.get_post_response(good_url)).to eq(good_hash) + end + end + + describe "#get_get_response" do + it "doesn't explode" do + expect(base_api.get_get_response(good_url)).to eq(good_hash) + end + end + end + + describe "#log_or_print" do + let(:msg) { "A message" } + let(:log_level) { "info" } + + it "prints to the logger when logger is defined" do + logger = Logger.new(STDOUT) + base_api.instance_variable_set(:@logger, logger) + + expect(logger).to receive(:send).with(log_level, msg) + base_api.log_or_print(msg, log_level) + end + + it "prints to STDOUT when logger is not defined" do + expect { base_api.log_or_print(msg, log_level) }.to output(/#{msg}(\n)?/).to_stdout + end + end end diff --git a/spec/lib/api/info_api_spec.rb b/spec/lib/api/info_api_spec.rb new file mode 100644 index 0000000..cf185ca --- /dev/null +++ b/spec/lib/api/info_api_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../lib/api/info_api.rb' + +describe InfoApi do + let(:info_api) { build(:info_api) } + let(:success_json) { {"info" => "info_and_stuff"} } + + before(:each) do + allow_any_instance_of(InfoApi).to receive(:verify_token).and_return(true) + allow_any_instance_of(InfoApi).to receive(:get_get_response).and_return(success_json) + end + + # Make sure this doesn't blow up, keeping in mind that + # that get_get_response is tested in the ApiHelpers spec + describe "#get_configuration" do + it "returns configuration JSON" do + expect(info_api.get_configuration).to eq(success_json) + end + end +end diff --git a/spec/lib/api/link_api_spec.rb b/spec/lib/api/link_api_spec.rb new file mode 100644 index 0000000..6f27642 --- /dev/null +++ b/spec/lib/api/link_api_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../lib/api/link_api.rb' + +describe LinkApi do + let(:link_api) { build(:link_api) } + let(:success_json) { {"shares" => 123456} } + let(:url) { "http%3A%2F%2Fjbegleiter.com" } + + before(:each) do + allow_any_instance_of(LinkApi).to receive(:get_get_response).and_return(success_json) + end + + # Make sure this doesn't blow up, keeping in mind that + # that get_get_response is tested in the ApiHelpers spec + describe "#get_shares" do + it "returns share JSON" do + expect(link_api.get_shares(url)).to eq(success_json) + end + end +end diff --git a/spec/lib/api/profile_api_spec.rb b/spec/lib/api/profile_api_spec.rb index 2f3b4e6..c3f2968 100644 --- a/spec/lib/api/profile_api_spec.rb +++ b/spec/lib/api/profile_api_spec.rb @@ -20,10 +20,13 @@ before(:each) do allow_any_instance_of(ProfileApi).to receive(:verify_token).and_return(true) - allow_any_instance_of(ProfileApi).to receive(:get_post_response).and_return(success_json) end describe "#update_schedule" do + before(:each) do + allow_any_instance_of(ProfileApi).to receive(:get_post_response).and_return(success_json) + end + it "raises an exception when the schedule is improperly defined" do expect { profile_api.update_schedule(id, bad_sched) }.to raise_error end @@ -32,4 +35,32 @@ expect(profile_api.update_schedule(id, good_sched)).to eq(success_json) end end + + # Make sure these don't blow up, keeping in mind that + # that get_get_response is tested in the ApiHelpers spec + describe "#gets" do + let(:id) { "123456789" } + + before(:each) do + allow_any_instance_of(ProfileApi).to receive(:get_get_response).and_return(success_json) + end + + describe "#get_profiles" do + it "doesn't blow up on invocation" do + expect(profile_api.get_profiles).to eq(success_json) + end + end + + describe "#get_profile" do + it "doesn't blow up on invocation" do + expect(profile_api.get_profile(id)).to eq(success_json) + end + end + + describe "#get_schedule" do + it "doesn't blow up on invocation" do + expect(profile_api.get_schedule(id)).to eq(success_json) + end + end + end end diff --git a/spec/lib/api/update_api_spec.rb b/spec/lib/api/update_api_spec.rb index b9f8d9b..0858a81 100644 --- a/spec/lib/api/update_api_spec.rb +++ b/spec/lib/api/update_api_spec.rb @@ -4,6 +4,7 @@ let(:update_api) { build(:update_api) } let(:id) { "123456789" } let(:path) { "some_path" } + let(:return_json) { { :key => "value" } } before(:each) do allow_any_instance_of(UpdateApi).to receive(:verify_token).and_return(true) @@ -22,4 +23,88 @@ expect(update_api.send(:build_update_url, id, path)).to include(expected_val) end end + + # Make sure these don't blow up, keeping in mind that + # that get_get_response is tested in the ApiHelpers spec + describe "#gets" do + before(:each) do + allow_any_instance_of(UpdateApi).to receive(:get_get_response).and_return(return_json) + end + + describe "#get_update" do + it "doesn't blow up when invoked" do + expect(update_api.get_update(id)).to eq(return_json) + end + end + + describe "#get_pending_updates" do + it "doesn't blow up when invoked" do + expect(update_api.get_pending_updates(id)).to eq(return_json) + end + end + + describe "#get_sent_updates" do + it "doesn't blow up when invoked" do + expect(update_api.get_sent_updates(id)).to eq(return_json) + end + end + + describe "#get_interactions" do + it "doesn't blow up when invoked" do + expect(update_api.get_interactions(id)).to eq(return_json) + end + end + end + + describe "#posts" do + before(:each) do + allow_any_instance_of(UpdateApi).to receive(:get_post_response).and_return(return_json) + end + + describe "#reorder_updates" do + let(:updates_array) { [ id, id, id ] } + + it "doesn't blow up when invoked" do + expect(update_api.reorder_updates(id, updates_array)).to eq(return_json) + end + end + + describe "#shuffle_updates" do + it "doesn't blow up when invoked" do + expect(update_api.shuffle_updates(id)).to eq(return_json) + end + end + + describe "#create_update" do + let(:profile_ids) { [ id, id, id ] } + + it "doesn't blow up when invoked" do + expect(update_api.create_update(profile_ids)).to eq(return_json) + end + end + + describe "#update_status" do + it "doesn't blow up when invoked" do + expect(update_api.update_status(id)).to eq(return_json) + end + end + + describe "#share_update" do + it "doesn't blow up when invoked" do + expect(update_api.share_update(id)).to eq(return_json) + end + end + + describe "#destroy_update" do + it "doesn't blow up when invoked" do + expect(update_api.destroy_update(id)).to eq(return_json) + end + end + + describe "#move_to_top" do + it "doesn't blow up when invoked" do + expect(update_api.move_to_top(id)).to eq(return_json) + end + end + end end diff --git a/spec/lib/api/user_api_spec.rb b/spec/lib/api/user_api_spec.rb new file mode 100644 index 0000000..fc3e7fa --- /dev/null +++ b/spec/lib/api/user_api_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../../lib/api/user_api.rb' + +describe UserApi do + let(:user_api) { build(:user_api) } + let(:success_json) { {"key" => "value"} } + + before(:each) do + allow_any_instance_of(UserApi).to receive(:verify_token).and_return(true) + allow_any_instance_of(UserApi).to receive(:get_get_response).and_return(success_json) + end + + # Make sure these don't blow up, keeping in mind that + # that get_get_response is tested in the ApiHelpers spec + describe "#get_user_json" do + it "returns user JSON" do + expect(user_api.get_user_json).to eq(success_json) + end + end + + describe "#deauthorize" do + it "returns success JSON" do + expect(user_api.deauthorize).to eq(success_json) + end + end +end From 0f56f7a86e0e42717a4ca63f5cdb5a4a8b6e6bcc Mon Sep 17 00:00:00 2001 From: kaneda Date: Sat, 18 Jul 2015 22:25:33 -0400 Subject: [PATCH 22/24] Roll version --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index eaf8bb9..e09b890 100644 --- a/readme.md +++ b/readme.md @@ -30,7 +30,7 @@ See the Travis-CI build information above for build status on 1.9.3 and 2.1.2 Put this sucker in your Gemfile and bundle install ```ruby -gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :tag => "v1.1" +gem "buffer-app", :git => "git://github.com/kaneda/buffer-ruby", :tag => "v1.2" ``` Then drop this into your config/application.rb (or wherever you want to use it) From 50cbfce495e08b06b98b2726605aa78fd75fa021 Mon Sep 17 00:00:00 2001 From: kaneda Date: Sun, 19 Jul 2015 22:36:01 -0400 Subject: [PATCH 23/24] Updated readme --- readme.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index e09b890..1def464 100644 --- a/readme.md +++ b/readme.md @@ -6,6 +6,19 @@ Modelled after [Octokit](https://github.com/octokit/octokit.rb) <3, this is a li [![Test Coverage](https://codeclimate.com/github/kaneda/buffer-ruby/badges/coverage.svg)](https://codeclimate.com/github/kaneda/buffer-ruby/coverage) [![Build Status](https://travis-ci.org/kaneda/buffer-ruby.svg?branch=master)](https://travis-ci.org/kaneda/buffer-ruby) +## Table of Contents + +* [Ruby Versions](https://github.com/kaneda/buffer-ruby#ruby-versions) +* [Client API](https://github.com/kaneda/buffer-ruby#client-api) +** [What's Implemented](https://github.com/kaneda/buffer-ruby#whats-imlemented) +** [Installing](https://github.com/kaneda/buffer-ruby#installing) +** [Basic Usage](https://github.com/kaneda/buffer-ruby#basic-usage) +** [Available Calls](https://github.com/kaneda/buffer-ruby#available-calls) +** [Helper Methods](https://github.com/kaneda/buffer-ruby#helper-methods) +** [Defining a Schedule](https://github.com/kaneda/buffer-ruby#defining-a-schedule) +* [Contributing](https://github.com/kaneda/buffer-ruby#contributing) +* [Contact](https://github.com/kaneda/buffer-ruby#contact) + ## Ruby Versions This gem is built to work with Ruby 1.9.3+ @@ -56,6 +69,8 @@ auth_tok = buffer_client.get_auth_token buffer_client.configure({ :auth_token => auth_tok }) + +# Subsequent calls here (see below) ``` ### Available Calls @@ -84,7 +99,7 @@ buffer_client.configure({ | [get_shares](https://buffer.com/developers/api/links#shares) | Unencodded URL:String | Shares:Integer | You can pass a normal URL here, the client will encode it. This is one of the only calls to not require an auth_token | | [get_configuration](https://buffer.com/developers/api/info#configuration) | | Configuration:Hash | "services" key has internal keys for each service | -### Helper methods +### Helper Methods | Method | Description | | :---------: | :----- | | configure | Takes in a hash (as above) and reconfigures all API objects | @@ -93,7 +108,7 @@ buffer_client.configure({ | error | Returns the current error, leaving it in tact | -### Defining a schedule +### Defining a Schedule To update a schedule the BufferClient is expecting a schedule of the form: @@ -110,6 +125,7 @@ To update a schedule the BufferClient is expecting a schedule of the form: ``` Note that you can make the keys symbols or strings, the client will accept either one. + ## Contributing To contribute simply: @@ -120,7 +136,7 @@ To contribute simply: 4. Once approved [squash your commits](http://davidwalsh.name/squash-commits-git) 5. Party -## Contact +## Contact Me Email: kanedasan@gmail.com From 5b0513fd35a6d02a41cf9a1fdee98cc935b41198 Mon Sep 17 00:00:00 2001 From: kaneda Date: Sun, 19 Jul 2015 22:36:41 -0400 Subject: [PATCH 24/24] Updated readme --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 1def464..f0ecde3 100644 --- a/readme.md +++ b/readme.md @@ -10,12 +10,12 @@ Modelled after [Octokit](https://github.com/octokit/octokit.rb) <3, this is a li * [Ruby Versions](https://github.com/kaneda/buffer-ruby#ruby-versions) * [Client API](https://github.com/kaneda/buffer-ruby#client-api) -** [What's Implemented](https://github.com/kaneda/buffer-ruby#whats-imlemented) -** [Installing](https://github.com/kaneda/buffer-ruby#installing) -** [Basic Usage](https://github.com/kaneda/buffer-ruby#basic-usage) -** [Available Calls](https://github.com/kaneda/buffer-ruby#available-calls) -** [Helper Methods](https://github.com/kaneda/buffer-ruby#helper-methods) -** [Defining a Schedule](https://github.com/kaneda/buffer-ruby#defining-a-schedule) + * [What's Implemented](https://github.com/kaneda/buffer-ruby#whats-imlemented) + * [Installing](https://github.com/kaneda/buffer-ruby#installing) + * [Basic Usage](https://github.com/kaneda/buffer-ruby#basic-usage) + * [Available Calls](https://github.com/kaneda/buffer-ruby#available-calls) + * [Helper Methods](https://github.com/kaneda/buffer-ruby#helper-methods) + * [Defining a Schedule](https://github.com/kaneda/buffer-ruby#defining-a-schedule) * [Contributing](https://github.com/kaneda/buffer-ruby#contributing) * [Contact](https://github.com/kaneda/buffer-ruby#contact)