timetree 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd2a9142e5ee6614ec6e97f691d61a2b054d0db7616487efa84f7ecfa89aab82
4
- data.tar.gz: f41663400a01d61da7e4cb26ef302c415ff238f8bc58c1b54b1d10156934ab6d
3
+ metadata.gz: '08f576804646a18cf1c980d7649f3cf621f7e98871d291bec881ba8232e69e6a'
4
+ data.tar.gz: 3740f58f68f0f6514b59295a7c3e8f57321fc10b4b5ca3fd6515da7b67a48c50
5
5
  SHA512:
6
- metadata.gz: a7793feb335fcee92f1c19733206a67b3f2e94fcef7e809701db8fd31e80835689f4dbb604422223fcd9c2c6848a5a8ea8ece93dc401cfe65a2b99234c1dde65
7
- data.tar.gz: ce9319e7839ce5663e2b15723b5492825332bab5265e7d57ba3d9bd84c59dd3a2b999ffb5de1c77c941e215c226a43699c4b5267aab790539c19e3ce11eb4a8d
6
+ metadata.gz: 5cd3358940be41c9e441ab348249b65628cb489df329687df949ff3f4a48a2749642f31f0cde56170d895acf35f63bd90c30c00d3491efb74ec8323eb501bd29
7
+ data.tar.gz: 8888d348d25e8c67645a50084443c79cba8a6d14c7aa7fbb43ae743abd2be8092b3a5ea5815edd35c115268c60bac1933438177c31f0e5ae0b27821e3b3d02b3
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ NewCops: enable
2
3
  TargetRubyVersion: 2.6
3
4
 
4
5
  Layout/AccessModifierIndentation:
@@ -23,12 +24,11 @@ Metrics/BlockNesting:
23
24
  Max: 2
24
25
 
25
26
  Layout/LineLength:
26
- AllowURI: true
27
- Enabled: false
27
+ Max: 120
28
28
 
29
29
  Metrics/MethodLength:
30
30
  CountComments: false
31
- Max: 15
31
+ Max: 20
32
32
 
33
33
  Metrics/ParameterLists:
34
34
  Max: 4
@@ -39,10 +39,10 @@ Metrics/AbcSize:
39
39
 
40
40
  Style/CollectionMethods:
41
41
  PreferredMethods:
42
- map: 'collect'
43
- reduce: 'inject'
44
- find: 'detect'
45
- find_all: 'select'
42
+ map: "collect"
43
+ reduce: "inject"
44
+ find: "detect"
45
+ find_all: "select"
46
46
 
47
47
  Style/Documentation:
48
48
  Enabled: false
@@ -60,10 +60,13 @@ Style/ExpandPathArguments:
60
60
  Enabled: false
61
61
 
62
62
  Style/HashSyntax:
63
- EnforcedStyle: hash_rockets
63
+ EnforcedStyle: ruby19
64
64
 
65
65
  Style/Lambda:
66
66
  Enabled: false
67
67
 
68
68
  Style/RaiseArgs:
