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.
@@ -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