rimesync 0.1.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 +7 -0
- data/lib/rimesync.rb +1010 -0
- data/lib/rimesync/mock_rimesync.rb +425 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: c1489e1e1923737e6266ce70528709b63b602fcc
|
|
4
|
+
data.tar.gz: 47eef1df05562b8642410f7fa95232ab4e30ad1b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 193aa6cee72c28c8b1665e7d9de8f04b6a929a5052ffb0d16771b7643ed9d138f2eb1eb3596a902e625c2ef0905e2b7ed2c89cfb76590210207f53c4eff0d2e5
|
|
7
|
+
data.tar.gz: b045234b3d165cabf9ac69b68519b37f4de0db08d0d62377d2b57b934adb85f5e2e1d3e2698c1928166ec5d70e1cafb4ae0ab2edb2e15a542f83c71d642b1079
|
data/lib/rimesync.rb
ADDED
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
# rimesync - Ruby Port of rimesync
|
|
2
|
+
|
|
3
|
+
# Allows for interactions with the TimeSync API
|
|
4
|
+
|
|
5
|
+
# - authenticate(username, password, auth_type) - Authorizes user with TimeSync
|
|
6
|
+
# - token_expiration_time - Returns datetime expiration of user authentication
|
|
7
|
+
# - create_time(time) - Sends time to baseurl (TimeSync)
|
|
8
|
+
# - update_time(time, uuid) - Updates time by uuid
|
|
9
|
+
# - create_project(project) - Creates project
|
|
10
|
+
# - update_project(project, slug) - Updates project by slug
|
|
11
|
+
# - create_activity(activity) - Creates activity
|
|
12
|
+
# - update_activity(activity, slug) - Updates activity by slug
|
|
13
|
+
# - create_user(user) - Creates a user
|
|
14
|
+
# - update_user(user, username) - Updates user by username
|
|
15
|
+
# - get_times(query_parameters) - Get times from TimeSync
|
|
16
|
+
# - get_projects(query_parameters) - Get project information from TimeSync
|
|
17
|
+
# - get_activities(query_parameters) - Get activity information from TimeSync
|
|
18
|
+
# - get_users(username) - Get user information from TimeSync
|
|
19
|
+
|
|
20
|
+
# Supported TimeSync versions:
|
|
21
|
+
# v0
|
|
22
|
+
|
|
23
|
+
require 'json'
|
|
24
|
+
require_relative 'rimesync/mock_rimesync'
|
|
25
|
+
require 'bcrypt'
|
|
26
|
+
require 'base64'
|
|
27
|
+
require 'rest-client'
|
|
28
|
+
|
|
29
|
+
# workaround for making `''.is_a? Boolean` work
|
|
30
|
+
module Boolean
|
|
31
|
+
end
|
|
32
|
+
class TrueClass
|
|
33
|
+
include Boolean
|
|
34
|
+
end
|
|
35
|
+
class FalseClass
|
|
36
|
+
include Boolean
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class TimeSync
|
|
40
|
+
def initialize(baseurl, token = nil, test = false)
|
|
41
|
+
@baseurl = if baseurl.end_with? '/'
|
|
42
|
+
baseurl.chop
|
|
43
|
+
else
|
|
44
|
+
baseurl
|
|
45
|
+
end
|
|
46
|
+
@user = nil
|
|
47
|
+
@password = nil
|
|
48
|
+
@auth_type = nil
|
|
49
|
+
@token = token
|
|
50
|
+
@error = 'rimesync error'
|
|
51
|
+
@test = test
|
|
52
|
+
|
|
53
|
+
@valid_get_queries = Array['user', 'project', 'activity',
|
|
54
|
+
'start', 'end', 'include_revisions',
|
|
55
|
+
'include_deleted', 'uuid']
|
|
56
|
+
@required_params = Hash[
|
|
57
|
+
'time' => %w(duration project user date_worked),
|
|
58
|
+
'project' => %w(name slugs),
|
|
59
|
+
'activity' => %w(name slug),
|
|
60
|
+
'user' => %w(username password)
|
|
61
|
+
]
|
|
62
|
+
@optional_params = Hash[
|
|
63
|
+
'time' => %w(notes issue_uri activities),
|
|
64
|
+
'project' => %w(uri users default_activity),
|
|
65
|
+
'activity' => [],
|
|
66
|
+
'user' => %w(display_name email site_admin site_spectator
|
|
67
|
+
site_manager meta active)
|
|
68
|
+
]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# authenticate(username, password, auth_type)
|
|
72
|
+
# Authenticate a username and password with TimeSync via a POST request
|
|
73
|
+
# to the login endpoint. This method will return a list containing a
|
|
74
|
+
# single ruby hash. If successful, the hash will contain
|
|
75
|
+
# the token in the form [{'token': 'SOMETOKEN'}]. If an error is returned
|
|
76
|
+
# the hash will contain the error information.
|
|
77
|
+
# ``username`` is a string containing the username of the TimeSync user
|
|
78
|
+
# ``password`` is a string containing the user's password
|
|
79
|
+
# ``auth_type`` is a string containing the authentication method used by
|
|
80
|
+
# TimeSync
|
|
81
|
+
# rubocop:disable MethodLength
|
|
82
|
+
def authenticate(username: nil, password: nil, auth_type: nil)
|
|
83
|
+
# Check for correct arguments in method call
|
|
84
|
+
arg_error_list = Array[]
|
|
85
|
+
|
|
86
|
+
arg_error_list.push('username') if username.nil?
|
|
87
|
+
|
|
88
|
+
arg_error_list.push('password') if password.nil?
|
|
89
|
+
|
|
90
|
+
arg_error_list.push('auth_type') if auth_type.nil?
|
|
91
|
+
|
|
92
|
+
unless arg_error_list.empty?
|
|
93
|
+
return Hash[@error =>
|
|
94
|
+
format('Missing %s; please add to method call',
|
|
95
|
+
arg_error_list.join(', '))]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
arg_error_list.clear
|
|
99
|
+
|
|
100
|
+
@user = username
|
|
101
|
+
@password = password
|
|
102
|
+
@auth_type = auth_type
|
|
103
|
+
|
|
104
|
+
# Create the auth block to send to the login endpoint
|
|
105
|
+
auth_hash = Hash['auth' => auth].to_json
|
|
106
|
+
|
|
107
|
+
# Construct the url with the login endpoint
|
|
108
|
+
url = format('%s/login', @baseurl)
|
|
109
|
+
|
|
110
|
+
# Test mode, set token and return it from the mocked method
|
|
111
|
+
if @test
|
|
112
|
+
@token = 'TESTTOKEN'
|
|
113
|
+
m = MockTimeSync.new
|
|
114
|
+
return m.authenticate
|
|
115
|
+
# return mock_rimesync.authenticate
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Send the request, then convert the resonse to a ruby hash
|
|
119
|
+
begin
|
|
120
|
+
# Success!
|
|
121
|
+
response = RestClient.post(url, auth_hash, content_type: :json,
|
|
122
|
+
accept: :json)
|
|
123
|
+
token_response = response_to_ruby(response.body, response.code)
|
|
124
|
+
rescue => e
|
|
125
|
+
err_msg = format('connection to TimeSync failed at baseurl %s - ', @baseurl)
|
|
126
|
+
err_msg += format('response status was %s', e.http_code)
|
|
127
|
+
return Hash[@error => err_msg]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# If TimeSync returns an error, return the error without setting the
|
|
131
|
+
# token.
|
|
132
|
+
# Else set the token to the returned token and return the dict.
|
|
133
|
+
if token_response.key?('error') || !token_response.key?('token')
|
|
134
|
+
return token_response
|
|
135
|
+
else
|
|
136
|
+
@token = token_response['token']
|
|
137
|
+
return token_response
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# create_time(time)
|
|
142
|
+
# Send a time entry to TimeSync via a POST request in a JSON body. This
|
|
143
|
+
# method will return that body in the form of a list containing a single
|
|
144
|
+
# ruby hash. The hash will contain a representation of that
|
|
145
|
+
# JSON body if it was successful or error information if it was not.
|
|
146
|
+
# ``time`` is a ruby hash containing the time information to send
|
|
147
|
+
# to TimeSync.
|
|
148
|
+
def create_time(time)
|
|
149
|
+
unless time['duration'].is_a? Integer
|
|
150
|
+
duration = duration_to_seconds(time['duration'])
|
|
151
|
+
time['duration'] = duration
|
|
152
|
+
|
|
153
|
+
# Duration at this point contains an error_msg if it's not an int
|
|
154
|
+
return duration unless time['duration'].is_a? Integer
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if time['duration'] < 0
|
|
158
|
+
return Hash[@error => 'time object: duration cannot be negative']
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
create_or_update(time, nil, 'time', 'times')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# update_time(time, uuid)
|
|
165
|
+
# Send a time entry update to TimeSync via a POST request in a JSON body.
|
|
166
|
+
# This method will return that body in the form of a list containing a
|
|
167
|
+
# single ruby hash. The hash will contain a representation
|
|
168
|
+
# of that updated time object if it was successful or error information
|
|
169
|
+
# if it was not.
|
|
170
|
+
# ``time`` is a ruby hash containing the time information to send
|
|
171
|
+
# to TimeSync.
|
|
172
|
+
# ``uuid`` contains the uuid for a time entry to update.
|
|
173
|
+
def update_time(time, uuid)
|
|
174
|
+
if time.key?('duration')
|
|
175
|
+
unless time['duration'].is_a? Integer
|
|
176
|
+
duration = duration_to_seconds(time['duration'])
|
|
177
|
+
time['duration'] = duration
|
|
178
|
+
|
|
179
|
+
# Duration at this point contains an error_msg if not an int
|
|
180
|
+
return duration unless time['duration'].is_a? Integer
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if time['duration'] < 0
|
|
184
|
+
return Hash[@error => 'time object: duration cannot be negative']
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
end
|
|
188
|
+
create_or_update(time, uuid, 'time', 'times', false)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# create_project(project)
|
|
192
|
+
# Post a project to TimeSync via a POST request in a JSON body. This
|
|
193
|
+
# method will return that body in the form of a list containing a single
|
|
194
|
+
# ruby hash. The hash will contain a representation of that
|
|
195
|
+
# JSON body if it was successful or error information if it was not.
|
|
196
|
+
# ``project`` is a ruby hash containing the project information
|
|
197
|
+
# to send to TimeSync.
|
|
198
|
+
def create_project(project)
|
|
199
|
+
create_or_update(project, nil, 'project', 'projects')
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# update_project(project, slug)
|
|
203
|
+
# Send a project update to TimeSync via a POST request in a JSON body.
|
|
204
|
+
# This method will return that body in the form of a list containing a
|
|
205
|
+
# single ruby hash. The hash will contain a representation
|
|
206
|
+
# of that updated project object if it was successful or error
|
|
207
|
+
# information if it was not.
|
|
208
|
+
# ``project`` is a ruby hash containing the project information
|
|
209
|
+
# to send to TimeSync.
|
|
210
|
+
# ``slug`` contains the slug for a project entry to update.
|
|
211
|
+
def update_project(project, slug)
|
|
212
|
+
create_or_update(project, slug, 'project', 'projects',
|
|
213
|
+
false)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# create_activity(activity, slug=nil)
|
|
217
|
+
# Post an activity to TimeSync via a POST request in a JSON body. This
|
|
218
|
+
# method will return that body in the form of a list containing a single
|
|
219
|
+
# ruby hash. The hash will contain a representation of that
|
|
220
|
+
# JSON body if it was successful or error information if it was not.
|
|
221
|
+
# ``activity`` is a ruby hash containing the activity information
|
|
222
|
+
# to send to TimeSync.
|
|
223
|
+
def create_activity(activity)
|
|
224
|
+
create_or_update(activity, nil,
|
|
225
|
+
'activity', 'activities')
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# update_activity(activity, slug)
|
|
229
|
+
# Send an activity update to TimeSync via a POST request in a JSON body.
|
|
230
|
+
# This method will return that body in the form of a list containing a
|
|
231
|
+
# single ruby hash. The hash will contain a representation
|
|
232
|
+
# of that updated activity object if it was successful or error
|
|
233
|
+
# information if it was not.
|
|
234
|
+
# ``activity`` is a ruby hash containing the project information
|
|
235
|
+
# to send to TimeSync.
|
|
236
|
+
# ``slug`` contains the slug for an activity entry to update.
|
|
237
|
+
def update_activity(activity, slug)
|
|
238
|
+
create_or_update(activity, slug,
|
|
239
|
+
'activity', 'activities',
|
|
240
|
+
false)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# create_user(user)
|
|
244
|
+
# Post a user to TimeSync via a POST request in a JSON body. This
|
|
245
|
+
# method will return that body in the form of a list containing a single
|
|
246
|
+
# ruby hash. The hash will contain a representation of that
|
|
247
|
+
# JSON body if it was successful or error information if it was not.
|
|
248
|
+
# ``user`` is a ruby hash containing the user information to send
|
|
249
|
+
# to TimeSync.
|
|
250
|
+
def create_user(user)
|
|
251
|
+
ary = %w(site_admin site_manager site_spectator active)
|
|
252
|
+
ary.each do |perm|
|
|
253
|
+
if user.key?(perm) && !(user[perm].is_a? Boolean)
|
|
254
|
+
return Hash[@error =>
|
|
255
|
+
format('user object: %s must be True or False', perm)]
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
hash_user_password(user)
|
|
260
|
+
create_or_update(user, nil, 'user', 'users')
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# update_user(user, username)
|
|
264
|
+
# Send a user update to TimeSync via a POST request in a JSON body.
|
|
265
|
+
# This method will return that body in the form of a list containing a
|
|
266
|
+
# single ruby hash. The hash will contain a representation
|
|
267
|
+
# of that updated user object if it was successful or error
|
|
268
|
+
# information if it was not.
|
|
269
|
+
# ``user`` is a ruby hash containing the user information to send
|
|
270
|
+
# to TimeSync.
|
|
271
|
+
# ``username`` contains the username for a user to update.
|
|
272
|
+
def update_user(user, username)
|
|
273
|
+
ary = %w(site_admin site_manager site_spectator active)
|
|
274
|
+
ary.each do |perm|
|
|
275
|
+
if user.key?(perm) && !(user[perm].is_a? Boolean)
|
|
276
|
+
return Hash[@error =>
|
|
277
|
+
format('user object: %s must be True or False', perm)]
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
hash_user_password(user)
|
|
282
|
+
create_or_update(user, username, 'user', 'users', false)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# get_times(query_parameters)
|
|
286
|
+
# Request time entries filtered by parameters passed in
|
|
287
|
+
# ``query_parameters``. Returns a list of ruby objects representing the
|
|
288
|
+
# JSON time information returned by TimeSync or an error message if
|
|
289
|
+
# unsuccessful.
|
|
290
|
+
# ``query_parameters`` is a ruby hash containing the optional
|
|
291
|
+
# query parameters described in the TimeSync documentation. If
|
|
292
|
+
# ``query_parameters`` is empty or nil, ``get_times`` will return all
|
|
293
|
+
# times in the database. The syntax for each argument is
|
|
294
|
+
# ``{'query': ['parameter']}``.
|
|
295
|
+
def get_times(query_parameters = nil)
|
|
296
|
+
# Check that user has authenticated
|
|
297
|
+
@local_auth_error = local_auth_error
|
|
298
|
+
return [Hash[@error => @local_auth_error]] if @local_auth_error
|
|
299
|
+
|
|
300
|
+
# Check for key error
|
|
301
|
+
unless query_parameters.nil?
|
|
302
|
+
query_parameters.each do |key, _value|
|
|
303
|
+
unless @valid_get_queries.include?(key)
|
|
304
|
+
return [Hash[@error =>
|
|
305
|
+
format('invalid query: %s', key)]]
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Initialize the query string
|
|
311
|
+
query_string = ''
|
|
312
|
+
|
|
313
|
+
# If there are filtering parameters, construct them correctly.
|
|
314
|
+
# Else reinitialize the query string to a ? so we can add the token.
|
|
315
|
+
query_string = if query_parameters.nil?
|
|
316
|
+
'?'
|
|
317
|
+
else
|
|
318
|
+
construct_filter_query(query_parameters)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Construct query url, at this point query_string ends with a ?
|
|
322
|
+
url = format('%s/times%stoken=%s', @baseurl, query_string, @token)
|
|
323
|
+
|
|
324
|
+
# Test mode, return one or many objects depending on if uuid is passed
|
|
325
|
+
if @test
|
|
326
|
+
m = MockTimeSync.new
|
|
327
|
+
if !query_parameters.nil? && query_parameters.key?('uuid')
|
|
328
|
+
return m.get_times(query_parameters['uuid'])
|
|
329
|
+
else
|
|
330
|
+
return m.get_times(nil)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Attempt to GET times, then convert the response to a ruby
|
|
335
|
+
# hash. Always returns a list.
|
|
336
|
+
begin
|
|
337
|
+
# Success!
|
|
338
|
+
response = RestClient.get url
|
|
339
|
+
res_dict = response_to_ruby(response.body, response.code)
|
|
340
|
+
return (res_dict.is_a?(Array) ? res_dict : [res_dict])
|
|
341
|
+
rescue => e
|
|
342
|
+
# Request Error
|
|
343
|
+
return [Hash[@error => e]]
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# get_projects(query_parameters)
|
|
348
|
+
# Request project information filtered by parameters passed to
|
|
349
|
+
# ``query_parameters``. Returns a list of ruby objects representing the
|
|
350
|
+
# JSON project information returned by TimeSync or an error message if
|
|
351
|
+
# unsuccessful.
|
|
352
|
+
# ``query_parameters`` is a ruby dict containing the optional query
|
|
353
|
+
# parameters described in the TimeSync documentation. If
|
|
354
|
+
# ``query_parameters`` is empty or nil, ``get_projects`` will return
|
|
355
|
+
# all projects in the database. The syntax for each argument is
|
|
356
|
+
# ``{'query': 'parameter'}`` or ``{'bool_query': <boolean>}``.
|
|
357
|
+
# Optional parameters:
|
|
358
|
+
# 'slug': '<slug>'
|
|
359
|
+
# 'include_deleted': <boolean>
|
|
360
|
+
# 'revisions': <boolean>
|
|
361
|
+
# Does not accept a slug combined with include_deleted, but does accept
|
|
362
|
+
# any other combination.
|
|
363
|
+
def get_projects(query_parameters = nil)
|
|
364
|
+
# Check that user has authenticated
|
|
365
|
+
@local_auth_error = local_auth_error
|
|
366
|
+
return [Hash[@error => @local_auth_error]] if @local_auth_error
|
|
367
|
+
|
|
368
|
+
# Save for passing to test mode since format_endpoints deletes
|
|
369
|
+
# kwargs['slug'] if it exists
|
|
370
|
+
slug = if !query_parameters.to_s.empty? && query_parameters.key?('slug')
|
|
371
|
+
query_parameters['slug']
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
query_string = ''
|
|
375
|
+
|
|
376
|
+
# If kwargs exist, create a correct query string
|
|
377
|
+
# Else, prepare query_string for the token
|
|
378
|
+
if !query_parameters.to_s.empty?
|
|
379
|
+
query_string = format_endpoints(query_parameters)
|
|
380
|
+
# If format_endpoints returns nil, it was passed both slug and
|
|
381
|
+
# include_deleted, which is not allowed by the TimeSync API
|
|
382
|
+
if query_string.nil?
|
|
383
|
+
error_message = 'invalid combination: slug and include_deleted'
|
|
384
|
+
return [Hash[@error => error_message]]
|
|
385
|
+
end
|
|
386
|
+
else
|
|
387
|
+
query_string = format('?token=%s', @token)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Construct query url - at this point query_string ends with
|
|
391
|
+
# ?token=token
|
|
392
|
+
url = format('%s/projects%s', @baseurl, query_string)
|
|
393
|
+
|
|
394
|
+
# Test mode, return list of projects if slug is nil, or a single
|
|
395
|
+
# project
|
|
396
|
+
if @test
|
|
397
|
+
m = MockTimeSync.new
|
|
398
|
+
return m.get_projects(slug)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Attempt to GET projects, then convert the response to a ruby
|
|
402
|
+
# hash. Always returns a list.
|
|
403
|
+
begin
|
|
404
|
+
# Success!
|
|
405
|
+
response = RestClient.get url
|
|
406
|
+
res_dict = response_to_ruby(response.body, response.code)
|
|
407
|
+
return (res_dict.is_a?(Array) ? res_dict : [res_dict])
|
|
408
|
+
rescue => e
|
|
409
|
+
# Request Error
|
|
410
|
+
return [Hash[@error => e]]
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# get_activities(query_parameters)
|
|
415
|
+
# Request activity information filtered by parameters passed to
|
|
416
|
+
# ``query_parameters``. Returns a list of ruby objects representing
|
|
417
|
+
# the JSON activity information returned by TimeSync or an error message
|
|
418
|
+
# if unsuccessful.
|
|
419
|
+
# ``query_parameters`` is a hash containing the optional query
|
|
420
|
+
# parameters described in the TimeSync documentation. If
|
|
421
|
+
# ``query_parameters`` is empty or nil, ``get_activities`` will
|
|
422
|
+
# return all activities in the database. The syntax for each argument is
|
|
423
|
+
# ``{'query': 'parameter'}`` or ``{'bool_query': <boolean>}``.
|
|
424
|
+
# Optional parameters:
|
|
425
|
+
# 'slug': '<slug>'
|
|
426
|
+
# 'include_deleted': <boolean>
|
|
427
|
+
# 'revisions': <boolean>
|
|
428
|
+
# Does not accept a slug combined with include_deleted, but does accept
|
|
429
|
+
# any other combination.
|
|
430
|
+
def get_activities(query_parameters = nil)
|
|
431
|
+
# Check that user has authenticated
|
|
432
|
+
@local_auth_error = local_auth_error
|
|
433
|
+
return [Hash[@error => @local_auth_error]] if @local_auth_error
|
|
434
|
+
|
|
435
|
+
# Save for passing to test mode since format_endpoints deletes
|
|
436
|
+
# kwargs['slug'] if it exists
|
|
437
|
+
slug = if !query_parameters.to_s.empty? && query_parameters.key?('slug')
|
|
438
|
+
query_parameters['slug']
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
query_string = ''
|
|
442
|
+
|
|
443
|
+
# If kwargs exist, create a correct query string
|
|
444
|
+
# Else, prepare query_string for the token
|
|
445
|
+
if !query_parameters.to_s.empty?
|
|
446
|
+
query_string = format_endpoints(query_parameters)
|
|
447
|
+
# If format_endpoints returns nil, it was passed both slug and
|
|
448
|
+
# include_deleted, which is not allowed by the TimeSync API
|
|
449
|
+
if query_string.nil?
|
|
450
|
+
error_message = 'invalid combination: slug and include_deleted'
|
|
451
|
+
return [Hash[@error => error_message]]
|
|
452
|
+
end
|
|
453
|
+
else
|
|
454
|
+
query_string = format('?token=%s', @token)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Construct query url - at this point query_string ends with
|
|
458
|
+
# ?token=token
|
|
459
|
+
url = format('%s/activities%s', @baseurl, query_string)
|
|
460
|
+
|
|
461
|
+
# Test mode, return list of projects if slug is nil, or a list of
|
|
462
|
+
# projects
|
|
463
|
+
if @test
|
|
464
|
+
m = MockTimeSync.new
|
|
465
|
+
return m.get_activities(slug)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Attempt to GET activities, then convert the response to a ruby
|
|
469
|
+
# hash. Always returns a list.
|
|
470
|
+
begin
|
|
471
|
+
# Success!
|
|
472
|
+
response = RestClient.get url
|
|
473
|
+
res_dict = response_to_ruby(response.body, response.code)
|
|
474
|
+
return (res_dict.is_a?(Array) ? res_dict : [res_dict])
|
|
475
|
+
rescue => e
|
|
476
|
+
# Request Error
|
|
477
|
+
return [Hash[@error => e]]
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# get_users(username=nil)
|
|
482
|
+
|
|
483
|
+
# Request user entities from the TimeSync instance specified by the
|
|
484
|
+
# baseurl provided when instantiating the TimeSync object. Returns a list
|
|
485
|
+
# of ruby dictionaries containing the user information returned by
|
|
486
|
+
# TimeSync or an error message if unsuccessful.
|
|
487
|
+
|
|
488
|
+
# ``username`` is an optional parameter containing a string of the
|
|
489
|
+
# specific username to be retrieved. If ``username`` is not provided, a
|
|
490
|
+
# list containing all users will be returned. Defaults to ``nil``.
|
|
491
|
+
def get_users(username = nil)
|
|
492
|
+
# Check that user has authenticated
|
|
493
|
+
@local_auth_error = local_auth_error
|
|
494
|
+
return [Hash[@error => @local_auth_error]] if @local_auth_error
|
|
495
|
+
|
|
496
|
+
# url should end with /users if no username is passed else
|
|
497
|
+
# /users/username
|
|
498
|
+
url = if username
|
|
499
|
+
format('%s/users/%s', @baseurl, username)
|
|
500
|
+
else
|
|
501
|
+
format('%s/users', @baseurl)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# The url should always end with a token
|
|
505
|
+
url += format('?token=%s', @token)
|
|
506
|
+
|
|
507
|
+
# Test mode, return one user object if username is passed else return
|
|
508
|
+
# several user objects
|
|
509
|
+
if @test
|
|
510
|
+
m = MockTimeSync.new
|
|
511
|
+
return m.get_users(username)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Attempt to GET users, then convert the response to a ruby
|
|
515
|
+
# hash. Always returns a list.
|
|
516
|
+
|
|
517
|
+
begin
|
|
518
|
+
# Success!
|
|
519
|
+
response = RestClient.get url
|
|
520
|
+
res_dict = response_to_ruby(response.body, response.code)
|
|
521
|
+
return (res_dict.is_a?(Array) ? res_dict : [res_dict])
|
|
522
|
+
rescue => e
|
|
523
|
+
# Request Error
|
|
524
|
+
return [Hash[@error => e]]
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# delete_time(uuid=nil)
|
|
529
|
+
# Allows the currently authenticated user to delete their own time entry
|
|
530
|
+
# by uuid.
|
|
531
|
+
# ``uuid`` is a string containing the uuid of the time entry to be
|
|
532
|
+
# deleted.
|
|
533
|
+
def delete_time(uuid = nil)
|
|
534
|
+
# Check that user has authenticated
|
|
535
|
+
@local_auth_error = local_auth_error
|
|
536
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
537
|
+
|
|
538
|
+
return Hash[@error => 'missing uuid; please add to method call'] unless uuid
|
|
539
|
+
|
|
540
|
+
delete_object('times', uuid)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# delete_project(slug=nil)
|
|
544
|
+
# Allows the currently authenticated admin user to delete a project
|
|
545
|
+
# record by slug.
|
|
546
|
+
# ``slug`` is a string containing the slug of the project to be deleted.
|
|
547
|
+
def delete_project(slug = nil)
|
|
548
|
+
# Check that user has authenticated
|
|
549
|
+
@local_auth_error = local_auth_error
|
|
550
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
551
|
+
|
|
552
|
+
return Hash[@error => 'missing slug; please add to method call'] unless slug
|
|
553
|
+
|
|
554
|
+
delete_object('projects', slug)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# delete_activity(slug=nil)
|
|
558
|
+
# Allows the currently authenticated admin user to delete an activity
|
|
559
|
+
# record by slug.
|
|
560
|
+
# ``slug`` is a string containing the slug of the activity to be deleted.
|
|
561
|
+
def delete_activity(slug = nil)
|
|
562
|
+
# Check that user has authenticated
|
|
563
|
+
@local_auth_error = local_auth_error
|
|
564
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
565
|
+
|
|
566
|
+
return Hash[@error => 'missing slug; please add to method call'] unless slug
|
|
567
|
+
|
|
568
|
+
delete_object('activities', slug)
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# delete_user(username=nil)
|
|
572
|
+
# Allows the currently authenticated admin user to delete a user
|
|
573
|
+
# record by username.
|
|
574
|
+
# ``username`` is a string containing the username of the user to be
|
|
575
|
+
# deleted.
|
|
576
|
+
def delete_user(username = nil)
|
|
577
|
+
# Check that user has authenticated
|
|
578
|
+
@local_auth_error = local_auth_error
|
|
579
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
580
|
+
|
|
581
|
+
unless username
|
|
582
|
+
return Hash[@error =>
|
|
583
|
+
'missing username; please add to method call']
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
delete_object('users', username)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# token_expiration_time
|
|
590
|
+
# Returns the expiration time of the JWT (JSON Web Token) associated with
|
|
591
|
+
# this object.
|
|
592
|
+
def token_expiration_time
|
|
593
|
+
# Check that user has authenticated
|
|
594
|
+
@local_auth_error = local_auth_error
|
|
595
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
596
|
+
|
|
597
|
+
# Return valid date if in test mode
|
|
598
|
+
if @test
|
|
599
|
+
m = MockTimeSync.new
|
|
600
|
+
return m.token_expiration_time
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# Decode the token, then get the second dict (payload) from the
|
|
604
|
+
# resulting string. The payload contains the expiration time.
|
|
605
|
+
begin
|
|
606
|
+
decoded_payload = Base64.decode64(@token.split('.')[1])
|
|
607
|
+
rescue
|
|
608
|
+
return Hash[@error => 'improperly encoded token']
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# literal_eval the string representation of a dict to convert it to a
|
|
612
|
+
# dict, then get the value at 'exp'. The value at 'exp' is epoch time
|
|
613
|
+
# in ms
|
|
614
|
+
exp_int = JSON.load(decoded_payload)['exp'] # not sure about this
|
|
615
|
+
|
|
616
|
+
# Convert the epoch time from ms to s
|
|
617
|
+
exp_int /= 1000
|
|
618
|
+
|
|
619
|
+
# Convert and format the epoch time to ruby datetime.
|
|
620
|
+
exp_datetime = Time.at(exp_int)
|
|
621
|
+
|
|
622
|
+
exp_datetime
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
# project_users(project)
|
|
626
|
+
# Returns a dict of users for the specified project containing usernames
|
|
627
|
+
# mapped to their list of permissions for the project.
|
|
628
|
+
def project_users(project = nil)
|
|
629
|
+
# Check that user has authenticated
|
|
630
|
+
@local_auth_error = local_auth_error
|
|
631
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
632
|
+
|
|
633
|
+
# Check that a project slug was passed
|
|
634
|
+
unless project
|
|
635
|
+
return Hash[@error =>
|
|
636
|
+
'Missing project slug, please include in method call']
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Construct query url
|
|
640
|
+
url = format('%s/projects/%s?token=%s', @baseurl, project, @token)
|
|
641
|
+
# Return valid user object if in test mode
|
|
642
|
+
if @test
|
|
643
|
+
m = MockTimeSync.new
|
|
644
|
+
return m.project_users
|
|
645
|
+
end
|
|
646
|
+
# Try to get the project object
|
|
647
|
+
begin
|
|
648
|
+
# Success!
|
|
649
|
+
response = RestClient.get(url)
|
|
650
|
+
project_object = response_to_ruby(response.body, response.code)
|
|
651
|
+
rescue => e
|
|
652
|
+
# Request Error
|
|
653
|
+
return Hash[@error => e]
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
# Create the user dict to return
|
|
657
|
+
# There was an error, don't do anything with it, return as a list
|
|
658
|
+
return project_object if project_object.key?('error')
|
|
659
|
+
|
|
660
|
+
# Get the user object from the project
|
|
661
|
+
users = project_object['users']
|
|
662
|
+
|
|
663
|
+
# Convert the nested permissions dict to a list containing only
|
|
664
|
+
# relevant (true) permissions
|
|
665
|
+
|
|
666
|
+
rv = Hash[]
|
|
667
|
+
|
|
668
|
+
users.each do |user, permissions|
|
|
669
|
+
rv_perms = Array[]
|
|
670
|
+
permissions.each do |perm, value|
|
|
671
|
+
rv_perms.push(perm) if value
|
|
672
|
+
end
|
|
673
|
+
rv[user] = rv_perms
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
rv
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
################################################
|
|
680
|
+
# Internal methods
|
|
681
|
+
################################################
|
|
682
|
+
|
|
683
|
+
# Returns auth object to log in to TimeSync
|
|
684
|
+
def auth
|
|
685
|
+
Hash['type' => @auth_type,
|
|
686
|
+
'username' => @user,
|
|
687
|
+
'password' => @password]
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
# Returns auth object with a token to send to TimeSync endpoints
|
|
691
|
+
def token_auth
|
|
692
|
+
Hash['type' => 'token',
|
|
693
|
+
'token' => @token,]
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# Checks that token is set.
|
|
697
|
+
# Returns error if not set, otherwise returns nil
|
|
698
|
+
def local_auth_error
|
|
699
|
+
(@token ? nil : 'Not authenticated with TimeSync,'\
|
|
700
|
+
' call authenticate first')
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# Hashes the password field in a user object.
|
|
704
|
+
# If the password is Unicode, encode it to UTF-8 first
|
|
705
|
+
def hash_user_password(user)
|
|
706
|
+
# Only hash password if it is present
|
|
707
|
+
# Don't error out here so that internal methods can catch all missing
|
|
708
|
+
# fields later on and return a more meaningful error if necessary.
|
|
709
|
+
if user.key?('password')
|
|
710
|
+
password = user['password']
|
|
711
|
+
|
|
712
|
+
# Hash the password
|
|
713
|
+
hashed = BCrypt::Password.create('password')
|
|
714
|
+
|
|
715
|
+
user['password'] = hashed
|
|
716
|
+
end
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Convert response to native ruby list of objects
|
|
720
|
+
def response_to_ruby(body, code)
|
|
721
|
+
# Body should always be a string
|
|
722
|
+
# DELETE returns an empty body if successful
|
|
723
|
+
return Hash['status' => 200] if body.empty? && code == 200
|
|
724
|
+
# If body is valid JSON, it came from TimeSync. If it isn't
|
|
725
|
+
# and we got a ValueError, we know we are having trouble connecting to
|
|
726
|
+
# TimeSync because we are not getting a return from TimeSync.
|
|
727
|
+
begin
|
|
728
|
+
ruby_object = JSON.load(body)
|
|
729
|
+
rescue
|
|
730
|
+
# If we get a ValueError, body isn't a JSON object, and
|
|
731
|
+
# therefore didn't come from a TimeSync connection.
|
|
732
|
+
err_msg = format('connection to TimeSync failed at baseurl %s - ',
|
|
733
|
+
@baseurl)
|
|
734
|
+
err_msg += format('response status was %s', code)
|
|
735
|
+
return Hash[@error => err_msg]
|
|
736
|
+
end
|
|
737
|
+
ruby_object
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
# Format endpoints for GET projects and activities requests.
|
|
741
|
+
# Returns nil if invalid combination of slug and include_deleted
|
|
742
|
+
def format_endpoints(queries)
|
|
743
|
+
query_string = '?'
|
|
744
|
+
query_list = Array[]
|
|
745
|
+
|
|
746
|
+
# The following combination is not allowed
|
|
747
|
+
if queries.key?('slug') && queries.key?('include_deleted')
|
|
748
|
+
return nil
|
|
749
|
+
# slug goes first, then delete it so it doesn't show up after the ?
|
|
750
|
+
elsif queries.key?('slug')
|
|
751
|
+
# query_string = '/%s?' % queries['slug']
|
|
752
|
+
query_string = format('/%s?', queries['slug'])
|
|
753
|
+
queries.delete('slug')
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Convert true and false booleans to TimeSync compatible strings
|
|
757
|
+
queries.to_a.each do |k, v|
|
|
758
|
+
queries[k] = v ? 'true' : 'false'
|
|
759
|
+
query_list.push('%s=%s', k, queries[k])
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
query_list = query_list.sort
|
|
763
|
+
|
|
764
|
+
# Check for items in query_list after slug was removed, create
|
|
765
|
+
# query string
|
|
766
|
+
if query_list
|
|
767
|
+
# query_string += '%s&' % query_list.join('&')
|
|
768
|
+
query_string += format('%s&', query_list.join('&'))
|
|
769
|
+
end
|
|
770
|
+
# Authenticate and return
|
|
771
|
+
# query_string += 'token=%s' % @token
|
|
772
|
+
query_string += format('token=%s', @token)
|
|
773
|
+
query_string
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# Construct the query string for filtering GET queries, such as get_times
|
|
777
|
+
def construct_filter_query(queries)
|
|
778
|
+
query_string = '?'
|
|
779
|
+
query_list = Array[]
|
|
780
|
+
|
|
781
|
+
# Format the include_* queries similarly to other queries for easier
|
|
782
|
+
# processing
|
|
783
|
+
if queries.key?('include_deleted')
|
|
784
|
+
queries['include_deleted'] = if queries['include_deleted']
|
|
785
|
+
['true']
|
|
786
|
+
else
|
|
787
|
+
['false']
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
if queries.key?('include_revisions')
|
|
792
|
+
queries['include_revisions'] = if queries['include_revisions']
|
|
793
|
+
['true']
|
|
794
|
+
else
|
|
795
|
+
['false']
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# If uuid is included, the only other accepted queries are the
|
|
800
|
+
# include_*s
|
|
801
|
+
if queries.key?('uuid')
|
|
802
|
+
query_string = format('/%s?', queries['uuid'])
|
|
803
|
+
if queries.key?('include_deleted')
|
|
804
|
+
query_string += format('include_deleted=%s&',
|
|
805
|
+
queries['include_deleted'][0])
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
if queries.key?('include_revisions')
|
|
809
|
+
query_string += format('include_revisions=%s&',
|
|
810
|
+
queries['include_revisions'][0])
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
# Everthing is a list now, so iterate through and push
|
|
814
|
+
else
|
|
815
|
+
# Sort them into an alphabetized list for easier testing
|
|
816
|
+
# sorted_qs = sorted(queries.to_a, key = operator.itemgetter(0))
|
|
817
|
+
sorted_qs = queries.to_a.sort
|
|
818
|
+
sorted_qs.each do |query, param|
|
|
819
|
+
param.each do |slug|
|
|
820
|
+
# Format each query in the list
|
|
821
|
+
# query_list.push('%s=%s' % Array[query, slug])
|
|
822
|
+
query_list.push(format('%s=%s', query, slug))
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
# Construct the query_string using the list.
|
|
827
|
+
# Last character will be an & so we can push the token
|
|
828
|
+
query_list.each do |string|
|
|
829
|
+
query_string += format('%s&', string)
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
query_string
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
# Checks that ``actual`` parameter passed to POST method contains
|
|
836
|
+
# items in required or optional lists for that ``object_name``.
|
|
837
|
+
# Returns nil if no errors found or error string if error found. If
|
|
838
|
+
# ``create_object`` then ``actual`` gets checked for required fields
|
|
839
|
+
def get_field_errors(actual, object_name, create_object)
|
|
840
|
+
# Check that actual is a ruby dict
|
|
841
|
+
unless actual.is_a? Hash
|
|
842
|
+
return format('%s object: must be ruby hash', object_name)
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
# missing_list contains a list of all the required parameters that were
|
|
846
|
+
# not passed. It is initialized to all required parameters.
|
|
847
|
+
missing_list = @required_params[object_name].clone
|
|
848
|
+
|
|
849
|
+
# For each key, if it is not required or optional, it is not allowed
|
|
850
|
+
# If it is requried, remove that parameter from the missing_list, since
|
|
851
|
+
# it is no longer missing
|
|
852
|
+
# actual.each do |key, value|
|
|
853
|
+
|
|
854
|
+
actual.each do |key, _value|
|
|
855
|
+
if !@required_params[object_name].include?(key.to_s) && !@optional_params[object_name].include?(key.to_s)
|
|
856
|
+
return format('%s object: invalid field: %s', object_name, key)
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
# Remove field from copied list if the field is in required
|
|
860
|
+
if @required_params[object_name].include? key.to_s
|
|
861
|
+
# missing_list.delete(key.to_s)
|
|
862
|
+
missing_list.delete_at(missing_list.index(key.to_s))
|
|
863
|
+
end
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
# If there is anything in missing_list, it is an absent required field
|
|
867
|
+
# This only needs to be checked if the create_object flag is passed
|
|
868
|
+
if create_object && !missing_list.empty?
|
|
869
|
+
return format('%s object: missing required field(s): %s',
|
|
870
|
+
object_name, missing_list.join(', '))
|
|
871
|
+
end
|
|
872
|
+
# No errors if we made it this far
|
|
873
|
+
nil
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
# Create or update an object ``object_name`` at specified ``endpoint``.
|
|
877
|
+
# This method will return that object in the form of a list containing a
|
|
878
|
+
# single ruby hash. The hash will contain a representation
|
|
879
|
+
# of the JSON body returned by TimeSync if it was successful or error
|
|
880
|
+
# information if it was not. If ``create_object``, then ``parameters``
|
|
881
|
+
# gets checked for required fields.
|
|
882
|
+
def create_or_update(object_fields, identifier,
|
|
883
|
+
object_name, endpoint, create_object = true)
|
|
884
|
+
# Check that user has authenticated
|
|
885
|
+
@local_auth_error = local_auth_error
|
|
886
|
+
|
|
887
|
+
return Hash[@error => @local_auth_error] if @local_auth_error
|
|
888
|
+
|
|
889
|
+
# Check that object contains required fields and no bad fields
|
|
890
|
+
field_error = get_field_errors(object_fields, object_name, create_object)
|
|
891
|
+
|
|
892
|
+
return Hash[@error => field_error] if field_error
|
|
893
|
+
|
|
894
|
+
# Since this is a POST request, we need an auth and object objects
|
|
895
|
+
values = Hash['auth' => token_auth, 'object' => object_fields].to_json
|
|
896
|
+
|
|
897
|
+
# Reformat the identifier with the leading '/' so it can be added to
|
|
898
|
+
# the url. Do this here instead of the url assignment below so we can
|
|
899
|
+
# set it to ' if it wasn't passed.
|
|
900
|
+
identifier = identifier ? format('/%s', identifier) : ''
|
|
901
|
+
|
|
902
|
+
# Construct url to post to
|
|
903
|
+
url = format('%s/%s%s', @baseurl, endpoint, identifier)
|
|
904
|
+
|
|
905
|
+
# Test mode, remove leading '/' from identifier
|
|
906
|
+
identifier.slice!(0)
|
|
907
|
+
test_identifier = identifier
|
|
908
|
+
if @test
|
|
909
|
+
return test_handler(object_fields, test_identifier,
|
|
910
|
+
object_name, create_object)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# Attempt to POST to TimeSync, then convert the response to a ruby
|
|
914
|
+
# hash
|
|
915
|
+
begin
|
|
916
|
+
# Success!
|
|
917
|
+
response = RestClient.post(url, values, content_type: :json,
|
|
918
|
+
accept: :json)
|
|
919
|
+
return response_to_ruby(response.body, response.code)
|
|
920
|
+
rescue => e
|
|
921
|
+
# Request error
|
|
922
|
+
return Hash[@error => e]
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
# When a time_entry is created, a user will enter a time duration as
|
|
927
|
+
# one of the parameters of the object. This method will convert that
|
|
928
|
+
# entry (if it's entered as a string) into the appropriate integer
|
|
929
|
+
# equivalent (in seconds).
|
|
930
|
+
def duration_to_seconds(duration)
|
|
931
|
+
t = Time.strptime(duration, '%Hh%Mm')
|
|
932
|
+
hours_spent = t.hour
|
|
933
|
+
minutes_spent = t.min
|
|
934
|
+
|
|
935
|
+
temp = format('%sh%sm', hours_spent, minutes_spent)
|
|
936
|
+
|
|
937
|
+
if temp != duration
|
|
938
|
+
error_msg = [Hash[@error => 'time object: invalid duration string']]
|
|
939
|
+
return error_msg
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
# Convert duration to seconds
|
|
943
|
+
return (hours_spent * 3600) + (minutes_spent * 60)
|
|
944
|
+
rescue
|
|
945
|
+
error_msg = [Hash[@error => 'time object: invalid duration string']]
|
|
946
|
+
return error_msg
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# Deletes object at ``endpoint`` identified by ``identifier``
|
|
950
|
+
def delete_object(endpoint, identifier)
|
|
951
|
+
# Construct url
|
|
952
|
+
url = format('%s/%s/%s?token=%s', @baseurl, endpoint, identifier, @token)
|
|
953
|
+
|
|
954
|
+
# Test mode
|
|
955
|
+
if @test
|
|
956
|
+
m = MockTimeSync.new
|
|
957
|
+
return m.delete_object
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Attempt to DELETE object
|
|
961
|
+
begin
|
|
962
|
+
# Success!
|
|
963
|
+
response = RestClient.delete url
|
|
964
|
+
return response_to_ruby(response.body, response.code)
|
|
965
|
+
rescue => e
|
|
966
|
+
# Request error
|
|
967
|
+
return Hash[@error => e]
|
|
968
|
+
end
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
# Handle test methods in test mode for creating or updating an object
|
|
972
|
+
def test_handler(parameters, identifier, obj_name, create_object)
|
|
973
|
+
m = MockTimeSync.new
|
|
974
|
+
case obj_name
|
|
975
|
+
|
|
976
|
+
when 'time'
|
|
977
|
+
if create_object
|
|
978
|
+
# return mock_rimesync.create_time(parameters)
|
|
979
|
+
return m.create_time(parameters)
|
|
980
|
+
else
|
|
981
|
+
# return mock_rimesync.update_time(parameters, identifier)
|
|
982
|
+
return m.update_time(parameters, identifier)
|
|
983
|
+
end
|
|
984
|
+
when 'project'
|
|
985
|
+
if create_object
|
|
986
|
+
# return mock_rimesync.create_project(parameters)
|
|
987
|
+
return m.create_project(parameters)
|
|
988
|
+
else
|
|
989
|
+
# return mock_rimesync.update_project(parameters, identifier)
|
|
990
|
+
return m.update_project(parameters, identifier)
|
|
991
|
+
end
|
|
992
|
+
when 'activity'
|
|
993
|
+
if create_object
|
|
994
|
+
# return mock_rimesync.create_activity(parameters)
|
|
995
|
+
return m.create_activity(parameters)
|
|
996
|
+
else
|
|
997
|
+
# return mock_rimesync.update_activity(parameters, identifier)
|
|
998
|
+
return m.update_activity(parameters, identifier)
|
|
999
|
+
end
|
|
1000
|
+
when 'user'
|
|
1001
|
+
if create_object
|
|
1002
|
+
# return mock_rimesync.create_user(parameters)
|
|
1003
|
+
return m.create_user(parameters)
|
|
1004
|
+
else
|
|
1005
|
+
# return mock_rimesync.update_user(parameters, identifier)
|
|
1006
|
+
return m.update_user(parameters, identifier)
|
|
1007
|
+
end
|
|
1008
|
+
end
|
|
1009
|
+
end
|
|
1010
|
+
end
|