69
- EnforcedStyle: compact
69
+ EnforcedStyle: compact
70
+
71
+ Style/AsciiComments:
72
+ Enabled: false
@@ -1,44 +1,70 @@
1
- # 0.2.0
1
+ # CHANGELOG
2
+
3
+ ## 1.0.0
4
+
5
+ - add Calendar App Client. (refs #28)
6
+ - rename `TimeTree::OAuthApp::Client` from `TimeTree::Client`.
7
+
8
+ ## 0.3.2
9
+
10
+ - remove zeitwerk dependency. (refs #29)
11
+
12
+ ## 0.3.1
13
+
14
+ - fix rubocop warnings.
15
+
16
+ ## 0.3.0
17
+
18
+ - refs #25 fix dependencies.
19
+ - remove minitest-reporter.
20
+
21
+ ## 0.2.1
22
+
23
+ - do refactor
24
+ - use `respond_to?(setter)` instead of `instance_methods.include?(setter)`.
25
+ - use getter method instead of instant variables if defined it.
26
+
27
+ ## 0.2.0
2
28
 
3
29
  - organize model classes.
4
30
 
5
- # 0.1.7
31
+ ## 0.1.7
6
32
 
7
33
  - refs #16 correct a mistake codecov badge url on README
8
34
 
9
- # 0.1.6
35
+ ## 0.1.6
10
36
 
11
37
  - refs #16 add code coverage to readme.
12
38
  - refs #17 update deprecated `version` for `ruby-version` on gem-push.yml.
13
39
 
14
- # 0.1.5
40
+ ## 0.1.5
15
41
 
16
42
  - refs #6 setup GitHub Actions.
17
43
 
18
- # 0.1.4
44
+ ## 0.1.4
19
45
 
20
46
  - updates comments.
21
47
 
22
- # 0.1.3
48
+ ## 0.1.3
23
49
 
24
50
  - refs #7 #9 make the code coverage 100%.
25
51
 
26
- # 0.1.2
52
+ ## 0.1.2
27
53
 
28
54
  - refs #1 fix typo Event#recurrences to recurrence
29
55
  - refs #2 add a note for debugging guide on README
30
56
 
31
- # 0.1.1
57
+ ## 0.1.1
32
58
 
33
59
  - set current version on this gem's documentation_uri.
34
60
  - fixed typo on README.
35
61
 
36
- # 0.1.0
62
+ ## 0.1.0
37
63
 
38
64
  - did refactor and wrote comments.
39
65
  - started to use zeitwerk.
40
66
  - wrote README.
41
67
 
42
- # 0.0.1
68
+ ## 0.0.1
43
69
 
44
70
  - Initial release
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Simple TimeTree APIs client
1
+ # TimeTree APIs client
2
2
 
3
3
  [![Test](https://github.com/koshilife/timetree-api-ruby-client/workflows/Test/badge.svg)](https://github.com/koshilife/timetree-api-ruby-client/actions?query=workflow%3ATest)
4
4
  [![codecov](https://codecov.io/gh/koshilife/timetree-api-ruby-client/branch/master/graph/badge.svg)](https://codecov.io/gh/koshilife/timetree-api-ruby-client)
@@ -25,60 +25,85 @@ Or install it yourself as:
25
25
 
26
26
  $ gem install timetree
27
27
 
28
- ## Usage
28
+ ## Usage for Calendar App
29
29
 
30
- The APIs client needs access token.
31
- Set a `token` variable to the value you got by above:
30
+ The APIs client for Calendar App needs installation_id, application_id and private key.
32
31
 
33
32
  ```ruby
34
33
  # set token by TimeTree.configure methods.
35
34
  TimeTree.configure do |config|
36
- config.token = '<YOUR_ACCESS_TOKEN>'
35
+ config.calendar_app_application_id = '<YOUR_APPLICATION_ID>'
36
+ config.calendar_app_private_key = File.read('<YOUR_PATH_TO_PEM>')
37
37
  end
38
- client = TimeTree::Client.new
38
+ client = TimeTree::CalendarApp::Client.new('<INSTALLATION_ID>')
39
39
 
40
- # set token by TimeTree::Client initializer.
41
- client = TimeTree::Client.new('<YOUR_ACCESS_TOKEN>')
40
+ # set token by TimeTree::CalendarApp::Client initializer.
41
+ client = TimeTree::CalendarApp::Client.new('<INSTALLATION_ID>', '<YOUR_APPLICATION_ID>', '<YOUR_PRIVATE_KEY_CONTENT>')
42
+
43
+ # get connected calendar's information.
44
+ cal = client.calendar
45
+ # => #<TimeTree::Calendar id:xxx_cal001>
46
+
47
+ # get upcoming events on the calendar.
48
+ evs = cal.upcoming_events
49
+ # => [#<TimeTree::Event id:xxx_ev001>, #<TimeTree::Event id:xxx_ev002>, ...]
50
+ ev = evs.first.title
51
+ # => "Event Title"
52
+ ```
53
+
54
+ ## Usage for OAuth App
55
+
56
+ The APIs client for OAuth App needs access token.
57
+
58
+ ```ruby
59
+ # set token by TimeTree.configure methods.
60
+ TimeTree.configure do |config|
61
+ config.oauth_app_token = '<YOUR_ACCESS_TOKEN>'
62
+ end
63
+ client = TimeTree::OAuthApp::Client.new
64
+
65
+ # set token by TimeTree::OAuthApp::Client initializer.
66
+ client = TimeTree::OAuthApp::Client.new('<YOUR_ACCESS_TOKEN>')
42
67
 
43
68
  # get a current user's information.
44
69
  user = client.current_user
45
- => #<TimeTree::User id:xxx_u001>
70
+ # => #<TimeTree::User id:xxx_u001>
46
71
  user.name
47
- => "USER Name"
72
+ # => "USER Name"
48
73
 
49
74
  # get current user's calendars.
50
75
  cals = client.calendars
51
- => [#<TimeTree::Calendar id:xxx_cal001>, #<TimeTree::Calendar id:xxx_cal002>, ...]
76
+ # => [#<TimeTree::Calendar id:xxx_cal001>, #<TimeTree::Calendar id:xxx_cal002>, ...]
52
77
  cal = cals.first
53
78
  cal.name
54
- => "Calendar Name"
79
+ # => "Calendar Name"
55
80
 
56
81
  # get upcoming events on the calendar.
57
82
  evs = cal.upcoming_events
58
- => [#<TimeTree::Event id:xxx_ev001>, #<TimeTree::Event id:xxx_ev002>, ...]
83
+ # => [#<TimeTree::Event id:xxx_ev001>, #<TimeTree::Event id:xxx_ev002>, ...]
59
84
  ev = evs.first
60
85
  ev.title
61
- => "Event Title"
86
+ # => "Event Title"
62
87
 
63
88
  # updates an event.
64
89
  ev.title += ' Updated'
65
90
  ev.start_at = Time.parse('2020-06-20 09:00 +09:00')
66
91
  ev.end_at = Time.parse('2020-06-20 10:00 +09:00')
67
92
  ev.update
68
- => #<TimeTree::Event id:xxx_ev001>
93
+ # => #<TimeTree::Event id:xxx_ev001>
69
94
 
70
95
  # creates an event.
71
96
  copy_ev = ev.dup
72
97
  new_ev = copy_ev.create
73
- => #<TimeTree::Event id:xxx_new_ev001>
98
+ # => #<TimeTree::Event id:xxx_new_ev001>
74
99
 
75
100
  # deletes an event.
76
101
  ev.delete
77
- => true
102
+ # => true
78
103
 
79
104
  # creates a comment to an event.
80
105
  ev.create_comment 'Hi there!'
81
- => #<TimeTree::Activity id:xxx_act001>
106
+ # => #<TimeTree::Activity id:xxx_act001>
82
107
 
83
108
  # handles APIs error.
84
109
  begin
@@ -90,7 +115,11 @@ rescue TimeTree::ApiError => e
90
115
  e.response
91
116
  => #<Faraday::Response>
92
117
  end
118
+ ```
119
+
120
+ ## Logging
93
121
 
122
+ ```ruby
94
123
  # if the log level set :debug, you can get the request/response information.
95
124
  TimeTree.configuration.logger.level = :debug
96
125
  => #<TimeTree::Event id:event_id_001_not_found>
@@ -99,6 +128,8 @@ I, [2020-06-24T10:05:07.294807] INFO -- : GET https://timetreeapis.com/calendar
99
128
  D, [2020-06-24T10:05:07.562038] DEBUG -- : Response status:404, body:{:type=>"https://developers.timetreeapp.com/en/docs/api#client-failure", :title=>"Not Found", :status=>404, :errors=>"Event not found"}
100
129
  ```
101
130
 
131
+ More in-depth method documentation can be found at [RubyDoc.info](https://www.rubydoc.info/gems/timetree/).
132
+
102
133
  ## Contributing
103
134
 
104
135
  Bug reports and pull requests are welcome on [GitHub](https://github.com/koshilife/timetree-api-ruby-client). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
3
5
 
4
6
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'zeitwerk'
4
- loader = Zeitwerk::Loader.for_gem
5
- loader.inflector.inflect(
6
- 'timetree' => 'TimeTree'
7
- )
8
- loader.collapse('**/models')
9
- loader.setup
3
+ Dir[
4
+ File.join(
5
+ File.dirname(__FILE__),
6
+ 'timetree',
7
+ '**',
8
+ '*'
9
+ )
10
+ ].sort.each do |f|
11
+ next if File.directory? f
12
+
13
+ require f
14
+ end
10
15
 
11
16
  # module for TimeTree apis client
12
17
  module TimeTree
@@ -23,7 +23,7 @@ module TimeTree
23
23
  end
24
24
 
25
25
  def inspect
26
- "\#<#{self.class}:#{object_id} title:#{@title}, status:#{@status}>"
26
+ "\#<#{self.class}:#{object_id} title:#{title}, status:#{status}>"
27
27
  end
28
28
  end
29
29
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TimeTree
4
+ class BaseClient
5
+ API_HOST = 'https://timetreeapis.com'
6
+ # @return [Integer]
7
+ attr_reader :ratelimit_limit
8
+ # @return [Integer]
9
+ attr_reader :ratelimit_remaining
10
+ # @return [Time]
11
+ attr_reader :ratelimit_reset_at
12
+
13
+ #
14
+ # update ratelimit properties
15
+ #
16
+ # @param res [Faraday::Response]
17
+ # apis http response.
18
+ def update_ratelimit(res)
19
+ limit = res.headers['x-ratelimit-limit']
20
+ remaining = res.headers['x-ratelimit-remaining']
21
+ reset = res.headers['x-ratelimit-reset']
22
+ @ratelimit_limit = limit.to_i if limit
23
+ @ratelimit_remaining = remaining.to_i if remaining
24
+ @ratelimit_reset_at = Time.at reset.to_i if reset
25
+ end
26
+
27
+ private
28
+
29
+ def check_event_id(value)
30
+ check_required_property(value, 'event_id')
31
+ end
32
+
33
+ def check_required_property(value, name)
34
+ err = Error.new "#{name} is required."
35
+ raise err if value.nil?
36
+ raise err if value.to_s.empty?
37
+
38
+ true
39
+ end
40
+
41
+ def to_model(data, included: nil)
42
+ TimeTree::BaseModel.to_model data, client: self, included: included
43
+ end
44
+
45
+ def relationships_params(relationships, default)
46
+ params = {}
47
+ relationships ||= default
48
+ params[:include] = relationships.join ',' if relationships.is_a? Array
49
+ params
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TimeTree
4
+ module CalendarApp
5
+ class AccessToken
6
+ # @return [String]
7
+ attr_reader :token
8
+ # @return [Integer]
9
+ attr_reader :expire_at
10
+
11
+ def initialize(token, expire_at)
12
+ @token = token
13
+ @expire_at = expire_at
14
+ end
15
+
16
+ #
17
+ # Returns the access token is expired or not.
18
+ #
19
+ # @return [Boolean]
20
+ def expired?
21
+ Time.now.to_i > expire_at
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'jwt'
5
+
6
+ module TimeTree
7
+ module CalendarApp
8
+ # TimeTree API CalendarApp client.
9
+ class Client < BaseClient
10
+ # @return [Integer]
11
+ attr_reader :installation_id
12
+ # @return [String]
13
+ attr_reader :application_id
14
+ # @return [String]
15
+ attr_reader :private_key
16
+ # @return [String]
17
+ attr_reader :token
18
+
19
+ # @param installation_id [Integer] CalendarApp's installation id
20
+ # @param application_id [String] CalendarApp id
21
+ # @param private_key [String] RSA private key for CalendarApp
22
+ def initialize(installation_id, application_id = nil, private_key = nil)
23
+ @installation_id = installation_id
24
+ @application_id = application_id || TimeTree.configuration.calendar_app_application_id
25
+ @private_key = OpenSSL::PKey::RSA.new((private_key || TimeTree.configuration.calendar_app_private_key).to_s)
26
+ check_client_requirement
27
+ @http_cmd = HttpCommand.new(API_HOST, self)
28
+ rescue OpenSSL::PKey::RSAError
29
+ raise Error.new 'private_key must be RSA private key.'
30
+ end
31
+
32
+ #
33
+ # Get a calendar information related to CalendarApp
34
+ #
35
+ # @param include_relationships [Array<symbol>]
36
+ # includes association's object in the response.
37
+ # @return [TimeTree::Calendar]
38
+ # @raise [TimeTree::ApiError] if the http response status will not success.
39
+ # @since 1.0.0
40
+ def calendar(include_relationships: nil)
41
+ check_access_token
42
+ params = relationships_params(include_relationships, Calendar::RELATIONSHIPS)
43
+ res = http_cmd.get('/calendar', params)
44
+ raise ApiError.new(res) if res.status != 200
45
+
46
+ to_model(res.body[:data], included: res.body[:included])
47
+ end
48
+
49
+ #
50
+ # Get a calendar's member information.
51
+ #
52
+ # @return [Array<TimeTree::User>]
53
+ # @raise [TimeTree::ApiError] if the http response status will not success.
54
+ # @since 1.0.0
55
+ def calendar_members
56
+ check_access_token
57
+ res = http_cmd.get('/calendar/members')
58
+ raise ApiError.new(res) if res.status != 200
59
+
60
+ res.body[:data].map { |item| to_model(item) }
61
+ end
62
+
63
+ #
64
+ # Get an event's information.
65
+ #
66
+ # @param event_id [String] event's id.
67
+ # @param include_relationships [Array<symbol>]
68
+ # includes association's object in the response.
69
+ # @return [TimeTree::Event]
70
+ # @raise [TimeTree::Error] if the event_id arg is empty.
71
+ # @raise [TimeTree::ApiError] if the http response status will not success.
72
+ # @since 1.0.0
73
+ def event(event_id, include_relationships: nil)
74
+ check_event_id event_id
75
+ check_access_token
76
+ params = relationships_params(include_relationships, Event::RELATIONSHIPS)
77
+ res = http_cmd.get("/calendar/events/#{event_id}", params)
78
+ raise ApiError.new(res) if res.status != 200
79
+
80
+ to_model(res.body[:data], included: res.body[:included])
81
+ end
82
+
83
+ #
84
+ # Get events' information after a request date.
85
+ #
86
+ # @param days [Integer] The number of days to get.
87
+ # @param timezone [String] Timezone.
88
+ # @param include_relationships [Array<symbol>]
89
+ # includes association's object in the response.
90
+ # @return [Array<TimeTree::Event>]
91
+ # @raise [TimeTree::ApiError] if the http response status will not success.
92
+ # @since 1.0.0
93
+ def upcoming_events(days: 7, timezone: 'UTC', include_relationships: nil)
94
+ check_access_token
95
+ params = relationships_params(include_relationships, Event::RELATIONSHIPS)
96
+ params.merge!(days: days, timezone: timezone)
97
+ res = http_cmd.get('/calendar/upcoming_events', params)
98
+ raise ApiError.new(res) if res.status != 200
99
+
100
+ included = res.body[:included]
101
+ res.body[:data].map { |item| to_model(item, included: included) }
102
+ end
103
+
104
+ #
105
+ # Creates an event.
106
+ #
107
+ # @param params [Hash] TimeTree request body format.
108
+ # @return [TimeTree::Event]
109
+ # @raise [TimeTree::Error] if the cal_id arg is empty.
110
+ # @raise [TimeTree::ApiError] if the http response status will not success.
111
+ # @since 1.0.0
112
+ def create_event(params)
113
+ check_access_token
114
+ res = http_cmd.post('/calendar/events', params)
115
+ raise ApiError.new(res) if res.status != 201
116
+
117
+ to_model(res.body[:data])
118
+ end
119
+
120
+ #
121
+ # Updates an event.
122
+ #
123
+ # @param event_id [String] event's id.
124
+ # @param params [Hash]
125
+ # event's information specified in TimeTree request body format.
126
+ # @return [TimeTree::Event]
127
+ # @raise [TimeTree::Error] if the event_id arg is empty.
128
+ # @raise [TimeTree::ApiError] if the http response status will not success.
129
+ # @since 1.0.0
130
+ def update_event(event_id, params)
131
+ check_event_id event_id
132
+ check_access_token
133
+ res = http_cmd.put("/calendar/events/#{event_id}", params)
134
+ raise ApiError.new(res) if res.status != 200
135
+
136
+ to_model(res.body[:data])
137
+ end
138
+
139
+ #
140
+ # Deletes an event.
141
+ #
142
+ # @param event_id [String] event's id.
143
+ # @return [true] if the operation succeeded.
144
+ # @raise [TimeTree::Error] if the event_id arg is empty.
145
+ # @raise [TimeTree::ApiError] if the http response status will not success.
146
+ # @since 1.0.0
147
+ def delete_event(event_id)
148
+ check_event_id event_id
149
+ check_access_token
150
+ res = http_cmd.delete("/calendar/events/#{event_id}")
151
+ raise ApiError.new(res) if res.status != 204
152
+
153
+ true
154
+ end
155
+
156
+ #
157
+ # Creates a comment.
158
+ #
159
+ # @param event_id [String] event's id.
160
+ # @param params [Hash]
161
+ # comment's information specified in TimeTree request body format.
162
+ # @return [TimeTree::Activity]
163
+ # @raise [TimeTree::Error] if the event_id arg is empty.
164
+ # @raise [TimeTree::ApiError] if the http response status is not success.
165
+ # @since 1.0.0
166
+ def create_activity(event_id, params)
167
+ check_event_id event_id
168
+ check_access_token
169
+ res = http_cmd.post("/calendar/events/#{event_id}/activities", params)
170
+ raise ApiError.new(res) if res.status != 201
171
+
172
+ activity = to_model(res.body[:data])
173
+ activity.event_id = event_id
174
+ activity
175
+ end
176
+
177
+ def inspect
178
+ limit_info = nil
179
+ if defined?(@ratelimit_limit) && @ratelimit_limit
180
+ limit_info = " ratelimit:#{ratelimit_remaining}/#{ratelimit_limit}"
181
+ end
182
+ if defined?(@ratelimit_reset_at) && @ratelimit_reset_at
183
+ limit_info = "#{limit_info}, reset_at:#{ratelimit_reset_at.strftime('%m/%d %R')}"
184
+ end
185
+ "\#<#{self.class}:#{object_id}#{limit_info}>"
186
+ end
187
+
188
+ private
189
+
190
+ attr_reader :http_cmd, :access_token
191
+
192
+ def check_client_requirement
193
+ check_required_property(installation_id, 'installation_id')
194
+ check_required_property(application_id, 'application_id')
195
+ end
196
+
197
+ def check_access_token
198
+ return if access_token?
199
+
200
+ get_access_token
201
+ end
202
+
203
+ def access_token?
204
+ access_token && !access_token.expired?
205
+ end
206
+
207
+ def get_access_token
208
+ res = http_cmd.post("/installations/#{installation_id}/access_tokens") do |req|
209
+ req.headers['Authorization'] = "Bearer #{jwt}"
210
+ end
211
+ raise ApiError.new(res) if res.status != 200
212
+
213
+ @access_token = AccessToken.new(res.body[:access_token], res.body[:expire_at])
214
+ @token = access_token.token
215
+ end
216
+
217
+ def jwt
218
+ now = Time.now.to_i
219
+ payload = {
220
+ iat: now,
221
+ exp: now + (10 * 60), # JWT expires in 10 minutes
222
+ iss: application_id
223
+ }
224
+ JWT.encode(payload, private_key, 'RS256')
225
+ end
226
+ end
227
+ end
228
+ end