ringcentral_sdk 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53f1dff65956dcafa9e3bd77501e187cf7c67e98
4
- data.tar.gz: 8176d3c80e94ff445be20793393d48e6fae8d0f1
3
+ metadata.gz: 18dd0357491d7aae9396a5a45bfa4f789775747b
4
+ data.tar.gz: 5a0880db95194fa5a7b95c2e64c67f664ca13fae
5
5
  SHA512:
6
- metadata.gz: 9e1742a2c45d5c7764a9623f435c674cce3b9f6a4140a2f7c9738a9b43c89ecbb55cdc74a330839ff6de139aca7d4a4db52acfe249e3ac21ef911b6574a44d03
7
- data.tar.gz: f8eb9a7e19aeb738fec47dc18af2c5825fe701ff34b8aaf7690258efbd58398ed2f0fcf3604088c511468985f6e299dc27e3b2d4900486ed537a096906d2b206
6
+ metadata.gz: c4ef8a3fe7f1eacdf40c06e9970632699f74679a25e7325c83639086432e6475227427bc8aebe8d7e80982785e1ae77ff5dd4d87c71b755cd56bcc62e89535a6
7
+ data.tar.gz: b4790e53b272994ebafbdda8e61a3cb34d2b082ee9ae0b33b9094d3d9c062d8eb05cee74193579f29cf1feeadb4921f538f3ab86416effa155e2728d40f6d7a3
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  RingCentral SDK
2
2
  ===============
3
3
 
