layer-api 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIGRATING.md +212 -0
- data/README.md +166 -43
- data/layer-api.gemspec +3 -3
- data/lib/layer/api.rb +16 -10
- data/lib/layer/errors.rb +43 -0
- data/lib/layer/http_client.rb +70 -0
- data/lib/layer/identity_token.rb +53 -0
- data/lib/layer/middleware/api_errors.rb +13 -0
- data/lib/layer/platform/client.rb +57 -0
- data/lib/layer/resource.rb +80 -0
- data/lib/layer/resource_proxy.rb +20 -0
- data/lib/layer/resources/announcement.rb +6 -0
- data/lib/layer/resources/block.rb +31 -0
- data/lib/layer/resources/conversation.rb +9 -0
- data/lib/layer/resources/message.rb +6 -0
- data/lib/layer/resources/user.rb +21 -0
- data/lib/layer/{api/version.rb → version.rb} +1 -1
- metadata +19 -15
- data/lib/layer/api/client.rb +0 -25
- data/lib/layer/api/client/announcements.rb +0 -9
- data/lib/layer/api/client/conversations.rb +0 -30
- data/lib/layer/api/client/identity_token.rb +0 -61
- data/lib/layer/api/client/users.rb +0 -18
- data/lib/layer/api/configuration.rb +0 -30
- data/lib/layer/api/connection.rb +0 -44
- data/lib/layer/api/error.rb +0 -39
- data/lib/layer/api/middleware/api_errors.rb +0 -15
data/layer-api.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'layer/
|
4
|
+
require 'layer/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "layer-api"
|
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["jake.kelly10@gmail.com"]
|
11
11
|
spec.license = "MIT"
|
12
12
|
|
13
|
-
spec.summary = "A ruby toolkit for Layer's
|
14
|
-
spec.description = "Simple wrapper for the Layer
|
13
|
+
spec.summary = "A ruby toolkit for Layer's Web API's (https://developer.layer.com/docs)"
|
14
|
+
spec.description = "Simple wrapper for the Layer's Web API's"
|
15
15
|
spec.homepage = "https://github.com/cakejelly/layer-api"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
data/lib/layer/api.rb
CHANGED
@@ -2,15 +2,21 @@ require 'faraday'
|
|
2
2
|
require 'json'
|
3
3
|
require 'jwt'
|
4
4
|
|
5
|
-
require "layer/
|
6
|
-
require "layer/
|
7
|
-
require "layer/
|
8
|
-
require "layer/
|
9
|
-
require "layer/
|
5
|
+
require "layer/version"
|
6
|
+
require "layer/http_client"
|
7
|
+
require "layer/errors"
|
8
|
+
require "layer/resource"
|
9
|
+
require "layer/resource_proxy"
|
10
|
+
require "layer/identity_token"
|
10
11
|
|
11
|
-
|
12
|
+
# Platform
|
13
|
+
require "layer/platform/client"
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
# Resources
|
16
|
+
require "layer/resources/conversation"
|
17
|
+
require "layer/resources/message"
|
18
|
+
require "layer/resources/announcement"
|
19
|
+
require "layer/resources/user"
|
20
|
+
require "layer/resources/block"
|
21
|
+
|
22
|
+
require "layer/middleware/api_errors"
|
data/lib/layer/errors.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Layer
|
2
|
+
class Error < StandardError
|
3
|
+
def self.from_response(response)
|
4
|
+
status = response[:status]
|
5
|
+
|
6
|
+
if klass = case status
|
7
|
+
when 400 then Layer::Errors::BadRequest
|
8
|
+
when 404 then Layer::Errors::NotFound
|
9
|
+
when 409 then Layer::Errors::Conflict
|
10
|
+
when 410 then Layer::Errors::ResourceDeleted
|
11
|
+
when 500..599 then Layer::Errors::ServerError
|
12
|
+
else self
|
13
|
+
end
|
14
|
+
klass.new(response)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(response)
|
19
|
+
@response = response
|
20
|
+
super(build_error_message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def response
|
24
|
+
@response
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def build_error_message
|
30
|
+
message = "Layer responded with status "
|
31
|
+
message << "#{@response[:status]}: #{@response[:body]}"
|
32
|
+
message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Errors
|
37
|
+
class BadRequest < Error; end
|
38
|
+
class NotFound < Error; end
|
39
|
+
class ServerError < Error; end
|
40
|
+
class ResourceDeleted < Error; end
|
41
|
+
class Conflict < Error; end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Layer
|
4
|
+
class HttpClient
|
5
|
+
DEFAULT_HOST = "https://api.layer.com"
|
6
|
+
|
7
|
+
attr_reader :app_id, :api_token
|
8
|
+
|
9
|
+
def initialize(app_id, api_token)
|
10
|
+
@app_id = app_id
|
11
|
+
@api_token = api_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def connection
|
15
|
+
@connection ||= Faraday.new(url: base_url) do |faraday|
|
16
|
+
faraday.headers = default_layer_headers
|
17
|
+
faraday.request :url_encoded
|
18
|
+
faraday.adapter Faraday.default_adapter
|
19
|
+
faraday.use Middleware::ApiErrors
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(url, options = {})
|
24
|
+
call(:get, url, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def post(url, options = {})
|
28
|
+
call(:post, url, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def patch(url, options = {})
|
32
|
+
call(:patch, url, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(url)
|
36
|
+
call(:delete, url)
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(method, url, options = {})
|
40
|
+
response = run_request(method, url, options)
|
41
|
+
response.body.empty? ? nil : JSON.parse(response.body)
|
42
|
+
end
|
43
|
+
|
44
|
+
def run_request(method, url, options = {})
|
45
|
+
connection.run_request(
|
46
|
+
method,
|
47
|
+
url,
|
48
|
+
options[:body],
|
49
|
+
options[:headers]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_layer_headers
|
54
|
+
{
|
55
|
+
'Accept' => 'application/vnd.layer+json; version=1.0',
|
56
|
+
'Authorization' => "Bearer #{api_token}",
|
57
|
+
'Content-Type' => 'application/json',
|
58
|
+
'If-None-Match' => SecureRandom.uuid
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def layer_patch_header
|
63
|
+
{ 'Content-Type' => 'application/vnd.layer-patch+json' }
|
64
|
+
end
|
65
|
+
|
66
|
+
def base_url
|
67
|
+
"#{DEFAULT_HOST}/apps/#{app_id}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Layer
|
2
|
+
class IdentityToken
|
3
|
+
attr_reader :user_id, :nonce, :expires_at
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@user_id = options[:user_id]
|
7
|
+
@nonce = options[:nonce]
|
8
|
+
@expires_at = (options[:expires_at] || Time.now+(1209600))
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
get_jwt
|
13
|
+
end
|
14
|
+
|
15
|
+
def layer_key_id
|
16
|
+
ENV['LAYER_KEY_ID']
|
17
|
+
end
|
18
|
+
|
19
|
+
def layer_provider_id
|
20
|
+
ENV['LAYER_PROVIDER_ID']
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def get_jwt
|
26
|
+
JWT.encode(claim, private_key, 'RS256', headers)
|
27
|
+
end
|
28
|
+
|
29
|
+
def headers
|
30
|
+
{
|
31
|
+
typ: 'JWT',
|
32
|
+
cty: 'layer-eit;v=1',
|
33
|
+
kid: layer_key_id
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def claim
|
38
|
+
{
|
39
|
+
iss: layer_provider_id,
|
40
|
+
prn: user_id.to_s,
|
41
|
+
iat: Time.now.to_i,
|
42
|
+
exp: expires_at.to_i,
|
43
|
+
nce: nonce
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def private_key
|
48
|
+
# Cloud66 stores newlines as \n instead of \\n
|
49
|
+
key = ENV['LAYER_PRIVATE_KEY'].dup
|
50
|
+
OpenSSL::PKey::RSA.new(key.gsub!("\\n","\n"))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Layer
|
2
|
+
module Middleware
|
3
|
+
class ApiErrors < Faraday::Response::Middleware
|
4
|
+
ERROR_CODES = 400...600
|
5
|
+
|
6
|
+
def on_complete(response)
|
7
|
+
if ERROR_CODES.include?(response.status)
|
8
|
+
raise Layer::Error.from_response(response)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Layer
|
2
|
+
module Platform
|
3
|
+
class Client
|
4
|
+
attr_accessor :api_token, :app_id
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@api_token = options[:api_token] || ENV['LAYER_API_TOKEN']
|
8
|
+
@app_id = options[:app_id] || ENV['LAYER_APP_ID']
|
9
|
+
strip_layer_prefix(@app_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def client
|
13
|
+
@http_client ||= Layer::HttpClient.new(app_id, api_token)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(url, *args)
|
17
|
+
client.get(url, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(url, *args)
|
21
|
+
client.post(url, *args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def put(url, *args)
|
25
|
+
client.put(url, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def patch(url, *args)
|
29
|
+
client.patch(url, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(url)
|
33
|
+
client.delete(url)
|
34
|
+
end
|
35
|
+
|
36
|
+
def strip_layer_prefix(string)
|
37
|
+
string.split("/").last if string
|
38
|
+
end
|
39
|
+
|
40
|
+
def announcements
|
41
|
+
Layer::ResourceProxy.new(nil, Layer::Resources::Announcement)
|
42
|
+
end
|
43
|
+
|
44
|
+
def conversations
|
45
|
+
Layer::ResourceProxy.new(nil, Layer::Resources::Conversation)
|
46
|
+
end
|
47
|
+
|
48
|
+
def users
|
49
|
+
Layer::ResourceProxy.new(nil, Layer::Resources::User)
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate_identity_token(options = {})
|
53
|
+
Layer::IdentityToken.new(options)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Layer
|
2
|
+
class Resource
|
3
|
+
attr_reader :attributes, :client
|
4
|
+
|
5
|
+
def initialize(attributes)
|
6
|
+
@attributes = attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def update(params)
|
10
|
+
client.patch(
|
11
|
+
url,
|
12
|
+
body: params.to_json,
|
13
|
+
headers: client.layer_patch_header
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
client.delete(url)
|
19
|
+
end
|
20
|
+
|
21
|
+
def client
|
22
|
+
self.class.client
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method, *args, &block)
|
26
|
+
method_key = method.to_s
|
27
|
+
attributes.has_key?(method_key) ? attributes[method_key] : super
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(method, include_private = false)
|
31
|
+
method_key = method.to_s
|
32
|
+
attributes.has_key?(method_key) ? true : false
|
33
|
+
end
|
34
|
+
|
35
|
+
def uuid
|
36
|
+
attributes["id"].split("/").last if attributes["id"]
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def class_name
|
41
|
+
name.split("::").last
|
42
|
+
end
|
43
|
+
|
44
|
+
def pluralized_name
|
45
|
+
"#{class_name}s"
|
46
|
+
end
|
47
|
+
|
48
|
+
def url
|
49
|
+
pluralized_name.downcase
|
50
|
+
end
|
51
|
+
|
52
|
+
def client
|
53
|
+
Layer::HttpClient.new(
|
54
|
+
ENV['LAYER_APP_ID'],
|
55
|
+
ENV['LAYER_API_TOKEN']
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create(url, params = {})
|
60
|
+
response = client.post(url, body: params.to_json)
|
61
|
+
new(response)
|
62
|
+
end
|
63
|
+
|
64
|
+
def find(url, id)
|
65
|
+
response = client.get("#{url}/#{id}")
|
66
|
+
new(response)
|
67
|
+
end
|
68
|
+
|
69
|
+
def list(url, params = {})
|
70
|
+
collection = client.get(url, body: params.to_json)
|
71
|
+
|
72
|
+
if collection.any?
|
73
|
+
collection.map{ |resource| new(resource) }
|
74
|
+
else
|
75
|
+
[]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Layer
|
2
|
+
class ResourceProxy
|
3
|
+
def initialize(base, resource)
|
4
|
+
@base = base
|
5
|
+
@resource = resource
|
6
|
+
end
|
7
|
+
|
8
|
+
def url
|
9
|
+
@base.nil? ? @resource.url : "#{@base.url}/#{@resource.url}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method, *args, &block)
|
13
|
+
@resource.public_send(method, *([url] + args), &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(method, include_private = false)
|
17
|
+
@resource.respond_to?(method) || super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Layer
|
2
|
+
module Resources
|
3
|
+
class Block < Layer::Resource
|
4
|
+
def self.find(url, user)
|
5
|
+
user_id = user.instance_of?(User) ? user.id : user
|
6
|
+
new_abstract_instance(url, user_id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.list(url, params = {})
|
10
|
+
collection = client.get(url, body: params.to_json)
|
11
|
+
|
12
|
+
if collection.any?
|
13
|
+
collection.map{ |resource| new_abstract_instance(url, resource['user_id']) }
|
14
|
+
else
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create(url, params = {})
|
20
|
+
user_params = params.instance_of?(User) ? {user_id: params.id} : params
|
21
|
+
|
22
|
+
client.post(url, body: user_params.to_json)
|
23
|
+
new_abstract_instance(url, user_params[:user_id])
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.new_abstract_instance(url, id)
|
27
|
+
new({"id" => id, "url" => "#{url}/#{id}"})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|