yandex_tracker 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/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +122 -0
- data/Rakefile +12 -0
- data/lib/yandex_tracker/auth.rb +76 -0
- data/lib/yandex_tracker/client.rb +89 -0
- data/lib/yandex_tracker/collections/attachments.rb +43 -0
- data/lib/yandex_tracker/collections/base.rb +30 -0
- data/lib/yandex_tracker/collections/categories.rb +34 -0
- data/lib/yandex_tracker/collections/comments.rb +44 -0
- data/lib/yandex_tracker/collections/fields.rb +38 -0
- data/lib/yandex_tracker/collections/issues.rb +49 -0
- data/lib/yandex_tracker/collections/local_fields.rb +39 -0
- data/lib/yandex_tracker/collections/queues.rb +34 -0
- data/lib/yandex_tracker/collections/resolutions.rb +29 -0
- data/lib/yandex_tracker/collections/users.rb +34 -0
- data/lib/yandex_tracker/collections/workflows.rb +29 -0
- data/lib/yandex_tracker/configuration.rb +66 -0
- data/lib/yandex_tracker/errors.rb +30 -0
- data/lib/yandex_tracker/objects/attachment.rb +24 -0
- data/lib/yandex_tracker/objects/base.rb +81 -0
- data/lib/yandex_tracker/objects/category.rb +16 -0
- data/lib/yandex_tracker/objects/comment.rb +27 -0
- data/lib/yandex_tracker/objects/field.rb +16 -0
- data/lib/yandex_tracker/objects/issue.rb +37 -0
- data/lib/yandex_tracker/objects/local_field.rb +16 -0
- data/lib/yandex_tracker/objects/queue.rb +29 -0
- data/lib/yandex_tracker/objects/resolution.rb +16 -0
- data/lib/yandex_tracker/objects/user.rb +16 -0
- data/lib/yandex_tracker/objects/workflow.rb +16 -0
- data/lib/yandex_tracker/resources/attachment.rb +54 -0
- data/lib/yandex_tracker/resources/base.rb +72 -0
- data/lib/yandex_tracker/resources/category.rb +22 -0
- data/lib/yandex_tracker/resources/comment.rb +22 -0
- data/lib/yandex_tracker/resources/field.rb +22 -0
- data/lib/yandex_tracker/resources/issue.rb +42 -0
- data/lib/yandex_tracker/resources/local_field.rb +22 -0
- data/lib/yandex_tracker/resources/queue.rb +22 -0
- data/lib/yandex_tracker/resources/resolution.rb +18 -0
- data/lib/yandex_tracker/resources/user.rb +22 -0
- data/lib/yandex_tracker/resources/workflow.rb +18 -0
- data/lib/yandex_tracker/version.rb +5 -0
- data/lib/yandex_tracker.rb +63 -0
- data/sig/yandex_tracker.rbs +4 -0
- data/yandex_tracker.gemspec +40 -0
- metadata +150 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Collections
|
5
|
+
#
|
6
|
+
# Collections::Resolutions
|
7
|
+
#
|
8
|
+
class Resolutions < Base
|
9
|
+
def initialize(client)
|
10
|
+
super
|
11
|
+
@resource = Resources::Resolution.new(client)
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(id)
|
15
|
+
response = resource.find(id)
|
16
|
+
build_object(Objects::Resolution, response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list(**params)
|
20
|
+
response = resource.list(**params)
|
21
|
+
build_objects(Objects::Resolution, response)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :resource
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Collections
|
5
|
+
#
|
6
|
+
# Collections::Users
|
7
|
+
#
|
8
|
+
class Users < Base
|
9
|
+
def initialize(client)
|
10
|
+
super
|
11
|
+
@resource = Resources::User.new(client)
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(id)
|
15
|
+
response = resource.find(id)
|
16
|
+
build_object(Objects::User, response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def myself(**params)
|
20
|
+
response = resource.myself(**params)
|
21
|
+
build_object(Objects::User, response)
|
22
|
+
end
|
23
|
+
|
24
|
+
def list(**params)
|
25
|
+
response = resource.list(**params)
|
26
|
+
build_objects(Objects::User, response)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :resource
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Collections
|
5
|
+
#
|
6
|
+
# Collections::Workflows
|
7
|
+
#
|
8
|
+
class Workflows < Base
|
9
|
+
def initialize(client)
|
10
|
+
super
|
11
|
+
@resource = Resources::Workflow.new(client)
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(id)
|
15
|
+
response = resource.find(id)
|
16
|
+
build_object(Objects::Workflow, response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list(**params)
|
20
|
+
response = resource.list(**params)
|
21
|
+
build_objects(Objects::Workflow, response)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :resource
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
#
|
5
|
+
# Handles API client configuration and validation
|
6
|
+
#
|
7
|
+
class Configuration
|
8
|
+
attr_accessor :client_id, :client_secret,
|
9
|
+
:cloud_org_id, :org_id,
|
10
|
+
:access_token, :refresh_token
|
11
|
+
|
12
|
+
def validate!
|
13
|
+
validate_org_configuration!
|
14
|
+
validate_auth_configuration!
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_tokens(access_token:, refresh_token:, expires_in:)
|
18
|
+
@access_token = access_token
|
19
|
+
@refresh_token = refresh_token
|
20
|
+
@token_expires_at = Time.now + expires_in
|
21
|
+
end
|
22
|
+
|
23
|
+
def token_expired?
|
24
|
+
return false unless @token_expires_at
|
25
|
+
|
26
|
+
Time.now >= (@token_expires_at - 300)
|
27
|
+
end
|
28
|
+
|
29
|
+
def can_refresh?
|
30
|
+
!@refresh_token.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def additional_headers
|
34
|
+
headers = {}
|
35
|
+
headers["X-Cloud-Org-ID"] = cloud_org_id if cloud_org_id
|
36
|
+
headers["X-Org-Id"] = org_id if org_id && !cloud_org_id
|
37
|
+
headers
|
38
|
+
end
|
39
|
+
|
40
|
+
def can_perform_oauth?
|
41
|
+
valid_oauth_auth?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def validate_org_configuration!
|
47
|
+
return if cloud_org_id || org_id
|
48
|
+
|
49
|
+
raise Errors::ConfigurationError, "Required configuration missing: either cloud_org_id or org_id must be set"
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_auth_configuration!
|
53
|
+
return if valid_token_auth? || valid_oauth_auth?
|
54
|
+
|
55
|
+
raise Errors::ConfigurationError, "Either access_token or (client_id + client_secret) must be set"
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_token_auth?
|
59
|
+
access_token
|
60
|
+
end
|
61
|
+
|
62
|
+
def valid_oauth_auth?
|
63
|
+
client_id && client_secret
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
#
|
5
|
+
# Custom error classes and error formatting for the API client
|
6
|
+
#
|
7
|
+
module Errors
|
8
|
+
class ApiError < StandardError; end
|
9
|
+
class AuthError < ApiError; end
|
10
|
+
class Unauthorized < ApiError; end
|
11
|
+
class NotFound < ApiError; end
|
12
|
+
class TimeoutError < ApiError; end
|
13
|
+
class ConnectionError < ApiError; end
|
14
|
+
class ConfigurationError < StandardError; end
|
15
|
+
class Error < StandardError; end
|
16
|
+
class ArgumentError < Error; end
|
17
|
+
class ResourceError < Error; end
|
18
|
+
class ContextError < Error; end
|
19
|
+
|
20
|
+
module_function
|
21
|
+
|
22
|
+
def format_message(body)
|
23
|
+
return body.to_s unless body.is_a?(Hash)
|
24
|
+
|
25
|
+
# TODO: {"errors"=>{"type"=>"Требуется параметр.", "category"=>"Требуется параметр."}, \
|
26
|
+
# "errorsData"=>{}, "errorMessages"=>[], "statusCode"=>422}
|
27
|
+
body["errorMessages"]&.join(", ") || body["message"] || body.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Objects
|
5
|
+
#
|
6
|
+
# Objects::Attachment
|
7
|
+
#
|
8
|
+
class Attachment < Base
|
9
|
+
def download
|
10
|
+
resource.get(data["content"])
|
11
|
+
end
|
12
|
+
|
13
|
+
def thumbnail
|
14
|
+
resource.get(data["thumbnail"])
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def resource
|
20
|
+
@resource ||= Resources::Attachment.new(client)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Objects
|
5
|
+
#
|
6
|
+
# Objects::Base
|
7
|
+
#
|
8
|
+
class Base
|
9
|
+
attr_reader :client, :data, :context
|
10
|
+
|
11
|
+
def initialize(client, data, context = {})
|
12
|
+
@client = client
|
13
|
+
@data = data
|
14
|
+
@context = context
|
15
|
+
refresh_from(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def id
|
19
|
+
data["id"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(name, *args)
|
23
|
+
key = name.to_s
|
24
|
+
return wrap_value(data[key]) if data.key?(key)
|
25
|
+
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to_missing?(name, include_private = false)
|
30
|
+
data.key?(name.to_s) || super
|
31
|
+
end
|
32
|
+
|
33
|
+
# fetch full object from .self
|
34
|
+
def expand
|
35
|
+
return self unless data["self"]
|
36
|
+
|
37
|
+
response = client.conn.get(data["self"]).body
|
38
|
+
refresh_from(response)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def refresh_from(new_data)
|
44
|
+
@data = new_data
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def wrap_value(value)
|
51
|
+
case value
|
52
|
+
when Hash then wrap_hash(value)
|
53
|
+
when Array then value.map { |v| wrap_value(v) }
|
54
|
+
else value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def wrap_hash(hash)
|
59
|
+
return hash unless hash["self"]
|
60
|
+
|
61
|
+
resource_type = hash["self"].split("/").last(2).first
|
62
|
+
object_class = classify(resource_type)
|
63
|
+
|
64
|
+
return hash unless object_class
|
65
|
+
|
66
|
+
object_class.new(client, hash, context)
|
67
|
+
end
|
68
|
+
|
69
|
+
def classify(type)
|
70
|
+
class_name = type.split(/(?=[A-Z])/).map(&:capitalize).join.chomp("s")
|
71
|
+
Objects.const_get(class_name)
|
72
|
+
rescue NameError
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_objects(klass, data)
|
77
|
+
data.map { |item| klass.new(client, item, context) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Objects
|
5
|
+
#
|
6
|
+
# Objects::Comment
|
7
|
+
#
|
8
|
+
class Comment < Base
|
9
|
+
def attachments
|
10
|
+
@attachments ||= Collections::Attachments.new(client, context[:issue_id], comment_id: id)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(**attributes)
|
14
|
+
raise ArgumentError, "issue_id is required" unless context[:issue_id]
|
15
|
+
|
16
|
+
response = resource.update(context[:issue_id], id, **attributes)
|
17
|
+
refresh_from(response)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def resource
|
23
|
+
@resource ||= Resources::Comment.new(client)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Objects
|
5
|
+
#
|
6
|
+
# Objects::Issue
|
7
|
+
#
|
8
|
+
class Issue < Base
|
9
|
+
def comments
|
10
|
+
@comments ||= Collections::Comments.new(client, id)
|
11
|
+
end
|
12
|
+
|
13
|
+
def attachments
|
14
|
+
if data["attachments"]
|
15
|
+
build_objects(Objects::Attachment, data["attachments"])
|
16
|
+
else
|
17
|
+
Collections::Attachments.new(client, id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(**attributes)
|
22
|
+
response = resource.update(id, attributes)
|
23
|
+
refresh_from(response)
|
24
|
+
end
|
25
|
+
|
26
|
+
def transition(transition_id, **attributes)
|
27
|
+
resource.transition(id, transition_id, **attributes)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def resource
|
33
|
+
@resource ||= Resources::Issue.new(client)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Objects
|
5
|
+
#
|
6
|
+
# Objects::Queue
|
7
|
+
#
|
8
|
+
class Queue < Base
|
9
|
+
def issues
|
10
|
+
@issues ||= Collections::Issues.new(client, id)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(**attributes)
|
14
|
+
response = resource.update(id, attributes)
|
15
|
+
refresh_from(response)
|
16
|
+
end
|
17
|
+
|
18
|
+
def local_fields
|
19
|
+
@local_fields ||= Collections::LocalFields.new(client, id)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def resource
|
25
|
+
@resource ||= Resources::Queue.new(client)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
# Resources::Attachment
|
7
|
+
#
|
8
|
+
class Attachment < Base
|
9
|
+
# Create unattached file
|
10
|
+
def create(file, **attributes)
|
11
|
+
upload("attachments", file, attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Upload file directly to issue
|
15
|
+
def create_for_issue(issue_id, file, **attributes)
|
16
|
+
upload("issues/#{issue_id}/attachments", file, attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Upload file directly to comment
|
20
|
+
def create_for_comment(issue_id, comment_id, file, **attributes)
|
21
|
+
upload("issues/#{issue_id}/comments/#{comment_id}/attachments", file, attributes)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(id)
|
25
|
+
get("attachments/#{id}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def list(issue_id, **params)
|
29
|
+
get("issues/#{issue_id}/attachments", params)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def upload(path, file, attributes)
|
35
|
+
form = {
|
36
|
+
file: Faraday::Multipart::FilePart.new(
|
37
|
+
file.path,
|
38
|
+
mime_type(file),
|
39
|
+
File.basename(file)
|
40
|
+
)
|
41
|
+
}.merge(attributes)
|
42
|
+
|
43
|
+
handle_response client.multipart_conn.post(path, form)
|
44
|
+
end
|
45
|
+
|
46
|
+
def mime_type(file)
|
47
|
+
return file.content_type if file.respond_to?(:content_type)
|
48
|
+
|
49
|
+
require "mime/types"
|
50
|
+
MIME::Types.type_for(file.path).first&.content_type || "application/octet-stream"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module YandexTracker
|
6
|
+
module Resources
|
7
|
+
#
|
8
|
+
# Resources::Base
|
9
|
+
#
|
10
|
+
class Base
|
11
|
+
attr_reader :client
|
12
|
+
|
13
|
+
def initialize(client)
|
14
|
+
@client = client
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(path, params = {}, query_params = {})
|
18
|
+
handle_response client.conn.get(prepare_path(path, query_params), params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(path, body = {}, query_params = {})
|
22
|
+
handle_response client.conn.post(prepare_path(path, query_params), body)
|
23
|
+
end
|
24
|
+
|
25
|
+
def put(path, body = {}, query_params = {})
|
26
|
+
handle_response client.conn.put(prepare_path(path, query_params), body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def patch(path, body = {}, query_params = {})
|
30
|
+
handle_response client.conn.patch(prepare_path(path, query_params), body)
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(path, params = {}, query_params = {})
|
34
|
+
handle_response client.conn.delete(prepare_path(path, query_params), params)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def handle_response(response)
|
40
|
+
return response.body if response.success?
|
41
|
+
|
42
|
+
handle_error_response(response)
|
43
|
+
rescue Faraday::TimeoutError
|
44
|
+
raise Errors::TimeoutError, "Request timed out"
|
45
|
+
rescue Faraday::ConnectionFailed => e
|
46
|
+
raise Errors::ConnectionError, "Connection failed: #{e.message}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_error_response(response)
|
50
|
+
puts response.body
|
51
|
+
case response.status
|
52
|
+
when 401, 403 then raise Errors::Unauthorized, Errors.format_message(response.body)
|
53
|
+
when 404 then raise Errors::NotFound, Errors.format_message(response.body)
|
54
|
+
else raise Errors::ApiError, Errors.format_message(response.body)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def encode_path(path)
|
59
|
+
URI.encode_www_form_component(path.to_s)
|
60
|
+
rescue URI::InvalidURIError => e
|
61
|
+
raise Errors::ApiError, "Invalid path: #{e.message}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare_path(path, query_params = {})
|
65
|
+
segments = path.split("/").map { |segment| encode_path(segment) }
|
66
|
+
path = segments.join("/")
|
67
|
+
path = "#{path}?#{URI.encode_www_form(query_params)}" unless query_params.empty?
|
68
|
+
path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
# Resources::Category
|
7
|
+
#
|
8
|
+
class Category < Base
|
9
|
+
def list(**params)
|
10
|
+
get("fields/categories", params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(**attributes)
|
14
|
+
post("fields/categories", attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(id)
|
18
|
+
get("fields/categories/#{id}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
# Resources::Comment
|
7
|
+
#
|
8
|
+
class Comment < Base
|
9
|
+
def create(issue_id, **attributes)
|
10
|
+
post("issues/#{issue_id}/comments", attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(issue_id, comment_id, **attributes)
|
14
|
+
patch("issues/#{issue_id}/comments/#{comment_id}", attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list(issue_id, **params)
|
18
|
+
get("issues/#{issue_id}/comments", params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YandexTracker
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
# Resources::Field
|
7
|
+
#
|
8
|
+
class Field < Base
|
9
|
+
def list(**params)
|
10
|
+
get("fields", params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(**attributes)
|
14
|
+
post("fields", attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(id)
|
18
|
+
get("fields/#{id}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|