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.
@@ -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/api/version'
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 Platform API (https://developer.layer.com/docs/platform)"
14
- spec.description = "Simple wrapper for the Layer Platform API"
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)/}) }
@@ -2,15 +2,21 @@ require 'faraday'
2
2
  require 'json'
3
3
  require 'jwt'
4
4
 
5
- require "layer/api/version"
6
- require "layer/api/configuration"
7
- require "layer/api/connection"
8
- require "layer/api/client"
9
- require "layer/api/error"
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
- require "layer/api/middleware/api_errors"
12
+ # Platform
13
+ require "layer/platform/client"
12
14
 
13
- module Layer
14
- module Api
15
- end
16
- end
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"
@@ -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,6 @@
1
+ module Layer
2
+ module Resources
3
+ class Announcement < Layer::Resource
4
+ end
5
+ end
6
+ 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
@@ -0,0 +1,9 @@
1
+ module Layer
2
+ module Resources
3
+ class Conversation < Layer::Resource
4
+ def messages
5
+ Layer::ResourceProxy.new(self, Message)
6
+ end
7
+ end
8
+ end
9
+ end