aftalk 0.1.0

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.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ [![Build](https://circleci.com/gh/thriveglobal/aftalk.svg?&style=shield&circle-token=ade67468e07185e650dc6ba100127bce34bd10b5)](https://circleci.com/gh/thriveglobal/aftalk)
2
+ [![Maintainability](https://api.codeclimate.com/v1/badges/8d84a0d4fc06f1c49cd8/maintainability)](https://codeclimate.com/github/thriveglobal/aftalk/maintainability)
3
+
4
+ # aftalk
5
+
6
+ A Ruby wrapper for [Africa's Talking](https://africastalking.com) telephony services.
7
+
8
+ Currently the only endpoint supported is the `/messaging` POST endpoint that allows you to send SMS messages. Support for other endpoints is planned for future releases.
9
+
10
+ ## Installation
11
+
12
+ Add
13
+
14
+ > `gem "aftalk"`
15
+
16
+ to your `Gemfile` and run
17
+
18
+ > `$ bundle`
19
+
20
+ or install manually by running
21
+
22
+ > `gem install aftalk`.
23
+
24
+ ## Configuration
25
+
26
+ Africa's Talking needs a username and an API key to authenticate your requests. You can also toggle sandbox mode, which causes the gem to hit AF's sandbox host instead of the live API. (**Note: Use of sandbox mode requires that you provide the username `sandbox`.** This is not well documented.)
27
+
28
+ There are two ways to configure these options.
29
+
30
+ ### Set environment variables
31
+
32
+ The first way to configure `aftalk` is by setting environment variables. You can do this by creating a file called `.env` in your project root and adding content like this to it:
33
+
34
+ ```txt
35
+ AFRICAS_TALKING_API_KEY=abc123
36
+ AFRICAS_TALKING_USER_NAME=sandbox
37
+ AFRICAS_TALKING_SANDBOX=true
38
+ ```
39
+
40
+ Replace the values with your own data. Sandbox mode will be activated if any value whatsoever is provided for `AFRICAS_TALKING_SANDBOX`. If you don't want to use sandbox mode, don't set this variable at all.
41
+
42
+ ### Use `AfTalk::Configuration`
43
+
44
+ You can also configure this data in Ruby code as follows:
45
+
46
+ ```ruby
47
+ AfTalk::Configuration.configure do |config|
48
+ config.api_key = "abc123"
49
+ config.sandbox = true
50
+ config.user_name = "sandbox"
51
+ end
52
+ ```
53
+
54
+ Make sure this code runs before you try to use the API.
55
+
56
+ ## Usage
57
+
58
+ To send an SMS:
59
+
60
+ ```ruby
61
+ phone_number = "+15555555555"
62
+ message = "Hello, world!"
63
+
64
+ AfTalk.send_message(
65
+ to: phone_number,
66
+ message: message,
67
+ )
68
+ ```
69
+ The `send_message` method requires `to` and `message` parameters, but also supports all optional parameters supported by the API. For a full list, see [the API docs](http://docs.africastalking.com/sms/sending/).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "aftalk/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "aftalk"
7
+ spec.version = AfTalk::VERSION
8
+ spec.email = ["techadmin@thriveglobal.com"]
9
+ spec.summary = "API wrapper for Africa's Talking services"
10
+ spec.description = <<-DESC
11
+ A Ruby wrapper for Africa's Talking telephony services (https://africastalking.com)
12
+ DESC
13
+ spec.homepage = "https://github.com/thriveglobal/africas_talking_api"
14
+ spec.license = "MIT"
15
+ spec.authors = ["Thrive Global Engineering Team"]
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.15"
20
+ spec.add_dependency "dotenv", "~>2.2"
21
+ spec.add_dependency "faraday", "~> 0.13"
22
+ spec.add_dependency "faraday_middleware", "~> 0.12"
23
+ spec.add_development_dependency "rake", "~> 12"
24
+ spec.add_development_dependency "rspec", "~> 3.7"
25
+ spec.add_development_dependency "rspec_junit_formatter", "~> 0.3"
26
+ spec.add_development_dependency "vcr", "~> 3.0"
27
+ end
data/lib/aftalk.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "dotenv"
2
+ Dotenv.load
3
+
4
+ APP_PATH = File.expand_path("../", __FILE__) + "/aftalk"
5
+
6
+ module AfTalk
7
+ autoload(:Response, "#{APP_PATH}/response.rb")
8
+ autoload(:Client, "#{APP_PATH}/client.rb")
9
+
10
+ extend AfTalk::Client
11
+ end
12
+
13
+ Dir["#{APP_PATH}/**/*.rb"].each { |file| require file }
@@ -0,0 +1,20 @@
1
+ module AfTalk
2
+ module Client
3
+ def send_message(to:, message:, **options)
4
+ messaging_params = { to: to, message: message }.merge(options)
5
+ response = post("/messaging", messaging_params)
6
+
7
+ AfTalk::SendMessageResponse.new(response)
8
+ end
9
+
10
+ private
11
+
12
+ def post(endpoint, **options)
13
+ request.post(endpoint, options)
14
+ end
15
+
16
+ def request
17
+ AfTalk::Request
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ module AfTalk
2
+ class Configuration
3
+ DEFAULT_VERSION = "version1".freeze
4
+
5
+ class << self
6
+ attr_writer :api_key, :sandbox, :user_name
7
+
8
+ def configure
9
+ yield self
10
+ connection.clear
11
+ self
12
+ end
13
+
14
+ def version
15
+ DEFAULT_VERSION
16
+ end
17
+
18
+ def api_key
19
+ @api_key || ENV["AFRICAS_TALKING_API_KEY"]
20
+ end
21
+
22
+ def sandbox
23
+ @sandbox || ENV["AFRICAS_TALKING_SANDBOX"]
24
+ end
25
+
26
+ def user_name
27
+ @user_name || ENV["AFRICAS_TALKING_USER_NAME"]
28
+ end
29
+
30
+ private
31
+
32
+ def connection
33
+ AfTalk::Connection
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require "faraday"
2
+ require "faraday_middleware"
3
+
4
+ module AfTalk
5
+ class Connection
6
+ URL = "https://api.africastalking.com".freeze
7
+ SANDBOX_URL = "https://api.sandbox.africastalking.com".freeze
8
+
9
+ def self.build
10
+ @_connection ||= new.build
11
+ end
12
+
13
+ def self.clear
14
+ @_connection = nil
15
+ end
16
+
17
+ def build
18
+ Faraday.new(
19
+ url: api_url,
20
+ headers: { apiKey: AfTalk::Configuration.api_key,
21
+ accept: "application/json" },
22
+ ) do |connection|
23
+ connection.request :url_encoded
24
+ connection.response :json,
25
+ content_type: /\bjson$/,
26
+ parser_options: { symbolize_names: true }
27
+ connection.adapter Faraday.default_adapter
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def api_url
34
+ ENV["AFRICAS_TALKING_SANDBOX"] ? SANDBOX_URL : URL
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module AfTalk
2
+ class Error < StandardError; end
3
+ class NoApiKeyError < Error; end
4
+ class ResponseFormatError < Error; end
5
+ end
@@ -0,0 +1,41 @@
1
+ module AfTalk
2
+ class Request
3
+ def self.get(path, **options)
4
+ new.get(path, options)
5
+ end
6
+
7
+ def self.post(path, **options)
8
+ new.post(path, options)
9
+ end
10
+
11
+ def get(path, **options)
12
+ connection.get(complete_path(path), complete_options(options))
13
+ end
14
+
15
+ def post(path, **options)
16
+ connection.post(complete_path(path), complete_options(options))
17
+ end
18
+
19
+ private
20
+
21
+ def complete_options(options)
22
+ default_options.merge(options)
23
+ end
24
+
25
+ def complete_path(path)
26
+ "/#{configuration.version}#{path}"
27
+ end
28
+
29
+ def configuration
30
+ AfTalk::Configuration
31
+ end
32
+
33
+ def connection
34
+ AfTalk::Connection.build
35
+ end
36
+
37
+ def default_options
38
+ { username: configuration.user_name }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ module AfTalk
2
+ class Response
3
+ SUCCESS_STATUSES = [200, 201].freeze
4
+ JSON_CONTENT = /\bjson$/
5
+
6
+ attr_reader :body, :error_message, :status
7
+
8
+ def initialize(response)
9
+ @status = response.status
10
+ body = response.body
11
+
12
+ if success?
13
+ @body = body
14
+ else
15
+ @error_message = body
16
+ @body = { error: body }
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def success?
23
+ SUCCESS_STATUSES.include?(status)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module AfTalk
2
+ class SendMessageResponse < Response
3
+ attr_reader :message, :recipients
4
+
5
+ def initialize(response)
6
+ super(response)
7
+ if success?
8
+ @message = body_data[:Message]
9
+ @recipients = body_data[:Recipients].map do |recipient|
10
+ SmsMessageStatus.new(recipient)
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def body_data
18
+ body[:SMSMessageData]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ module AfTalk
2
+ class SmsMessageStatus
3
+ attr_reader :cost, :message_id, :number, :status
4
+
5
+ def initialize(opts = {})
6
+ @cost = opts[:cost]
7
+ @message_id = opts[:messageId]
8
+ @number = opts[:number]
9
+ @status = opts[:status]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module AfTalk
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe AfTalk do
4
+ it "is a module" do
5
+ expect(described_class).to be_a_kind_of(Module)
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe AfTalk do
4
+ describe "#send_message" do
5
+ let(:options) do
6
+ { to: "+14125554563", message: "Hello World!", foo: "bar" }
7
+ end
8
+
9
+ it "produces a send message response object" do
10
+ VCR.use_cassette("aftalk/messaging/send_message") do
11
+ response = described_class.send_message(options)
12
+ recipient = response.recipients.first
13
+
14
+ expect(response.error_message).to be_nil
15
+ expect(response.status).to eq(201)
16
+ expect(response.message).to be
17
+
18
+ expect(recipient.cost).to be
19
+ expect(recipient.message_id).to be
20
+ expect(recipient.number).to eq(options[:to])
21
+ expect(recipient.status).to eq("Success")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ require "rspec"
2
+ require "aftalk"
3
+ require "vcr"
4
+
5
+ current_path = File.expand_path("../", __FILE__)
6
+ Dir["#{current_path}/support/**/*.rb"].each { |file| require file }
7
+
8
+ VCR.configure do |config|
9
+ config.cassette_library_dir = "spec/support/vcr_cassettes"
10
+ config.hook_into :faraday
11
+ config.configure_rspec_metadata!
12
+ config.allow_http_connections_when_no_cassette = false
13
+ config.default_cassette_options = { record: :new_episodes }
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ config.before :each do
18
+ AfTalk::Connection.clear
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module EnvVars
2
+ def stub_env_vars(vars)
3
+ allow(ENV).to receive(:[]).and_call_original
4
+
5
+ vars.each do |key, val|
6
+ allow(ENV).to receive(:[]).with(key).and_return(val)
7
+ end
8
+ end
9
+ end
10
+
11
+ RSpec.configure do |config|
12
+ config.include EnvVars
13
+ end
@@ -0,0 +1,46 @@
1
+ require "spec_helper"
2
+
3
+ describe AfTalk::Client do
4
+ subject do
5
+ class ExampleKlass
6
+ extend AfTalk::Client
7
+ end
8
+ ExampleKlass
9
+ end
10
+
11
+ before(:all) { reset_config }
12
+
13
+ describe "#send_message" do
14
+ let(:options) {
15
+ { to: "+14125554563", message: "Hello World!", foo: "bar" }
16
+ }
17
+ let(:response) { double(headers: { "content-type" => "application/json" }) }
18
+
19
+ context "with all required options" do
20
+ it "it instantiates a send message response" do
21
+ allow(AfTalk::Request).to receive(:post).
22
+ with("/messaging", options).
23
+ and_return(response)
24
+ allow(AfTalk::SendMessageResponse).to receive(:new).with(response)
25
+
26
+ subject.send_message(options)
27
+
28
+ expect(AfTalk::SendMessageResponse).to have_received(:new).with(response)
29
+ end
30
+ end
31
+
32
+ context "without some required options" do
33
+ let(:options) { {} }
34
+
35
+ it "raises a missing options error" do
36
+ expect { subject.send_message(options) }.to raise_error(ArgumentError)
37
+ end
38
+ end
39
+ end
40
+
41
+ def reset_config
42
+ config = AfTalk::Configuration
43
+ config.api_key = nil
44
+ config.user_name = nil
45
+ end
46
+ end