timely-app 1.3.0 → 1.3.2
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 +4 -4
- data/CHANGELOG.md +34 -22
- data/README.md +20 -1
- data/bin/timely-app +6 -118
- data/lib/timely-app/cli.rb +132 -0
- data/lib/timely-app/client/accounts.rb +1 -1
- data/lib/timely-app/client/auth.rb +1 -1
- data/lib/timely-app/client/clients.rb +1 -1
- data/lib/timely-app/client.rb +29 -24
- data/lib/timely-app/errors.rb +1 -1
- data/lib/timely-app/link_header.rb +2 -2
- data/lib/timely-app/params.rb +5 -4
- data/lib/timely-app/record.rb +3 -3
- data/lib/timely-app/response.rb +6 -5
- data/lib/timely-app.rb +16 -16
- data/sig/timely-app/cli.rbs +33 -0
- data/sig/timely-app/client/accounts.rbs +10 -0
- data/sig/timely-app/client/auth.rbs +8 -0
- data/sig/timely-app/client/clients.rbs +12 -0
- data/sig/timely-app/client/events.rbs +36 -0
- data/sig/timely-app/client/forecasts.rbs +12 -0
- data/sig/timely-app/client/labels.rbs +16 -0
- data/sig/timely-app/client/permissions.rbs +8 -0
- data/sig/timely-app/client/projects.rbs +14 -0
- data/sig/timely-app/client/reports.rbs +8 -0
- data/sig/timely-app/client/roles.rbs +6 -0
- data/sig/timely-app/client/teams.rbs +14 -0
- data/sig/timely-app/client/user_capacities.rbs +8 -0
- data/sig/timely-app/client/users.rbs +16 -0
- data/sig/timely-app/client/webhooks.rbs +14 -0
- data/sig/timely-app/client.rbs +24 -0
- data/sig/timely-app/errors.rbs +24 -0
- data/sig/timely-app/link_header.rbs +8 -0
- data/sig/timely-app/params.rbs +14 -0
- data/sig/timely-app/record.rbs +15 -0
- data/sig/timely-app/response.rbs +10 -0
- data/sig/timely-app.rbs +6 -0
- data/timely-app.gemspec +26 -18
- metadata +174 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c82f58f7c3e0713310beb5653d74487822d9f96bb883ae15805c5987896a589e
|
|
4
|
+
data.tar.gz: d90c62ee9a7baa1b29468f70997a7b1e5e6236d8553c9d92abfc082855595292
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf7b4edda75f282566b4f38c3e48c88f716056055734dae4bd2a08016e5d1e9d7d148922fc4ddcd5625fde5740376cb6e0088bf0c8a558c5d78e6e4723f84668
|
|
7
|
+
data.tar.gz: 3cdb10455d12dbc9f265ea500bccb965775e2d767ae8e798bea35a356ce8065036c0402e60016ff7feb2ea2de2198ea75b901592ac3c45993e3f55c953662ba7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,41 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 1.3.2 (2026-07-25)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- BREAKING: Require Ruby 3.4 or newer
|
|
6
|
+
- Fix `get_clients` to call `/1.1/:account_id/clients` instead of `/1.1/accounts`
|
|
7
|
+
- Move `TimelyApp::Cli` under `lib/`
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## 1.3.1 (2023-09-30)
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
- Refresh gemfile lock
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## 1.3.0 (2023-08-30)
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
- Set minimum ruby version requirement to 2.5.0
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
* Update dependencies
|
|
17
|
+
## 1.2.0 (2023-08-29)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
- Fix version definition
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
## 1.1.1 (2023-08-29)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
- Update gem configuration
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
## 1.1.0 (2023-08-15)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
- Update ruby version to 3.2.2
|
|
28
|
+
- Update dependencies
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
## 1.0.5 (2023-03-02)
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
- Fix verbose mode check
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
## 1.0.4 (2023-02-14)
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
- Update CLI to check input properly and handle command arguments
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
* Add example web service for CLI authorization
|
|
38
|
+
## 1.0.3 (2023-02-14)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
- Fix gemspec
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
## 1.0.2 (2023-02-14)
|
|
43
|
+
|
|
44
|
+
- Add basic CLI script `timely-app`
|
|
45
|
+
|
|
46
|
+
## 1.0.1 (2023-02-14)
|
|
47
|
+
|
|
48
|
+
- Fix oauth token request
|
|
49
|
+
- Add example web service for CLI authorization
|
|
50
|
+
|
|
51
|
+
## 1.0.0 (2023-02-14)
|
|
52
|
+
|
|
53
|
+
- Initial version
|
data/README.md
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
# timely-app
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/timely-app) [](https://github.com/amkisko/timely-app/actions/workflows/test.yml) [](https://codecov.io/
|
|
3
|
+
[](https://badge.fury.io/rb/timely-app) [](https://github.com/amkisko/timely-app/actions/workflows/test.yml) [](https://app.codecov.io/github/amkisko/timely_app.rb)
|
|
4
4
|
|
|
5
5
|
Ruby client for the [Timely API](https://dev.timelyapp.com).
|
|
6
6
|
|
|
7
7
|
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
8
8
|
|
|
9
|
+
<a href="https://www.kiskolabs.com">
|
|
10
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
11
|
+
</a>
|
|
12
|
+
|
|
9
13
|
## Install
|
|
10
14
|
|
|
11
15
|
Using Bundler:
|
|
16
|
+
|
|
12
17
|
```sh
|
|
13
18
|
bundle add timely-app
|
|
14
19
|
```
|
|
15
20
|
|
|
16
21
|
Using RubyGems:
|
|
22
|
+
|
|
17
23
|
```sh
|
|
18
24
|
gem install timely-app
|
|
19
25
|
```
|
|
@@ -99,6 +105,19 @@ gem build timely-app.gemspec
|
|
|
99
105
|
gem push timely-app-*.gem
|
|
100
106
|
```
|
|
101
107
|
|
|
108
|
+
## Links
|
|
109
|
+
|
|
110
|
+
- [GitHub](https://github.com/amkisko/timely_app.rb)
|
|
111
|
+
- [GitLab](https://gitlab.com/amkisko/timely_app.rb)
|
|
112
|
+
- [RubyGems](https://rubygems.org/gems/timely-app)
|
|
113
|
+
- [Versions Atom](https://rubygems.org/gems/timely-app/versions.atom) (feed id `5816973660388`)
|
|
114
|
+
- [libraries.io](https://libraries.io/rubygems/timely-app)
|
|
115
|
+
- [Deps.dev](https://deps.dev/rubygems/timely-app)
|
|
116
|
+
- [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_timely_app.rb)
|
|
117
|
+
- [Snyk](https://snyk.io/test/github/amkisko/timely_app.rb)
|
|
118
|
+
- [Codecov](https://app.codecov.io/github/amkisko/timely_app.rb)
|
|
119
|
+
- [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/timely_app.rb)
|
|
120
|
+
|
|
102
121
|
## License
|
|
103
122
|
|
|
104
123
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/timely-app
CHANGED
|
@@ -4,121 +4,6 @@ require "yaml"
|
|
|
4
4
|
require "optparse"
|
|
5
5
|
require "timely-app"
|
|
6
6
|
|
|
7
|
-
module TimelyApp
|
|
8
|
-
class CLI
|
|
9
|
-
attr_reader :options, :client
|
|
10
|
-
|
|
11
|
-
def initialize(options = {})
|
|
12
|
-
@options = options
|
|
13
|
-
@client = TimelyApp::Client.new(
|
|
14
|
-
access_token: options[:access_token] || fetch_access_token,
|
|
15
|
-
account_id: options[:account_id] || fetch_account_id,
|
|
16
|
-
verbose: options[:verbose]
|
|
17
|
-
)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def get_config(key)
|
|
21
|
-
read_config_file&.fetch(key, nil)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def set_config(key, value)
|
|
25
|
-
save_config_file(key => value)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def command_exists?(cmd)
|
|
29
|
-
return false if cmd.nil? || cmd.empty?
|
|
30
|
-
|
|
31
|
-
client.respond_to?(cmd)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def call(cmd, *args)
|
|
35
|
-
client.send(cmd, *args)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def auth(client_id, client_secret)
|
|
39
|
-
if !client_id || !client_secret
|
|
40
|
-
puts "Usage: timely-app auth CLIENT_ID CLIENT_SECRET"
|
|
41
|
-
exit 1
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
auth_client = TimelyApp::Client.new(verbose: options[:verbose])
|
|
45
|
-
auth_url = auth_client.get_oauth_authorize_url(
|
|
46
|
-
client_id: client_id,
|
|
47
|
-
redirect_uri: "urn:ietf:wg:oauth:2.0:oob"
|
|
48
|
-
)
|
|
49
|
-
puts "Visit this URL in your browser:"
|
|
50
|
-
puts auth_url
|
|
51
|
-
puts "\nEnter authorization code here:"
|
|
52
|
-
code = gets.chomp
|
|
53
|
-
begin
|
|
54
|
-
token = auth_client.post_oauth_token(
|
|
55
|
-
client_id: client_id,
|
|
56
|
-
client_secret: client_secret,
|
|
57
|
-
code: code,
|
|
58
|
-
redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
|
|
59
|
-
grant_type: "authorization_code"
|
|
60
|
-
)
|
|
61
|
-
if options[:save]
|
|
62
|
-
save_config_file(
|
|
63
|
-
access_token: token.access_token,
|
|
64
|
-
refresh_token: token.refresh_token,
|
|
65
|
-
created_at: token.created_at
|
|
66
|
-
)
|
|
67
|
-
else
|
|
68
|
-
puts "\nAccess token:\n#{token.access_token}\n"
|
|
69
|
-
end
|
|
70
|
-
if options[:verbose]
|
|
71
|
-
puts "Token details: #{token.to_h}"
|
|
72
|
-
end
|
|
73
|
-
rescue TimelyApp::Error => e
|
|
74
|
-
puts "Authentication failed"
|
|
75
|
-
if options[:verbose]
|
|
76
|
-
puts "Code: #{code}"
|
|
77
|
-
puts "Response code: #{e.response.code}"
|
|
78
|
-
puts "Response: #{e.response.body}"
|
|
79
|
-
end
|
|
80
|
-
exit 1
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
private
|
|
85
|
-
|
|
86
|
-
def config_file_path
|
|
87
|
-
Dir.home + "/.timelyrc"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def read_config_file
|
|
91
|
-
if File.exist?(config_file_path)
|
|
92
|
-
YAML.load_file(config_file_path)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def fetch_account_id
|
|
97
|
-
read_config_file&.fetch("account_id", nil)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def fetch_access_token
|
|
101
|
-
read_config_file&.fetch("access_token", nil)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def check_access_token
|
|
105
|
-
if !fetch_access_token
|
|
106
|
-
puts "No access token found. Run `timely-app auth` to get one."
|
|
107
|
-
exit 1
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def save_config_file(**options)
|
|
112
|
-
config = read_config_file || {}
|
|
113
|
-
config.merge!(options)
|
|
114
|
-
File.open(config_file_path, "w") do |f|
|
|
115
|
-
f.write(config.to_yaml)
|
|
116
|
-
end
|
|
117
|
-
puts "Saved to #{config_file_path}"
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
7
|
options = {}
|
|
123
8
|
OptionParser.new do |opts|
|
|
124
9
|
opts.banner = "Usage: timely-app [command] [options]"
|
|
@@ -141,8 +26,8 @@ cli = TimelyApp::CLI.new(options)
|
|
|
141
26
|
cmd = ARGV.shift
|
|
142
27
|
case cmd
|
|
143
28
|
when "auth"
|
|
144
|
-
client_id = ARGV.shift || ENV.fetch(
|
|
145
|
-
client_secret = ARGV.shift || ENV.fetch(
|
|
29
|
+
client_id = ARGV.shift || ENV.fetch("TIMELY_CLIENT_ID")
|
|
30
|
+
client_secret = ARGV.shift || ENV.fetch("TIMELY_CLIENT_SECRET")
|
|
146
31
|
ARGV.clear
|
|
147
32
|
if client_id && client_secret
|
|
148
33
|
cli.auth(client_id, client_secret)
|
|
@@ -161,7 +46,10 @@ when "config"
|
|
|
161
46
|
end
|
|
162
47
|
else
|
|
163
48
|
if cli.command_exists?(cmd)
|
|
164
|
-
args = ARGV.
|
|
49
|
+
args = ARGV.each_with_object({}) { |arg, h|
|
|
50
|
+
k, v = arg.split("=")
|
|
51
|
+
h[k] = v
|
|
52
|
+
}
|
|
165
53
|
ARGV.clear
|
|
166
54
|
response = cli.call(cmd, *args)
|
|
167
55
|
case response
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module TimelyApp
|
|
2
|
+
class CLI
|
|
3
|
+
attr_reader :options, :client
|
|
4
|
+
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
@options = options
|
|
7
|
+
@client = TimelyApp::Client.new(
|
|
8
|
+
access_token: options[:access_token] || fetch_access_token,
|
|
9
|
+
account_id: options[:account_id] || fetch_account_id,
|
|
10
|
+
verbose: options[:verbose]
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get_config(key)
|
|
15
|
+
read_config_file&.fetch(key, nil)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def set_config(key, value)
|
|
19
|
+
save_config_file(key => value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def command_exists?(cmd)
|
|
23
|
+
return false if cmd.nil? || cmd.empty?
|
|
24
|
+
|
|
25
|
+
client.respond_to?(cmd)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(cmd, *args)
|
|
29
|
+
client.send(cmd, *args)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def auth(client_id, client_secret)
|
|
33
|
+
validate_auth_credentials!(client_id, client_secret)
|
|
34
|
+
|
|
35
|
+
auth_client = TimelyApp::Client.new(verbose: options[:verbose])
|
|
36
|
+
code = authorize_via_browser(auth_client, client_id)
|
|
37
|
+
token = fetch_oauth_token(auth_client, client_id, client_secret, code)
|
|
38
|
+
persist_or_print_token(token)
|
|
39
|
+
rescue TimelyApp::Error => error
|
|
40
|
+
handle_auth_failure(error, code)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def validate_auth_credentials!(client_id, client_secret)
|
|
46
|
+
return if client_id && client_secret
|
|
47
|
+
|
|
48
|
+
puts "Usage: timely-app auth CLIENT_ID CLIENT_SECRET"
|
|
49
|
+
exit 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def authorize_via_browser(auth_client, client_id)
|
|
53
|
+
auth_url = auth_client.get_oauth_authorize_url(
|
|
54
|
+
client_id: client_id,
|
|
55
|
+
redirect_uri: oauth_redirect_uri
|
|
56
|
+
)
|
|
57
|
+
puts "Visit this URL in your browser:"
|
|
58
|
+
puts auth_url
|
|
59
|
+
puts "\nEnter authorization code here:"
|
|
60
|
+
gets.chomp
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def oauth_redirect_uri
|
|
64
|
+
"urn:ietf:wg:oauth:2.0:oob"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def fetch_oauth_token(auth_client, client_id, client_secret, code)
|
|
68
|
+
auth_client.post_oauth_token(
|
|
69
|
+
client_id: client_id,
|
|
70
|
+
client_secret: client_secret,
|
|
71
|
+
code: code,
|
|
72
|
+
redirect_uri: oauth_redirect_uri,
|
|
73
|
+
grant_type: "authorization_code"
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def persist_or_print_token(token)
|
|
78
|
+
if options[:save]
|
|
79
|
+
save_config_file(
|
|
80
|
+
access_token: token.access_token,
|
|
81
|
+
refresh_token: token.refresh_token,
|
|
82
|
+
created_at: token.created_at
|
|
83
|
+
)
|
|
84
|
+
else
|
|
85
|
+
puts "\nAccess token:\n#{token.access_token}\n"
|
|
86
|
+
end
|
|
87
|
+
puts "Token details: #{token.to_h}" if options[:verbose]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def handle_auth_failure(error, code)
|
|
91
|
+
puts "Authentication failed"
|
|
92
|
+
if options[:verbose]
|
|
93
|
+
puts "Code: #{code}"
|
|
94
|
+
puts "Response code: #{error.response.code}"
|
|
95
|
+
puts "Response: #{error.response.body}"
|
|
96
|
+
end
|
|
97
|
+
exit 1
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def config_file_path
|
|
101
|
+
Dir.home + "/.timelyrc"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def read_config_file
|
|
105
|
+
if File.exist?(config_file_path)
|
|
106
|
+
YAML.load_file(config_file_path)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def fetch_account_id
|
|
111
|
+
read_config_file&.fetch("account_id", nil)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def fetch_access_token
|
|
115
|
+
read_config_file&.fetch("access_token", nil)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def check_access_token
|
|
119
|
+
unless fetch_access_token
|
|
120
|
+
puts "No access token found. Run `timely-app auth` to get one."
|
|
121
|
+
exit 1
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def save_config_file(**options)
|
|
126
|
+
config = read_config_file || {}
|
|
127
|
+
config.merge!(options)
|
|
128
|
+
File.write(config_file_path, config.to_yaml)
|
|
129
|
+
puts "Saved to #{config_file_path}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/timely-app/client.rb
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
3
|
+
require "timely-app/errors"
|
|
4
|
+
require "timely-app/link_header"
|
|
5
|
+
require "timely-app/params"
|
|
6
|
+
require "timely-app/record"
|
|
7
|
+
require "timely-app/response"
|
|
8
|
+
require "net/http"
|
|
9
|
+
require "json"
|
|
10
10
|
|
|
11
11
|
module TimelyApp
|
|
12
12
|
class Client
|
|
13
13
|
attr_accessor :account_id
|
|
14
14
|
|
|
15
15
|
def initialize(options = {})
|
|
16
|
-
@auth_header =
|
|
16
|
+
@auth_header = "Authorization"
|
|
17
17
|
@auth_value = "Bearer #{options[:access_token]}"
|
|
18
18
|
@user_agent = options.fetch(:user_agent) { "timely-app/#{VERSION} ruby/#{RUBY_VERSION}" }
|
|
19
19
|
|
|
20
|
-
@host =
|
|
20
|
+
@host = "api.timelyapp.com"
|
|
21
21
|
|
|
22
22
|
@http = Net::HTTP.new(@host, Net::HTTP.https_default_port)
|
|
23
23
|
@http.use_ssl = true
|
|
24
24
|
|
|
25
25
|
@account_id = options[:account_id]
|
|
26
|
-
@verbose = options[:verbose] || !ENV[
|
|
26
|
+
@verbose = options[:verbose] || !ENV["VERBOSE"].nil? || false
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def get(path, params = nil)
|
|
@@ -37,7 +37,7 @@ module TimelyApp
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def host_uri_join(path, params)
|
|
40
|
-
URI
|
|
40
|
+
URI.join("https://#{@host}", Params.join(path, params)).to_s
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def post(path, attributes)
|
|
@@ -53,24 +53,29 @@ module TimelyApp
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def request(http_request, body_object = nil)
|
|
56
|
-
http_request
|
|
57
|
-
|
|
56
|
+
apply_request_headers(http_request, body_object)
|
|
57
|
+
response = @http.request(http_request)
|
|
58
|
+
log_verbose_exchange(http_request, response)
|
|
59
|
+
raise Response.error(response) unless response.is_a?(Net::HTTPSuccess)
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
http_request.body = JSON.generate(body_object)
|
|
62
|
-
end
|
|
61
|
+
Response.parse(response)
|
|
62
|
+
end
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
def apply_request_headers(http_request, body_object)
|
|
65
|
+
http_request["User-Agent"] = @user_agent
|
|
66
|
+
http_request[@auth_header] = @auth_value
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
puts ">> request: #{http_request.method} #{http_request.path} #{http_request.body}"
|
|
68
|
-
puts "<< response: #{http_request.method} #{http_request.path} #{response.code} #{response.body}"
|
|
69
|
-
end
|
|
68
|
+
return unless body_object
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
http_request["Content-Type"] = "application/json"
|
|
71
|
+
http_request.body = JSON.generate(body_object)
|
|
72
|
+
end
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
def log_verbose_exchange(http_request, response)
|
|
75
|
+
return unless verbose?
|
|
76
|
+
|
|
77
|
+
puts ">> request: #{http_request.method} #{http_request.path} #{http_request.body}"
|
|
78
|
+
puts "<< response: #{http_request.method} #{http_request.path} #{response.code} #{response.body}"
|
|
74
79
|
end
|
|
75
80
|
end
|
|
76
81
|
end
|
data/lib/timely-app/errors.rb
CHANGED
data/lib/timely-app/params.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
3
4
|
|
|
4
5
|
module TimelyApp
|
|
5
6
|
module Params
|
|
@@ -8,17 +9,17 @@ module TimelyApp
|
|
|
8
9
|
def join(path, params = nil)
|
|
9
10
|
return path if params.nil? || params.empty?
|
|
10
11
|
|
|
11
|
-
path +
|
|
12
|
+
path + "?" + encode(params)
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def encode(params)
|
|
15
|
-
params.map { |k, v| "#{escape(k)}=#{array_escape(v)}" }.join(
|
|
16
|
+
params.map { |k, v| "#{escape(k)}=#{array_escape(v)}" }.join("&")
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
private
|
|
19
20
|
|
|
20
21
|
def array_escape(object)
|
|
21
|
-
Array(object).map { |value| escape(value) }.join(
|
|
22
|
+
Array(object).map { |value| escape(value) }.join(",")
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def escape(component)
|
data/lib/timely-app/record.rb
CHANGED
|
@@ -14,13 +14,13 @@ module TimelyApp
|
|
|
14
14
|
|
|
15
15
|
def method_missing(name, *args, &block)
|
|
16
16
|
if @attributes.has_key?(name) && args.empty? && block.nil?
|
|
17
|
-
|
|
17
|
+
@attributes[name]
|
|
18
18
|
else
|
|
19
|
-
super
|
|
19
|
+
super
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def respond_to_missing?(name,
|
|
23
|
+
def respond_to_missing?(name, _include_private = false)
|
|
24
24
|
@attributes.has_key?(name)
|
|
25
25
|
end
|
|
26
26
|
|
data/lib/timely-app/response.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
4
5
|
|
|
5
6
|
module TimelyApp
|
|
6
7
|
module Response
|
|
@@ -11,12 +12,12 @@ module TimelyApp
|
|
|
11
12
|
return :no_content
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
if response.content_type ==
|
|
15
|
+
if response.content_type == "application/json"
|
|
15
16
|
object = JSON.parse(response.body, symbolize_names: true, object_class: Record)
|
|
16
17
|
|
|
17
|
-
if response[
|
|
18
|
+
if response["Link"]
|
|
18
19
|
object.singleton_class.module_eval { attr_accessor :link }
|
|
19
|
-
object.link = LinkHeader.parse(response[
|
|
20
|
+
object.link = LinkHeader.parse(response["Link"])
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
return object
|
data/lib/timely-app.rb
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
require
|
|
11
|
-
require
|
|
12
|
-
require
|
|
13
|
-
require
|
|
14
|
-
require
|
|
15
|
-
require
|
|
1
|
+
require "timely-app/client"
|
|
2
|
+
require "timely-app/client/accounts"
|
|
3
|
+
require "timely-app/client/auth"
|
|
4
|
+
require "timely-app/client/clients"
|
|
5
|
+
require "timely-app/client/events"
|
|
6
|
+
require "timely-app/client/forecasts"
|
|
7
|
+
require "timely-app/client/labels"
|
|
8
|
+
require "timely-app/client/permissions"
|
|
9
|
+
require "timely-app/client/projects"
|
|
10
|
+
require "timely-app/client/reports"
|
|
11
|
+
require "timely-app/client/roles"
|
|
12
|
+
require "timely-app/client/teams"
|
|
13
|
+
require "timely-app/client/user_capacities"
|
|
14
|
+
require "timely-app/client/users"
|
|
15
|
+
require "timely-app/client/webhooks"
|
|
16
16
|
|
|
17
17
|
module TimelyApp
|
|
18
|
-
VERSION =
|
|
18
|
+
VERSION = "1.3.2"
|
|
19
19
|
end
|