aftalk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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