tincan 0.1.5
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +19 -0
- data/Rakefile +12 -0
- data/Readme.markdown +78 -0
- data/config.ru +4 -0
- data/lib/tincan/application.rb +87 -0
- data/lib/tincan/phone_number.rb +94 -0
- data/lib/tincan/sms.rb +14 -0
- data/lib/tincan/utils.rb +13 -0
- data/lib/tincan/version.rb +3 -0
- data/lib/tincan.rb +54 -0
- data/spec/integration/create_spec.rb +47 -0
- data/spec/integration/verify_spec.rb +22 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/request_macros.rb +13 -0
- data/spec/unit/sms_spec.rb +10 -0
- data/spec/unit/utils_spec.rb +21 -0
- data/tincan.gemspec +28 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2967dd0d4f1f05af20103c1c50939111c751aa90
|
4
|
+
data.tar.gz: 17ff307ad20ea27021eb624c5d3a9ece804e8d89
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 92c22a93fc950507fa525d98b6711ae1237d558082aac60eb7e5ec1b85026d189a48bd71531a1a12045e5d3127146269d638ae54832ab1be19329fa3d9a35b54
|
7
|
+
data.tar.gz: c4a48557ced89f840dc503284ea6d4e20413be60b7e4040eca5aa81f9e7666a480ad1eac7d8d9c12e3225ded13d045ee434863711c393ae5cadf6cef453378ec
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
ruby '2.0.0'
|
4
|
+
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
gem 'rake', require: false
|
8
|
+
gem 'oj', require: false
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem 'shotgun', require: false
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'minitest'
|
16
|
+
gem 'rack-test'
|
17
|
+
gem 'fakeredis'
|
18
|
+
gem 'simplecov', require: false
|
19
|
+
end
|
data/Rakefile
ADDED
data/Readme.markdown
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Phone
|
2
|
+
|
3
|
+
Phone number provider.
|
4
|
+
|
5
|
+
# Configuration
|
6
|
+
|
7
|
+
Tincan delegates out the sending of SMS messages. Place the following code in an initializer
|
8
|
+
with an implementation in order to use it.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
Tincan::SMS.sender = lambda do |phone_number, body|
|
12
|
+
puts "SMS sent to #{phone_number} - #{body}"
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
## Create
|
17
|
+
|
18
|
+
First, create a phone number verification:
|
19
|
+
|
20
|
+
POST /v1/phone_numbers
|
21
|
+
phone_number=%2B14152751660&message_format=Click%20this%20link%3A%20https%3A//seesaw.co/a/CODE
|
22
|
+
|
23
|
+
Response:
|
24
|
+
|
25
|
+
``` json
|
26
|
+
{
|
27
|
+
"id": "0H2r2U1W3s3I0M3K0k173j1s3y2i2r05",
|
28
|
+
"e164": "+14152751660",
|
29
|
+
"country_code": "US",
|
30
|
+
"local_format": "(415) 275-1660",
|
31
|
+
"verified_at": null
|
32
|
+
}
|
33
|
+
```
|
34
|
+
|
35
|
+
This will send a text message with the following message:
|
36
|
+
|
37
|
+
> Click this link: https://seesaw.co/a/aFdh79mD"
|
38
|
+
|
39
|
+
|
40
|
+
## Verify
|
41
|
+
|
42
|
+
A client can now make the following request:
|
43
|
+
|
44
|
+
POST /v1/phone_numbers/verify
|
45
|
+
code=aFdh79mD
|
46
|
+
|
47
|
+
Response:
|
48
|
+
|
49
|
+
``` json
|
50
|
+
{
|
51
|
+
"id": "0H2r2U1W3s3I0M3K0k173j1s3y2i2r05",
|
52
|
+
"e164": "+14152751660",
|
53
|
+
"country_code": "US",
|
54
|
+
"local_format": "(415) 275-1660",
|
55
|
+
"verified_at": 1365445347
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
Now `verified_at` is set and the client knows this is a valid phone number.
|
60
|
+
|
61
|
+
|
62
|
+
## Show
|
63
|
+
|
64
|
+
You can show a phone number token for a day to poll to see if it's verified. This will probably rarely be used since the request to verify it will be the only place this information is needed.
|
65
|
+
|
66
|
+
GET /v1/phone_numbers/0H2r2U1W3s3I0M3K0k173j1s3y2i2r05
|
67
|
+
|
68
|
+
Response:
|
69
|
+
|
70
|
+
``` json
|
71
|
+
{
|
72
|
+
"id": "0H2r2U1W3s3I0M3K0k173j1s3y2i2r05",
|
73
|
+
"e164": "+14152751660",
|
74
|
+
"country_code": "US",
|
75
|
+
"local_format": "(415) 275-1660",
|
76
|
+
"verified_at": 1365445347
|
77
|
+
}
|
78
|
+
```
|
data/config.ru
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'fakie/errors'
|
3
|
+
|
4
|
+
module Tincan
|
5
|
+
class Application < Sinatra::Base
|
6
|
+
# Create phone number
|
7
|
+
post '/' do
|
8
|
+
unless phone_number = params['phone_number']
|
9
|
+
status 400
|
10
|
+
return json({
|
11
|
+
error: 'bad_request',
|
12
|
+
error_description: "The `phone_number` parameter is required."
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
unless message_format = params['message_format']
|
17
|
+
status 400
|
18
|
+
return json({
|
19
|
+
error: 'bad_request',
|
20
|
+
error_description: "The `message_format` parameter is required."
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
phone = PhoneNumber.create!(phone_number)
|
26
|
+
rescue Fakie::InvalidPhoneNumber
|
27
|
+
status 400
|
28
|
+
hash = {
|
29
|
+
error: 'invalid_phone_number',
|
30
|
+
error_description: 'The phone number provided was invalid.'
|
31
|
+
}
|
32
|
+
return json(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
SMS.send(phone, message_format)
|
36
|
+
|
37
|
+
status 201
|
38
|
+
json phone.as_json
|
39
|
+
end
|
40
|
+
|
41
|
+
# Verify phone number
|
42
|
+
post '/verify' do
|
43
|
+
unless code = params['code']
|
44
|
+
status 400
|
45
|
+
return json({
|
46
|
+
error: 'bad_request',
|
47
|
+
error_description: "The `code` parameter is required."
|
48
|
+
})
|
49
|
+
end
|
50
|
+
|
51
|
+
if phone = PhoneNumber.verify_code!(code)
|
52
|
+
return json(phone.as_json)
|
53
|
+
end
|
54
|
+
|
55
|
+
status 400
|
56
|
+
json({
|
57
|
+
error: 'invalid_code',
|
58
|
+
error_description: 'There were no phone numbers matching the given code.'
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
# Show phone number
|
63
|
+
get '/:id' do
|
64
|
+
if phone = PhoneNumber.find(params[:id])
|
65
|
+
return json(phone)
|
66
|
+
end
|
67
|
+
|
68
|
+
status 404
|
69
|
+
json({
|
70
|
+
error: 'not_found',
|
71
|
+
error_description: 'A phone_number was not found with this id.'
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def json(content)
|
78
|
+
content_type 'application/json'
|
79
|
+
MultiJson.dump(content)
|
80
|
+
end
|
81
|
+
|
82
|
+
def params
|
83
|
+
return super if request.get? || !request.content_type || request.content_type.index('application/json') != 0
|
84
|
+
@_params ||= MultiJson.load(request.env['rack.input'].read)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'fakie'
|
3
|
+
|
4
|
+
module Tincan
|
5
|
+
class PhoneNumber
|
6
|
+
REDIS_KEY_PREFIX = 'phone-number_'
|
7
|
+
CODE_REDIS_KEY_PREFIX = 'phone-number-code'
|
8
|
+
EXPIRATION = 86400 # one day of in-cache storage
|
9
|
+
|
10
|
+
attr_reader :id, :e164, :country_code, :local_format, :verified_at
|
11
|
+
attr_accessor :code
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def create!(e164)
|
15
|
+
# TODO: Make sure there aren't collisions
|
16
|
+
parsed = Fakie.parse(e164)
|
17
|
+
phone = new({
|
18
|
+
'id' => Utils.generate_code(32),
|
19
|
+
'e164' => parsed.e164,
|
20
|
+
'country_code' => parsed.region_code,
|
21
|
+
'local_format' => parsed.local_format
|
22
|
+
})
|
23
|
+
|
24
|
+
phone.save
|
25
|
+
|
26
|
+
# Store code
|
27
|
+
phone.code = Utils.generate_code(8)
|
28
|
+
key = "#{CODE_REDIS_KEY_PREFIX}#{phone.code}"
|
29
|
+
Tincan.redis.set(key, phone.id)
|
30
|
+
Tincan.redis.expire(key, EXPIRATION)
|
31
|
+
phone
|
32
|
+
end
|
33
|
+
|
34
|
+
def find(id)
|
35
|
+
return nil unless id
|
36
|
+
|
37
|
+
result = Tincan.redis.get("#{REDIS_KEY_PREFIX}#{id}")
|
38
|
+
return nil unless result
|
39
|
+
|
40
|
+
new(MultiJson.load(result))
|
41
|
+
end
|
42
|
+
|
43
|
+
def verify_code!(code)
|
44
|
+
id = Tincan.redis.get("#{CODE_REDIS_KEY_PREFIX}#{code}")
|
45
|
+
return nil unless phone = find(id)
|
46
|
+
|
47
|
+
phone.verify!
|
48
|
+
phone
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(hash)
|
53
|
+
%w{id e164 country_code local_format}.each do |key|
|
54
|
+
instance_variable_set(:"@#{key}", hash[key])
|
55
|
+
end
|
56
|
+
|
57
|
+
@verified_at = Time.at(hash['verified_at']) if hash['verified_at']
|
58
|
+
end
|
59
|
+
|
60
|
+
def verified?
|
61
|
+
verified_at && verified_at < Time.now.utc
|
62
|
+
end
|
63
|
+
|
64
|
+
def verify!
|
65
|
+
@verified_at = Time.now.utc
|
66
|
+
save
|
67
|
+
end
|
68
|
+
|
69
|
+
def as_json
|
70
|
+
hash = {}
|
71
|
+
|
72
|
+
# Default attributes
|
73
|
+
%w{id e164 country_code local_format}.each do |key|
|
74
|
+
hash[key] = instance_variable_get(:"@#{key}")
|
75
|
+
end
|
76
|
+
|
77
|
+
# Add `verified_at` as an integer
|
78
|
+
hash['verified_at'] = verified_at.to_i if verified_at
|
79
|
+
|
80
|
+
# Return the hash
|
81
|
+
hash
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_json
|
85
|
+
MultiJson.dump(as_json)
|
86
|
+
end
|
87
|
+
|
88
|
+
def save
|
89
|
+
key = "#{REDIS_KEY_PREFIX}#{id}"
|
90
|
+
Tincan.redis.set(key, to_json)
|
91
|
+
Tincan.redis.expire(key, EXPIRATION)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/tincan/sms.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Tincan
|
2
|
+
class SMS
|
3
|
+
class << self
|
4
|
+
attr_accessor :sender
|
5
|
+
|
6
|
+
def send(number, message)
|
7
|
+
raise NoSmsSenderError.new unless @sender
|
8
|
+
@sender.call number.e164, message.sub('CODE', number.code)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class NoSmsSenderError < ArgumentError; end
|
13
|
+
end
|
14
|
+
end
|
data/lib/tincan/utils.rb
ADDED
data/lib/tincan.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'tincan/version'
|
2
|
+
require 'tincan/utils'
|
3
|
+
require 'tincan/phone_number'
|
4
|
+
require 'tincan/application'
|
5
|
+
require 'tincan/sms'
|
6
|
+
|
7
|
+
require 'redis'
|
8
|
+
require 'redis/namespace'
|
9
|
+
|
10
|
+
module Tincan
|
11
|
+
def self.configure
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.redis
|
16
|
+
# Set redis to nothing make the setter run and setup a default if it's nothing
|
17
|
+
self.redis = {} unless defined? @@redis
|
18
|
+
|
19
|
+
# Return the namespaced Redis instance
|
20
|
+
@@redis
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.redis=(options = {})
|
24
|
+
client = nil
|
25
|
+
if options.is_a?(Redis)
|
26
|
+
client = options
|
27
|
+
else
|
28
|
+
url = options[:url] || determine_redis_provider || 'redis://localhost:6379/0'
|
29
|
+
driver = options[:driver] || 'ruby'
|
30
|
+
namespace = options[:namespace] || 'tincan'
|
31
|
+
|
32
|
+
client = Redis.connect(url: url, driver: driver)
|
33
|
+
end
|
34
|
+
|
35
|
+
@@redis = Redis::Namespace.new(namespace, redis: client)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.sms_sender
|
39
|
+
SMS.sender
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.sms_sender=(sender)
|
43
|
+
SMS.sender = sender
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.determine_redis_provider
|
49
|
+
return ENV['REDISTOGO_URL'] if ENV['REDISTOGO_URL']
|
50
|
+
provider = ENV['REDIS_PROVIDER'] || 'REDIS_URL'
|
51
|
+
ENV[provider]
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Create integration' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Tincan::SMS.sender = lambda do |number, body|
|
7
|
+
@messages = [] unless @messages
|
8
|
+
@messages << {number: number, body: body}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
Tincan::SMS.sender = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'requires a phone number' do
|
17
|
+
post '/'
|
18
|
+
last_status.must_equal 400
|
19
|
+
last_json['error'].must_equal 'bad_request'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'requires a valid phone number' do
|
23
|
+
post '/', phone_number: '+14152751660', message_format: 'Click this: https://seesaw.co/CODE'
|
24
|
+
last_status.must_equal 201
|
25
|
+
last_json['e164'].must_equal '+14152751660'
|
26
|
+
last_json['local_format'].must_equal '(415) 275-1660'
|
27
|
+
|
28
|
+
# Verify the last messages are in the correct formats
|
29
|
+
@messages.last[:number].must_equal last_json['e164']
|
30
|
+
@messages.last[:body].must_match /https\:\/\/seesaw.co\/(\S+)/
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'handles invalid phone numbers' do
|
34
|
+
post '/', phone_number: '+1415275166', message_format: 'Click this: https://seesaw.co/CODE'
|
35
|
+
last_status.must_equal 400
|
36
|
+
last_json['error'].must_equal 'invalid_phone_number'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'handles missing message formats' do
|
40
|
+
post '/', phone_number: '+14152751660'
|
41
|
+
last_status.must_equal 400
|
42
|
+
last_json['error'].must_equal 'bad_request'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'sends an SMS'
|
46
|
+
it 'store a redirect URI'
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Verify integration' do
|
4
|
+
it 'should require a code' do
|
5
|
+
post '/verify'
|
6
|
+
last_status.must_equal 400
|
7
|
+
last_json['error'].must_equal 'bad_request'
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should require a valid code' do
|
11
|
+
post '/verify', code: 'asdf'
|
12
|
+
last_status.must_equal 400
|
13
|
+
last_json['error'].must_equal 'invalid_code'
|
14
|
+
|
15
|
+
phone = Tincan::PhoneNumber.create!('+14152751660')
|
16
|
+
post '/verify', code: phone.code
|
17
|
+
last_status.must_equal 200
|
18
|
+
|
19
|
+
get "/#{phone.id}"
|
20
|
+
last_json['verified_at'].wont_be_nil
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require :test
|
4
|
+
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter '/spec/'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'minitest/autorun'
|
11
|
+
require 'tincan'
|
12
|
+
|
13
|
+
# Support files
|
14
|
+
Dir["#{File.expand_path(File.dirname(__FILE__))}/support/*.rb"].each do |file|
|
15
|
+
require file
|
16
|
+
end
|
17
|
+
|
18
|
+
class IntegrationTest < MiniTest::Spec
|
19
|
+
include Rack::Test::Methods
|
20
|
+
include RequestMacros
|
21
|
+
|
22
|
+
def app
|
23
|
+
Tincan::Application
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
super
|
28
|
+
Tincan.redis.flushdb
|
29
|
+
end
|
30
|
+
|
31
|
+
register_spec_type(/integration$/, self)
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RequestMacros
|
2
|
+
def last_json
|
3
|
+
return @_last_json if @_last_json_id == last_response.object_id
|
4
|
+
@_last_json_id = last_response.object_id
|
5
|
+
@_last_json = last_response.body.length > 0 ? MultiJson.load(last_response.body) : nil
|
6
|
+
rescue MultiJson::LoadError
|
7
|
+
raise "Failed to parse JSON:\n\n#{last_response.body}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def last_status
|
11
|
+
last_response.status
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tincan::Utils do
|
4
|
+
describe 'generate_code' do
|
5
|
+
subject do
|
6
|
+
Tincan::Utils.generate_code
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'returns a string' do
|
10
|
+
subject.must_be_kind_of String
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'defaults to 8 characters' do
|
14
|
+
subject.length.must_equal 8
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'supports odd numbers of characters' do
|
18
|
+
Tincan::Utils.generate_code(7).length.must_equal 7
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/tincan.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tincan/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'tincan'
|
8
|
+
gem.version = Tincan::VERSION
|
9
|
+
gem.authors = ['Sam Soffes']
|
10
|
+
gem.email = ['sam@soff.es']
|
11
|
+
gem.description = 'Phone number provider.'
|
12
|
+
gem.summary = gem.description
|
13
|
+
gem.homepage = 'https://github.com/seesawco/tincan'
|
14
|
+
gem.license = 'MIT'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ['lib']
|
20
|
+
|
21
|
+
gem.required_ruby_version = '>= 1.9.2'
|
22
|
+
gem.add_dependency 'sinatra'
|
23
|
+
gem.add_dependency 'fakie'
|
24
|
+
gem.add_dependency 'redis'
|
25
|
+
gem.add_dependency 'redis-namespace'
|
26
|
+
gem.add_dependency 'base62'
|
27
|
+
gem.add_dependency 'multi_json'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tincan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sam Soffes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fakie
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redis-namespace
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: base62
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: multi_json
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Phone number provider.
|
98
|
+
email:
|
99
|
+
- sam@soff.es
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- Gemfile
|
106
|
+
- Rakefile
|
107
|
+
- Readme.markdown
|
108
|
+
- config.ru
|
109
|
+
- lib/tincan.rb
|
110
|
+
- lib/tincan/application.rb
|
111
|
+
- lib/tincan/phone_number.rb
|
112
|
+
- lib/tincan/sms.rb
|
113
|
+
- lib/tincan/utils.rb
|
114
|
+
- lib/tincan/version.rb
|
115
|
+
- spec/integration/create_spec.rb
|
116
|
+
- spec/integration/verify_spec.rb
|
117
|
+
- spec/spec_helper.rb
|
118
|
+
- spec/support/request_macros.rb
|
119
|
+
- spec/unit/sms_spec.rb
|
120
|
+
- spec/unit/utils_spec.rb
|
121
|
+
- tincan.gemspec
|
122
|
+
homepage: https://github.com/seesawco/tincan
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.9.2
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.0.3
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Phone number provider.
|
146
|
+
test_files:
|
147
|
+
- spec/integration/create_spec.rb
|
148
|
+
- spec/integration/verify_spec.rb
|
149
|
+
- spec/spec_helper.rb
|
150
|
+
- spec/support/request_macros.rb
|
151
|
+
- spec/unit/sms_spec.rb
|
152
|
+
- spec/unit/utils_spec.rb
|