layer-api 0.3.1 → 0.4.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.
- 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
|