4
+ [![Build Status](https://img.shields.io/travis/grokify/ringcentral-sdk-ruby/master.svg)](https://travis-ci.org/grokify/ringcentral-sdk-ruby)
5
+ [![Code Climate](https://codeclimate.com/github/grokify/ringcentral-sdk-ruby/badges/gpa.svg)](https://codeclimate.com/github/grokify/ringcentral-sdk-ruby)
6
+ [![Coverage Status](https://coveralls.io/repos/grokify/ringcentral-sdk-ruby/badge.svg?branch=master)](https://coveralls.io/r/grokify/ringcentral-sdk-ruby?branch=master)
7
+ [![Documentation](https://img.shields.io/badge/documentation-rubydoc-blue.svg)](http://www.rubydoc.info/gems/ringcentral_sdk/)
8
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/grokify/ringcentral-sdk-ruby/master/LICENSE.txt)
9
+
4
10
  This is an unofficial Ruby SDK for the RingCentral Connect Platform REST API (https://developers.ringcentral.com).
5
11
 
6
12
  The core SDK objects follow the general design of the [official RingCentral SDKs](https://github.com/ringcentral). The SDK helper additions are included to make it easier to interact with features of the API.
@@ -24,7 +30,21 @@ The following items are still needed for this SDK. Contributions are most welcom
24
30
  Installation
25
31
  ============
26
32
 
27
- ## RubyGems
33
+ ## Via Bundler
34
+
35
+ Add this line to your application's Gemfile:
36
+
37
+ ```ruby
38
+ gem 'ringcentral_sdk'
39
+ ```
40
+
41
+ And then execute:
42
+
43
+ ```sh
44
+ $ bundle
45
+ ```
46
+
47
+ ## Via RubyGems
28
48
 
29
49
  ```sh
30
50
  $ gem install ringcentral_sdk
@@ -75,7 +95,7 @@ client = rcsdk.platform.client
75
95
 
76
96
  ## Create SMS Message
77
97
 
78
- SMS and other requests are directly without helpers.
98
+ SMS and other requests can be easily sent directly without helpers.
79
99
 
80
100
  ```ruby
81
101
  # Send SMS - POST request
@@ -97,11 +117,11 @@ A fax helper is included that can be used to create the `multipart/mixed` HTTP r
97
117
 
98
118
  This consists of instantiating a fax helper object and then executing a Faraday POST request.
99
119
 
100
- ```ruby
101
- # 1) Fax Helper for Text Message
120
+ ### 1) Fax Helper for Text Message
102
121
 
122
+ ```ruby
103
123
  fax = RingCentralSdk::Helpers::CreateFaxRequest.new(
104
- { account_id => '~', extension_id => '~' }, # Can be nil or {} for defaults '~'
124
+ { :account_id => '~', :extension_id => '~' }, # Can be nil or {} for defaults '~'
105
125
  {
106
126
  # phone numbers are in E.164 format with or without leading '+'
107
127
  :to => [{ :phoneNumber => '+16505551212' }],
@@ -110,15 +130,17 @@ fax = RingCentralSdk::Helpers::CreateFaxRequest.new(
110
130
  },
111
131
  :text => 'RingCentral Fax via Ruby!'
112
132
  )
113
- # send the fax using Faraday below
133
+ # send the fax using Faraday as shown below
134
+ ```
114
135
 
115
- # 2) Fax Helper for File as Raw Bytes (e.g. PDF or TIFF)
136
+ ### 2) Fax Helper for File as Raw Bytes (e.g. PDF or TIFF)
116
137
 
117
- # Sending a file as a plain octet-stream is useful in
118
- # production as it can decrease file size by 30%.
138
+ Sending a file as a plain octet-stream is useful in production as it can decrease file size by 30%.
139
+
140
+ ```ruby
119
141
 
120
142
  fax = RingCentralSdk::Helpers::CreateFaxRequest.new(
121
- { account_id => '~', extension_id => '~' }, # Can be nil or {} for defaults '~'
143
+ { :account_id => '~', :extension_id => '~' }, # Can be nil or {} for defaults '~'
122
144
  {
123
145
  # phone numbers are in E.164 format with or without leading '+'
124
146
  :to => [{ :phoneNumber => '+16505551212' }],
@@ -127,15 +149,16 @@ fax = RingCentralSdk::Helpers::CreateFaxRequest.new(
127
149
  },
128
150
  :file_name => '/path/to/my_file.pdf'
129
151
  )
130
- # send the fax using Faraday below
152
+ # send the fax using Faraday as shown below
153
+ ```
131
154
 
132
- # 3) Fax Helper for File Base64 Encoded (e.g. PDF or TIFF)
155
+ ### 3) Fax Helper for File Base64 Encoded (e.g. PDF or TIFF)
133
156
 
134
- # Sending a file base64 encoded is useful for debugging
135
- # purposes as the file can be copy and pasted.
157
+ Sending a file base64 encoded is useful for debugging purposes as the file can be copy and pasted.
136
158
 
159
+ ```ruby
137
160
  fax = RingCentralSdk::Helpers::CreateFaxRequest.new(
138
- { account_id => '~', extension_id => '~' }, # Can be nil or {} for defaults '~'
161
+ { :account_id => '~', :extension_id => '~' }, # Can be nil or {} for defaults '~'
139
162
  {
140
163
  # phone numbers are in E.164 format with or without leading '+'
141
164
  :to => [{ :phoneNumber => '+16505551212' }],
@@ -145,10 +168,12 @@ fax = RingCentralSdk::Helpers::CreateFaxRequest.new(
145
168
  :file_name => '/path/to/my_file.tif',
146
169
  :base64_encode => true
147
170
  )
148
- # send the fax using Faraday below
171
+ # send the fax using Faraday as shown below
172
+ ```
149
173
 
150
- # Sending the fax
174
+ ### Sending the fax
151
175
 
176
+ ```ruby
152
177
  response = client.post do |req|
153
178
  req.url fax.url
154
179
  req.headers['Content-Type'] = fax.content_type
data/VERSION.txt CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
@@ -1,3 +1,4 @@
1
1
  module RingCentralSdk::Helpers
2
2
  autoload :CreateFaxRequest, 'ringcentral_sdk/helpers/fax'
3
+ autoload :Request, 'ringcentral_sdk/helpers/request'
3
4
  end
@@ -1,10 +1,10 @@
1
1
  require 'base64'
2
2
  require 'mime'
3
- require 'mime-types'
3
+ require 'mime/types'
4
4
  require 'multi_json'
5
5
 
6
6
  module RingCentralSdk::Helpers
7
- class CreateFaxRequest
7
+ class CreateFaxRequest < RingCentralSdk::Helpers::Request
8
8
  attr_reader :msg
9
9
 
10
10
  def initialize(path_params=nil,metadata=nil,options=nil)
@@ -60,12 +60,18 @@ module RingCentralSdk::Helpers
60
60
  content_type = (content_type.is_a?(String) && content_type =~ /^[^\/\s]+\/[^\/\s]+/) \
61
61
  ? content_type : MIME::Types.type_for(file_name).first.content_type
62
62
 
63
+ base_name = File.basename(file_name)
63
64
  file_base64 = Base64.encode64(File.binread(file_name))
64
65
 
65
66
  base64_part = MIME::Text.new(file_base64)
66
67
  base64_part.headers.delete('Content-Id')
67
- base64_part.headers.set('Content-Type',content_type)
68
+ base64_part.headers.set('Content-Type', content_type)
68
69
  base64_part.headers.set('Content-Transfer-Encoding','base64')
70
+ if base_name.is_a?(String) && base_name.length>0
71
+ base64_part.headers.set('Content-Disposition', "attachment; filename=\"#{base_name}\"")
72
+ else
73
+ base64_part.headers.set('Content-Disposition', 'attachment')
74
+ end
69
75
 
70
76
  @msg.add(base64_part)
71
77
  return true
@@ -76,12 +82,15 @@ module RingCentralSdk::Helpers
76
82
  return false
77
83
  end
78
84
 
85
+ content_type = (content_type.is_a?(String) && content_type =~ /^[^\/\s]+\/[^\/\s]+/) \
86
+ ? content_type : MIME::Types.type_for(file_name).first.content_type
87
+
79
88
  base_name = File.basename(file_name)
80
89
  file_bytes = File.binread(file_name)
81
90
 
82
91
  file_part = MIME::Application.new(file_bytes)
83
92
  file_part.headers.delete('Content-Id')
84
- file_part.headers.set('Content-Type','application/octet-stream')
93
+ file_part.headers.set('Content-Type', content_type)
85
94
  if base_name.is_a?(String) && base_name.length>0
86
95
  file_part.headers.set('Content-Disposition', "attachment; filename=\"#{base_name}\"")
87
96
  else
@@ -92,18 +101,30 @@ module RingCentralSdk::Helpers
92
101
  return true
93
102
  end
94
103
 
104
+ def method()
105
+ return 'post'
106
+ end
107
+
95
108
  def url()
96
109
  account_id = "~"
97
110
  extension_id = "~"
98
111
  if @path_params.is_a?(Hash)
99
- if @path_params.has?(:account_id) && @path_params[:account_id].length>0
100
- account_id = @path_params[:account_id]
112
+ if @path_params.has_key?(:account_id)
113
+ if @path_params[:account_id].is_a?(String) && @path_params[:account_id].length>0
114
+ account_id = @path_params[:account_id]
115
+ elsif @path_params[:account_id].is_a?(Integer) && @path_params[:account_id]>0
116
+ account_id = @path_params[:account_id].to_s
117
+ end
101
118
  end
102
- if @path_params.has?(:extension_id) && @path_params[:extension_id].length>0
103
- account_id = @path_params[:extension_id]
119
+ if @path_params.has_key?(:extension_id)
120
+ if @path_params[:extension_id].is_a?(String) && @path_params[:extension_id].length>0
121
+ extension_id = @path_params[:extension_id]
122
+ elsif @path_params[:extension_id].is_a?(Integer) && @path_params[:extension_id]>0
123
+ extension_id = @path_params[:extension_id].to_s
124
+ end
104
125
  end
105
126
  end
106
- url = "account/#{account_id}/extension/#{extension_id}/fax"
127
+ url = "account/#{account_id.to_s}/extension/#{extension_id.to_s}/fax"
107
128
  return url
108
129
  end
109
130
 
@@ -0,0 +1,16 @@
1
+ module RingCentralSdk::Helpers
2
+ class Request
3
+ def method()
4
+ return 'get'
5
+ end
6
+ def url()
7
+ return ''
8
+ end
9
+ def content_type()
10
+ return ''
11
+ end
12
+ def body()
13
+ return ''
14
+ end
15
+ end
16
+ end
@@ -1,20 +1,36 @@
1
+ require 'time'
2
+
1
3
  module RingCentralSdk::Platform
2
- class Auth
3
- attr_accessor :data
4
- attr_accessor :remember
4
+ class Auth
5
+ attr_accessor :data
6
+ attr_accessor :remember
7
+
8
+ attr_reader :access_token
9
+ attr_reader :token_type
10
+
11
+ def initialize()
12
+ @data = nil
13
+ @remember = nil
14
+ end
15
+ def set_data(data={})
16
+ return false unless data.is_a?(Hash)
17
+
18
+ @access_token = data["access_token"] ? data["access_token"] : ''
19
+ @expire_time = data["expire_time"] ? data["expire_time"] : 0
20
+ @token_type = data["token_type"] ? data["token_type"] : ''
5
21
 
6
- attr_reader :access_token
22
+ @data = data
23
+ return true
24
+ end
7
25
 
8
- def initialize()
9
- @data = nil
10
- @remember = nil
11
- end
12
- def set_data(data={})
13
- return unless data.is_a?(Hash)
26
+ def is_access_token_valid()
27
+ return _is_token_date_valid(@expire_time)
28
+ end
14
29
 
15
- @access_token = data["access_token"] ? data["access_token"] : ''
16
- @token_type = data["token_type"] ? data["token_type"] : ''
30
+ def _is_token_date_valid(token_date=nil)
31
+ return false unless token_date.is_a?(Integer)
32
+ return token_date > Time.now.to_i
33
+ end
17
34
 
18
- end
19
- end
35
+ end
20
36
  end
@@ -22,7 +22,7 @@ module RingCentralSdk::Platform
22
22
  @app_key = app_key
23
23
  @app_secret = app_secret
24
24
  @server_url = server_url
25
- @auth = RingCentralSdk::Platform::Auth.new
25
+ @_auth = RingCentralSdk::Platform::Auth.new
26
26
 
27
27
  @client = Faraday.new(:url => get_api_version_url()) do |conn|
28
28
  conn.request :json
@@ -39,7 +39,7 @@ module RingCentralSdk::Platform
39
39
 
40
40
  def authorize(username='',extension='',password='',remember=false)
41
41
 
42
- response = auth_call({}, {
42
+ response = _auth_call({}, {
43
43
  :grant_type => 'password',
44
44
  :username => username,
45
45
  :extension => extension.is_a?(String) || extension.is_a?(Integer) ? extension : '',
@@ -48,8 +48,8 @@ module RingCentralSdk::Platform
48
48
  :refresh_token_ttl => remember ? REFRESH_TOKEN_TTL_REMEMBER : REFRESH_TOKEN_TTL
49
49
  })
50
50
 
51
- @auth.set_data( response.body )
52
- @auth.remember = remember
51
+ @_auth.set_data( response.body )
52
+ @_auth.remember = remember
53
53
 
54
54
  if response.body.has_key?("access_token") && response.body["access_token"].is_a?(String)
55
55
  @client.headers['Authorization'] = 'Bearer ' + response.body["access_token"]
@@ -58,22 +58,23 @@ module RingCentralSdk::Platform
58
58
  return response
59
59
  end
60
60
 
61
+ #def refresh()
62
+ #end
63
+
61
64
  def get_api_key()
62
- if @app_key.is_a?(String) && @app_secret.is_a?(String)
63
- return Base64.encode64(@app_key + ":" + @app_secret).gsub(/[\s\r\n]/,"")
64
- else
65
- return ''
66
- end
65
+ api_key = (@app_key.is_a?(String) && @app_secret.is_a?(String)) \
66
+ ? Base64.encode64(@app_key + ":" + @app_secret).gsub(/[\s\r\n]/,"") : ''
67
+ return api_key
67
68
  end
68
69
 
69
70
  def get_auth_header()
70
- if @auth.token_type.is_a?(String) && @auth.access_token.is_a?(String)
71
- return @auth.token_type + ' ' + @auth.access_token
71
+ if @_auth.token_type.is_a?(String) && @_auth.access_token.is_a?(String)
72
+ return @_auth.token_type + ' ' + @_auth.access_token
72
73
  end
73
74
  return ''
74
75
  end
75
76
 
76
- def auth_call(queryParams={},body={})
77
+ def _auth_call(queryParams={},body={})
77
78
  return @client.post do |req|
78
79
  req.url TOKEN_ENDPOINT
79
80
  req.headers['Authorization'] = 'Basic ' + get_api_key()
@@ -84,6 +85,22 @@ module RingCentralSdk::Platform
84
85
  end
85
86
  end
86
87
 
87
- private :auth_call, :get_api_key, :get_api_version_url, :get_auth_header
88
+ def request(helper=nil)
89
+ unless helper.is_a?(RingCentralSdk::Helpers::Request)
90
+ raise 'Request is not a RingCentralSdk::Helpers::Request'
91
+ end
92
+
93
+ if helper.method.downcase == 'post'
94
+ resp = @client.post do |req|
95
+ req.url helper.url
96
+ req.headers['Content-Type'] = helper.content_type if helper.content_type
97
+ req.body = helper.body if helper.body
98
+ end
99
+ return resp
100
+ end
101
+ return nil
102
+ end
103
+
104
+ private :_auth_call, :get_api_key, :get_api_version_url, :get_auth_header
88
105
  end
89
106
  end
@@ -7,9 +7,16 @@ module RingCentralSdk
7
7
  attr_reader :parser
8
8
  attr_reader :platform
9
9
 
10
- def initialize(app_key=nil,app_secret=nil,server_url=nil)
10
+ def initialize(app_key=nil,app_secret=nil,server_url=nil,username=nil,extension=nil,password=nil)
11
11
  @parser = RingCentralSdk::Platform::Parser.new
12
12
  @platform = RingCentralSdk::Platform::Platform.new(app_key, app_secret, server_url)
13
+ if not username.nil? and not password.nil?
14
+ @platform.authorize(username, extension, password)
15
+ end
16
+ end
17
+
18
+ def request(helper=nil)
19
+ return @platform.request(helper)
13
20
  end
14
21
  end
15
22
  end
data/test/test_setup.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'test/unit'
2
- require 'ringcentral_sdk'
1
+ require './test/test_helper.rb'
3
2
 
4
3
  class RingCentralSdkTest < Test::Unit::TestCase
5
4
  def testSetup
metadata CHANGED
@@ -1,95 +1,115 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ringcentral_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Wang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-13 00:00:00.000000000 Z
11
+ date: 2015-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
20
- - - ">="
19
+ version: '0.9'
20
+ - - '>='
21
21
  - !ruby/object:Gem::Version
22
- version: '0'
22
+ version: '0.9'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - "~>"
27
+ - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
30
- - - ">="
29
+ version: '0.9'
30
+ - - '>='
31
31
  - !ruby/object:Gem::Version
32
- version: '0'
32
+ version: '0.9'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: faraday_middleware
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ~>
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
- - - ">="
40
+ - - '>='
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - "~>"
47
+ - - ~>
48
48
  - !ruby/object:Gem::Version
49
49
  version: '0'
50
- - - ">="
50
+ - - '>='
51
51
  - !ruby/object:Gem::Version
52
52
  version: '0'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: mime
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
- - - "~>"
57
+ - - ~>
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
- - - ">="
60
+ - - '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - "~>"
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
- - - ">="
70
+ - - '>='
71
71
  - !ruby/object:Gem::Version
72
72
  version: '0'
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: mime-types
75
75
  requirement: !ruby/object:Gem::Requirement
76
76
  requirements:
77
- - - "~>"
77
+ - - ~>
78
78
  - !ruby/object:Gem::Version
79
79
  version: '2.5'
80
- - - ">="
80
+ - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '2.5'
83
83
  type: :runtime
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ~>
88
88
  - !ruby/object:Gem::Version
89
89
  version: '2.5'
90
- - - ">="
90
+ - - '>='
91
91
  - !ruby/object:Gem::Version
92
92
  version: '2.5'
93
+ - !ruby/object:Gem::Dependency
94
+ name: multi_json
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
93
113
  description: An unofficial Ruby SDK for the RingCentral Connect Platform API
94
114
  email: johncwang@gmail.com
95
115
  executables: []
@@ -104,6 +124,7 @@ files:
104
124
  - lib/ringcentral_sdk.rb
105
125
  - lib/ringcentral_sdk/helpers.rb
106
126
  - lib/ringcentral_sdk/helpers/fax.rb
127
+ - lib/ringcentral_sdk/helpers/request.rb
107
128
  - lib/ringcentral_sdk/platform.rb
108
129
  - lib/ringcentral_sdk/platform/auth.rb
109
130
  - lib/ringcentral_sdk/platform/parser.rb
@@ -119,17 +140,17 @@ require_paths:
119
140
  - lib
120
141
  required_ruby_version: !ruby/object:Gem::Requirement
121
142
  requirements:
122
- - - ">="
143
+ - - '>='
123
144
  - !ruby/object:Gem::Version
124
145
  version: '0'
125
146
  required_rubygems_version: !ruby/object:Gem::Requirement
126
147
  requirements:
127
- - - ">="
148
+ - - '>='
128
149
  - !ruby/object:Gem::Version
129
150
  version: '0'
130
151
  requirements: []
131
152
  rubyforge_project:
132
- rubygems_version: 2.2.1
153
+ rubygems_version: 2.1.5
133
154
  signing_key:
134
155
  specification_version: 4
135
156
  summary: RingCentral SDK - Unofficial Ruby SDK for the RingCentral Connect Platform