data_nexus 0.2.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/.github/workflows/ci.yml +43 -0
- data/.mise.local.toml.example +14 -0
- data/.mise.toml +36 -0
- data/.rubocop.yml +28 -0
- data/README.md +268 -0
- data/Rakefile +8 -0
- data/lib/data_nexus/client.rb +103 -0
- data/lib/data_nexus/collection.rb +120 -0
- data/lib/data_nexus/configuration.rb +74 -0
- data/lib/data_nexus/connection.rb +176 -0
- data/lib/data_nexus/errors.rb +78 -0
- data/lib/data_nexus/resources/member_consents.rb +125 -0
- data/lib/data_nexus/resources/member_enrollments.rb +122 -0
- data/lib/data_nexus/resources/members.rb +117 -0
- data/lib/data_nexus/resources/program_member.rb +134 -0
- data/lib/data_nexus/resources/program_members.rb +78 -0
- data/lib/data_nexus/resources/programs.rb +146 -0
- data/lib/data_nexus/version.rb +5 -0
- data/lib/data_nexus.rb +52 -0
- data/sig/datanexus.rbs +4 -0
- metadata +95 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'faraday/retry'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module DataNexus
|
|
8
|
+
# HTTP connection wrapper using Faraday
|
|
9
|
+
#
|
|
10
|
+
# Handles authentication, request/response processing, and error handling.
|
|
11
|
+
#
|
|
12
|
+
class Connection
|
|
13
|
+
# @return [Configuration] The connection configuration
|
|
14
|
+
attr_reader :config
|
|
15
|
+
|
|
16
|
+
# Initialize a new Connection
|
|
17
|
+
#
|
|
18
|
+
# @param config [Configuration] The configuration object
|
|
19
|
+
def initialize(config)
|
|
20
|
+
@config = config
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Perform a GET request
|
|
24
|
+
#
|
|
25
|
+
# @param path [String] The API endpoint path
|
|
26
|
+
# @param params [Hash] Query parameters
|
|
27
|
+
# @return [Hash] Parsed JSON response
|
|
28
|
+
def get(path, params = {})
|
|
29
|
+
request(:get, path, params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Perform a POST request
|
|
33
|
+
#
|
|
34
|
+
# @param path [String] The API endpoint path
|
|
35
|
+
# @param body [Hash] Request body
|
|
36
|
+
# @return [Hash] Parsed JSON response
|
|
37
|
+
def post(path, body = {})
|
|
38
|
+
request(:post, path, body)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Perform a PATCH request
|
|
42
|
+
#
|
|
43
|
+
# @param path [String] The API endpoint path
|
|
44
|
+
# @param body [Hash] Request body
|
|
45
|
+
# @return [Hash] Parsed JSON response
|
|
46
|
+
def patch(path, body = {})
|
|
47
|
+
request(:patch, path, body)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Perform a DELETE request
|
|
51
|
+
#
|
|
52
|
+
# @param path [String] The API endpoint path
|
|
53
|
+
# @return [Hash] Parsed JSON response (empty on 204 No Content)
|
|
54
|
+
def delete(path)
|
|
55
|
+
request(:delete, path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Build the Faraday connection
|
|
61
|
+
#
|
|
62
|
+
# @return [Faraday::Connection]
|
|
63
|
+
def faraday
|
|
64
|
+
@faraday ||= Faraday.new(url: config.base_url, ssl: ssl_options) do |conn|
|
|
65
|
+
configure_request(conn)
|
|
66
|
+
configure_headers(conn)
|
|
67
|
+
configure_timeouts(conn)
|
|
68
|
+
|
|
69
|
+
conn.response :raise_error
|
|
70
|
+
conn.adapter Faraday.default_adapter
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def ssl_options
|
|
75
|
+
{ verify: config.ssl_verify }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def configure_request(conn)
|
|
79
|
+
conn.request :json
|
|
80
|
+
conn.request :retry, max: 2, interval: 0.5, backoff_factor: 2,
|
|
81
|
+
exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def configure_headers(conn)
|
|
85
|
+
conn.headers['Authorization'] = "apikey #{config.api_key}"
|
|
86
|
+
conn.headers['Content-Type'] = 'application/json'
|
|
87
|
+
conn.headers['Accept'] = 'application/json'
|
|
88
|
+
conn.headers['User-Agent'] = "data-nexus-ruby/#{DataNexus::VERSION}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def configure_timeouts(conn)
|
|
92
|
+
conn.options.timeout = config.timeout
|
|
93
|
+
conn.options.open_timeout = config.open_timeout
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Perform an HTTP request
|
|
97
|
+
#
|
|
98
|
+
# @param method [Symbol] HTTP method (:get, :post, :patch)
|
|
99
|
+
# @param path [String] The API endpoint path
|
|
100
|
+
# @param params_or_body [Hash] Query params (GET) or body (POST/PATCH)
|
|
101
|
+
# @return [Hash] Parsed JSON response
|
|
102
|
+
def request(method, path, params_or_body = {})
|
|
103
|
+
response = faraday.public_send(method, path, params_or_body)
|
|
104
|
+
parse_response(response)
|
|
105
|
+
rescue Faraday::TimeoutError => e
|
|
106
|
+
raise TimeoutError, "Request timed out: #{e.message}"
|
|
107
|
+
rescue Faraday::ConnectionFailed => e
|
|
108
|
+
raise ConnectionError, "Connection failed: #{e.message}"
|
|
109
|
+
rescue Faraday::ClientError, Faraday::ServerError => e
|
|
110
|
+
handle_error_response(e)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Parse the response body as JSON
|
|
114
|
+
#
|
|
115
|
+
# @param response [Faraday::Response]
|
|
116
|
+
# @return [Hash]
|
|
117
|
+
def parse_response(response)
|
|
118
|
+
return {} if response.body.nil? || response.body.empty?
|
|
119
|
+
|
|
120
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
121
|
+
rescue JSON::ParserError
|
|
122
|
+
{ raw_body: response.body }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Handle error responses and raise appropriate exceptions
|
|
126
|
+
#
|
|
127
|
+
# @param error [Faraday::Error]
|
|
128
|
+
# @raise [APIError] The appropriate error subclass
|
|
129
|
+
def handle_error_response(error)
|
|
130
|
+
status, body, message = extract_error_details(error)
|
|
131
|
+
error_class = HTTP_STATUS_ERRORS[status] || APIError
|
|
132
|
+
|
|
133
|
+
raise_api_error(error_class, message, status, body, error.response)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def extract_error_details(error)
|
|
137
|
+
status = error.response&.dig(:status)
|
|
138
|
+
body = parse_error_body(error.response&.dig(:body))
|
|
139
|
+
message = extract_error_message(body, error.message)
|
|
140
|
+
|
|
141
|
+
[status, body, message]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def raise_api_error(error_class, message, status, body, response)
|
|
145
|
+
if error_class == RateLimitError
|
|
146
|
+
retry_after = response&.dig(:headers, 'retry-after')&.to_i
|
|
147
|
+
raise error_class.new(message, status: status, response_body: body, retry_after: retry_after)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
raise error_class.new(message, status: status, response_body: body)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Parse error response body
|
|
154
|
+
#
|
|
155
|
+
# @param body [String, nil]
|
|
156
|
+
# @return [Hash, nil]
|
|
157
|
+
def parse_error_body(body)
|
|
158
|
+
return nil if body.nil? || body.empty?
|
|
159
|
+
|
|
160
|
+
JSON.parse(body, symbolize_names: true)
|
|
161
|
+
rescue JSON::ParserError
|
|
162
|
+
{ raw_body: body }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Extract a human-readable error message
|
|
166
|
+
#
|
|
167
|
+
# @param body [Hash, nil]
|
|
168
|
+
# @param default [String]
|
|
169
|
+
# @return [String]
|
|
170
|
+
def extract_error_message(body, default)
|
|
171
|
+
return default unless body.is_a?(Hash)
|
|
172
|
+
|
|
173
|
+
body[:error] || body[:message] || body[:errors]&.first || default
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DataNexus
|
|
4
|
+
# Base error class for all DataNexus errors
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
# @return [Integer, nil] HTTP status code if applicable
|
|
7
|
+
attr_reader :status
|
|
8
|
+
|
|
9
|
+
# @return [Hash, nil] Response body if available
|
|
10
|
+
attr_reader :response_body
|
|
11
|
+
|
|
12
|
+
# Initialize a new Error
|
|
13
|
+
#
|
|
14
|
+
# @param message [String] Error message
|
|
15
|
+
# @param status [Integer, nil] HTTP status code
|
|
16
|
+
# @param response_body [Hash, nil] Parsed response body
|
|
17
|
+
def initialize(message = nil, status: nil, response_body: nil)
|
|
18
|
+
@status = status
|
|
19
|
+
@response_body = response_body
|
|
20
|
+
super(message)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Raised when the API key is missing or invalid
|
|
25
|
+
class ConfigurationError < Error; end
|
|
26
|
+
|
|
27
|
+
# Base class for HTTP errors returned by the API
|
|
28
|
+
class APIError < Error; end
|
|
29
|
+
|
|
30
|
+
# Raised when authentication fails (401)
|
|
31
|
+
class AuthenticationError < APIError; end
|
|
32
|
+
|
|
33
|
+
# Raised when the request is forbidden (403)
|
|
34
|
+
class ForbiddenError < APIError; end
|
|
35
|
+
|
|
36
|
+
# Raised when a resource is not found (404)
|
|
37
|
+
class NotFoundError < APIError; end
|
|
38
|
+
|
|
39
|
+
# Raised when the request is invalid (400)
|
|
40
|
+
class BadRequestError < APIError; end
|
|
41
|
+
|
|
42
|
+
# Raised when validation fails (422)
|
|
43
|
+
class UnprocessableEntityError < APIError; end
|
|
44
|
+
|
|
45
|
+
# Raised when rate limit is exceeded (429)
|
|
46
|
+
class RateLimitError < APIError
|
|
47
|
+
# @return [Integer, nil] Seconds until rate limit resets
|
|
48
|
+
attr_reader :retry_after
|
|
49
|
+
|
|
50
|
+
def initialize(message = nil, status: nil, response_body: nil, retry_after: nil)
|
|
51
|
+
@retry_after = retry_after
|
|
52
|
+
super(message, status: status, response_body: response_body)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Raised when there's a server error (500, 502, 503, 504)
|
|
57
|
+
class ServerError < APIError; end
|
|
58
|
+
|
|
59
|
+
# Raised when there's a network/connection error
|
|
60
|
+
class ConnectionError < Error; end
|
|
61
|
+
|
|
62
|
+
# Raised when a request times out
|
|
63
|
+
class TimeoutError < ConnectionError; end
|
|
64
|
+
|
|
65
|
+
# Maps HTTP status codes to error classes
|
|
66
|
+
HTTP_STATUS_ERRORS = {
|
|
67
|
+
400 => BadRequestError,
|
|
68
|
+
401 => AuthenticationError,
|
|
69
|
+
403 => ForbiddenError,
|
|
70
|
+
404 => NotFoundError,
|
|
71
|
+
422 => UnprocessableEntityError,
|
|
72
|
+
429 => RateLimitError,
|
|
73
|
+
500 => ServerError,
|
|
74
|
+
502 => ServerError,
|
|
75
|
+
503 => ServerError,
|
|
76
|
+
504 => ServerError
|
|
77
|
+
}.freeze
|
|
78
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DataNexus
|
|
4
|
+
module Resources
|
|
5
|
+
# Resource for managing member consents
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for creating, finding, updating, and deleting
|
|
8
|
+
# consents for a specific member.
|
|
9
|
+
#
|
|
10
|
+
# @example Create a consent
|
|
11
|
+
# client.programs("uuid").members("member-id").consents.create(
|
|
12
|
+
# consent: {
|
|
13
|
+
# category: "sms",
|
|
14
|
+
# member_response: true,
|
|
15
|
+
# consent_details: { sms_phone_number: "+15558675309" }
|
|
16
|
+
# }
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
# @example Find a consent
|
|
20
|
+
# consent = client.programs("uuid").members("member-id").consents.find(123)
|
|
21
|
+
#
|
|
22
|
+
# @example Update a consent
|
|
23
|
+
# client.programs("uuid").members("member-id").consents.update(123,
|
|
24
|
+
# consent: { member_response: false }
|
|
25
|
+
# )
|
|
26
|
+
#
|
|
27
|
+
# @example Delete a consent
|
|
28
|
+
# client.programs("uuid").members("member-id").consents.delete(123)
|
|
29
|
+
#
|
|
30
|
+
class MemberConsents
|
|
31
|
+
# @return [Connection] The HTTP connection
|
|
32
|
+
attr_reader :connection
|
|
33
|
+
|
|
34
|
+
# @return [String] The member ID
|
|
35
|
+
attr_reader :member_id
|
|
36
|
+
|
|
37
|
+
# @return [String] The program UUID
|
|
38
|
+
attr_reader :program_id
|
|
39
|
+
|
|
40
|
+
# Initialize a new MemberConsents resource
|
|
41
|
+
#
|
|
42
|
+
# @param connection [Connection] The HTTP connection
|
|
43
|
+
# @param member_id [String] The member ID
|
|
44
|
+
# @param program_id [String] The program UUID
|
|
45
|
+
def initialize(connection, member_id, program_id)
|
|
46
|
+
@connection = connection
|
|
47
|
+
@member_id = member_id
|
|
48
|
+
@program_id = program_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Create a new consent for the member
|
|
52
|
+
#
|
|
53
|
+
# @param consent [Hash] The consent attributes
|
|
54
|
+
# @option consent [String] :category The consent category (required)
|
|
55
|
+
# @option consent [Hash] :consent_details Additional consent details (required, can be empty)
|
|
56
|
+
# @option consent [String] :consented_at The datetime of consent (optional)
|
|
57
|
+
# @option consent [Boolean] :member_response Whether member agreed (optional, default: false)
|
|
58
|
+
# @return [Hash] Response containing :data with the created consent
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# response = client.programs("uuid").members("member-id").consents.create(
|
|
62
|
+
# consent: {
|
|
63
|
+
# category: "sms",
|
|
64
|
+
# member_response: true,
|
|
65
|
+
# consent_details: { sms_phone_number: "+15558675309" }
|
|
66
|
+
# }
|
|
67
|
+
# )
|
|
68
|
+
# created_consent = response[:data]
|
|
69
|
+
def create(consent:)
|
|
70
|
+
consent_with_program = consent.merge(program_id: program_id)
|
|
71
|
+
body = { consent: consent_with_program }
|
|
72
|
+
connection.post(base_path, body)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Find a specific consent by ID
|
|
76
|
+
#
|
|
77
|
+
# @param consent_id [Integer, String] The consent ID
|
|
78
|
+
# @return [Hash] The consent data
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# consent = client.programs("uuid").members("member-id").consents.find(123)
|
|
82
|
+
# puts consent[:category]
|
|
83
|
+
def find(consent_id)
|
|
84
|
+
response = connection.get("#{base_path}/#{consent_id}")
|
|
85
|
+
response[:data]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Update a consent's attributes
|
|
89
|
+
#
|
|
90
|
+
# @param consent_id [Integer, String] The consent ID
|
|
91
|
+
# @param consent [Hash] The consent attributes to update
|
|
92
|
+
# @return [Hash] Response containing :data with the updated consent
|
|
93
|
+
#
|
|
94
|
+
# @example
|
|
95
|
+
# response = client.programs("uuid").members("member-id").consents.update(123,
|
|
96
|
+
# consent: { member_response: false }
|
|
97
|
+
# )
|
|
98
|
+
# updated_consent = response[:data]
|
|
99
|
+
def update(consent_id, consent:)
|
|
100
|
+
body = { consent: consent }
|
|
101
|
+
connection.patch("#{base_path}/#{consent_id}", body)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Delete a consent
|
|
105
|
+
#
|
|
106
|
+
# @param consent_id [Integer, String] The consent ID
|
|
107
|
+
# @return [Hash] Empty hash on success (204 No Content)
|
|
108
|
+
#
|
|
109
|
+
# @example
|
|
110
|
+
# client.programs("uuid").members("member-id").consents.delete(123)
|
|
111
|
+
def delete(consent_id)
|
|
112
|
+
connection.delete("#{base_path}/#{consent_id}")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# Base path for member consents endpoints
|
|
118
|
+
#
|
|
119
|
+
# @return [String]
|
|
120
|
+
def base_path
|
|
121
|
+
"/api/members/#{member_id}/consents"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DataNexus
|
|
4
|
+
module Resources
|
|
5
|
+
# Resource for managing member enrollments
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for creating, finding, updating, and deleting
|
|
8
|
+
# enrollments for a specific member.
|
|
9
|
+
#
|
|
10
|
+
# @example Create an enrollment
|
|
11
|
+
# client.programs("program-uuid").members("member-id").enrollments.create(
|
|
12
|
+
# enrollment: {
|
|
13
|
+
# enrolled_at: "2024-01-01T00:00:00Z",
|
|
14
|
+
# expires_at: "2025-01-01T00:00:00Z"
|
|
15
|
+
# }
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
# @example Find an enrollment
|
|
19
|
+
# enrollment = client.programs("program-uuid").members("member-id").enrollments.find(123)
|
|
20
|
+
#
|
|
21
|
+
# @example Update an enrollment
|
|
22
|
+
# client.programs("program-uuid").members("member-id").enrollments.update(123,
|
|
23
|
+
# enrollment: { expires_at: "2026-01-01T00:00:00Z" }
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# @example Delete an enrollment
|
|
27
|
+
# client.programs("program-uuid").members("member-id").enrollments.delete(123)
|
|
28
|
+
#
|
|
29
|
+
class MemberEnrollments
|
|
30
|
+
# @return [Connection] The HTTP connection
|
|
31
|
+
attr_reader :connection
|
|
32
|
+
|
|
33
|
+
# @return [String] The member ID
|
|
34
|
+
attr_reader :member_id
|
|
35
|
+
|
|
36
|
+
# @return [String] The program UUID
|
|
37
|
+
attr_reader :program_id
|
|
38
|
+
|
|
39
|
+
# Initialize a new MemberEnrollments resource
|
|
40
|
+
#
|
|
41
|
+
# @param connection [Connection] The HTTP connection
|
|
42
|
+
# @param member_id [String] The member ID
|
|
43
|
+
# @param program_id [String] The program UUID
|
|
44
|
+
def initialize(connection, member_id, program_id)
|
|
45
|
+
@connection = connection
|
|
46
|
+
@member_id = member_id
|
|
47
|
+
@program_id = program_id
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Create a new enrollment for the member
|
|
51
|
+
#
|
|
52
|
+
# @param enrollment [Hash] The enrollment attributes
|
|
53
|
+
# @option enrollment [String] :enrolled_at The datetime of enrollment (required)
|
|
54
|
+
# @option enrollment [String] :expires_at The datetime when enrollment expires (optional)
|
|
55
|
+
# @return [Hash] Response containing :data with the created enrollment
|
|
56
|
+
#
|
|
57
|
+
# @note The program_id is automatically injected from the resource chain
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# response = client.programs("program-uuid").members("member-id").enrollments.create(
|
|
61
|
+
# enrollment: {
|
|
62
|
+
# enrolled_at: "2024-01-01T00:00:00Z"
|
|
63
|
+
# }
|
|
64
|
+
# )
|
|
65
|
+
# created_enrollment = response[:data]
|
|
66
|
+
def create(enrollment:)
|
|
67
|
+
enrollment_with_program = enrollment.merge(program_id: program_id)
|
|
68
|
+
body = { enrollment: enrollment_with_program }
|
|
69
|
+
connection.post(base_path, body)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Find a specific enrollment by ID
|
|
73
|
+
#
|
|
74
|
+
# @param enrollment_id [Integer, String] The enrollment ID
|
|
75
|
+
# @return [Hash] The enrollment data
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# enrollment = client.programs("program-uuid").members("member-id").enrollments.find(123)
|
|
79
|
+
# puts enrollment[:program_id]
|
|
80
|
+
def find(enrollment_id)
|
|
81
|
+
response = connection.get("#{base_path}/#{enrollment_id}")
|
|
82
|
+
response[:data]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Update an enrollment's attributes
|
|
86
|
+
#
|
|
87
|
+
# @param enrollment_id [Integer, String] The enrollment ID
|
|
88
|
+
# @param enrollment [Hash] The enrollment attributes to update
|
|
89
|
+
# @return [Hash] Response containing :data with the updated enrollment
|
|
90
|
+
#
|
|
91
|
+
# @example
|
|
92
|
+
# response = client.programs("program-uuid").members("member-id").enrollments.update(123,
|
|
93
|
+
# enrollment: { expires_at: "2026-01-01T00:00:00Z" }
|
|
94
|
+
# )
|
|
95
|
+
# updated_enrollment = response[:data]
|
|
96
|
+
def update(enrollment_id, enrollment:)
|
|
97
|
+
body = { enrollment: enrollment }
|
|
98
|
+
connection.patch("#{base_path}/#{enrollment_id}", body)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Delete an enrollment
|
|
102
|
+
#
|
|
103
|
+
# @param enrollment_id [Integer, String] The enrollment ID
|
|
104
|
+
# @return [Hash] Empty hash on success (204 No Content)
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# client.programs("program-uuid").members("member-id").enrollments.delete(123)
|
|
108
|
+
def delete(enrollment_id)
|
|
109
|
+
connection.delete("#{base_path}/#{enrollment_id}")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Base path for member enrollments endpoints
|
|
115
|
+
#
|
|
116
|
+
# @return [String]
|
|
117
|
+
def base_path
|
|
118
|
+
"/api/members/#{member_id}/enrollments"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DataNexus
|
|
4
|
+
module Resources
|
|
5
|
+
# Resource for managing members at the top level
|
|
6
|
+
#
|
|
7
|
+
# Provides methods for listing, finding, and updating members
|
|
8
|
+
# without requiring a program scope.
|
|
9
|
+
#
|
|
10
|
+
# @example List members with filters
|
|
11
|
+
# client.members.list(
|
|
12
|
+
# first_name: "george",
|
|
13
|
+
# born_on: "1976-07-04"
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example Paginate through members
|
|
17
|
+
# collection = client.members.list(first: 50)
|
|
18
|
+
# collection.each_page { |page| process(page.data) }
|
|
19
|
+
#
|
|
20
|
+
# @example Find a specific member
|
|
21
|
+
# member = client.members.find("member-id")
|
|
22
|
+
#
|
|
23
|
+
class Members
|
|
24
|
+
# @return [Connection] The HTTP connection
|
|
25
|
+
attr_reader :connection
|
|
26
|
+
|
|
27
|
+
# Initialize a new Members resource
|
|
28
|
+
#
|
|
29
|
+
# @param connection [Connection] The HTTP connection
|
|
30
|
+
def initialize(connection)
|
|
31
|
+
@connection = connection
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# List members with optional filters
|
|
35
|
+
#
|
|
36
|
+
# @param after [String, nil] Cursor for next group of records
|
|
37
|
+
# @param before [String, nil] Cursor for previous group of records
|
|
38
|
+
# @param first [Integer, nil] Number of records to fetch after cursor
|
|
39
|
+
# @param last [Integer, nil] Number of records to fetch before cursor
|
|
40
|
+
# @param born_on [String, nil] Filter by date of birth (YYYY-MM-DD)
|
|
41
|
+
# @param first_name [String, nil] Filter by first name
|
|
42
|
+
# @param last_name [String, nil] Filter by last name
|
|
43
|
+
# @param program_id [String, nil] Filter by program UUID (members eligible for program)
|
|
44
|
+
# @param updated_since [String, nil] Filter by update time (ISO 8601 datetime)
|
|
45
|
+
#
|
|
46
|
+
# @return [Collection] Paginated collection of members
|
|
47
|
+
#
|
|
48
|
+
# @example Basic listing
|
|
49
|
+
# collection = client.members.list
|
|
50
|
+
# collection.data.each { |m| puts m[:first_name] }
|
|
51
|
+
#
|
|
52
|
+
# @example With filters
|
|
53
|
+
# collection = client.members.list(
|
|
54
|
+
# born_on: "1976-07-04",
|
|
55
|
+
# first_name: "george"
|
|
56
|
+
# )
|
|
57
|
+
#
|
|
58
|
+
# @example With pagination
|
|
59
|
+
# collection = client.members.list(first: 50, after: "cursor")
|
|
60
|
+
#
|
|
61
|
+
# @example Filter by program eligibility
|
|
62
|
+
# collection = client.members.list(program_id: "uuid")
|
|
63
|
+
#
|
|
64
|
+
# @example Filter by update time
|
|
65
|
+
# collection = client.members.list(updated_since: "2024-01-01T00:00:00Z")
|
|
66
|
+
def list(**params)
|
|
67
|
+
allowed_params = %i[
|
|
68
|
+
after before first last
|
|
69
|
+
born_on first_name last_name
|
|
70
|
+
program_id updated_since
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
query_params = params.slice(*allowed_params).compact
|
|
74
|
+
response = connection.get(base_path, query_params)
|
|
75
|
+
Collection.new(response, resource: self, params: query_params)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Find a specific member by ID
|
|
79
|
+
#
|
|
80
|
+
# @param member_id [String] The member ID
|
|
81
|
+
# @return [Hash] The member data
|
|
82
|
+
#
|
|
83
|
+
# @example
|
|
84
|
+
# member = client.members.find("member-id")
|
|
85
|
+
# puts member[:first_name]
|
|
86
|
+
def find(member_id)
|
|
87
|
+
response = connection.get("#{base_path}/#{member_id}")
|
|
88
|
+
response[:data]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Update a member's attributes
|
|
92
|
+
#
|
|
93
|
+
# @param member_id [String] The member ID
|
|
94
|
+
# @param member [Hash] The member attributes to update
|
|
95
|
+
# @return [Hash] Response containing :data with the updated member
|
|
96
|
+
#
|
|
97
|
+
# @example
|
|
98
|
+
# response = client.members.update("member-id",
|
|
99
|
+
# member: { phone_number: "+15551234567" }
|
|
100
|
+
# )
|
|
101
|
+
# updated_member = response[:data]
|
|
102
|
+
def update(member_id, member:)
|
|
103
|
+
body = { member: member }
|
|
104
|
+
connection.patch("#{base_path}/#{member_id}", body)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Base path for members endpoints
|
|
110
|
+
#
|
|
111
|
+
# @return [String]
|
|
112
|
+
def base_path
|
|
113
|
+
'/api/members'
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|